<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Tutorials – IMG.LY Blog</title><description>Posts tagged Tutorials on the IMG.LY blog.</description><link>https://img.ly/blog/tag/tutorials/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Tutorials – IMG.LY Blog</title><link>https://img.ly/blog/tag/tutorials/</link></image><atom:link href="https://img.ly/blog/tag/tutorials/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Tue, 09 Jun 2026 09:48:30 GMT</lastBuildDate><ttl>60</ttl><item><title>A Modern Video Editor SDK for your Flutter App</title><link>https://img.ly/blog/a-modern-video-editor-sdk-for-your-flutter-app/</link><guid isPermaLink="true">https://img.ly/blog/a-modern-video-editor-sdk-for-your-flutter-app/</guid><description>Learn how to integrate IMG.LY&apos;s video editor for Flutter into your app and how to best leverage its capabilities.</description><pubDate>Fri, 13 Sep 2024 11:26:15 GMT</pubDate><content:encoded>&lt;p&gt;Video has been the only type of content with steadily growing demand, and it will remain a staple and an expected medium for users to create and consume.&lt;/p&gt;
&lt;p&gt;With platforms such as TikTok, Instagram Reels, and YouTube Shorts dominating and shaping user habits, the ability to create and edit videos directly within your app can significantly increase engagement.&lt;/p&gt;
&lt;p&gt;In this post, we’ll show how to integrate a video editor into a Flutter app using CreativeEditor SDK and customize it for specific use cases, like creating TikTok-style short videos.&lt;/p&gt;
&lt;p&gt;You can check out the code for the default and custom &lt;a href=&quot;https://github.com/imgly/cesdk-flutter-examples/tree/main/showcases/lib/data/code_examples/video&quot;&gt;Flutter video editor on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-add-a-video-editor-to-your-flutter-app&quot;&gt;Why add a Video Editor to Your Flutter App?&lt;/h3&gt;
&lt;p&gt;The last decade has made one thing clear: video content is king. Particularly, short-form dominates digital content. At the forefront of this trend are apps like TikTok, which made people more comfortable creating, editing, and sharing bite-sized clips.&lt;/p&gt;
&lt;p&gt;These apps have capitalized on their ability to let users apply filters, and add audio overlays, text, and more, empowering users at all skill levels to make professional-looking videos with minimal effort.&lt;/p&gt;
&lt;p&gt;A host of different use cases stand to profit from this trend by enhancing their user experience with video editing features, particularly marketing tech tooling and apps with a messaging or social media component.&lt;/p&gt;
&lt;p&gt;In general, video creation lowers the threshold for user-generated content and can positively impact distribution and product engagement.&lt;/p&gt;
&lt;p&gt;Flutter, Google’s UI toolkit for building apps across platforms from a single code base, is a perfect fit for &lt;a href=&quot;https://IMG.LY&quot;&gt;IMG.LY&lt;/a&gt;’s Video Editor. What often motivates the choice to use Flutter is that It ensures performance, flexibility, and a seamless user experience on iOS and Android devices. Similarly, our SDK is designed to offer a native cross-platform experience, while relying on the same underlying graphics processing engine, ensuring consistency and interoperability.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-started-integrating-the-video-editor-in-flutter&quot;&gt;Getting Started: Integrating the Video Editor in Flutter&lt;/h3&gt;
&lt;p&gt;Let’s dive into how to set up a video editor in your Flutter app using CreativeEditor SDK.&lt;/p&gt;
&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;
&lt;p&gt;Before we get into the details, here’s what you’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flutter 3.16.0+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dart 2.12.0+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iOS 16 or later&lt;/strong&gt; (as the video editor is currently only available for iOS, Android coming soon)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swift 5.10 and Xcode 15.4&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, ensure your &lt;code&gt;pubspec.yml&lt;/code&gt; file includes the necessary dependencies:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  flutter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sdk&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flutter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  imgly_editor&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.34.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once your environment is set up, you’re ready to start coding.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-the-editor&quot;&gt;Setting Up the Editor&lt;/h3&gt;
&lt;p&gt;After adding the &lt;code&gt;imgly_editor&lt;/code&gt; dependency, import the package in your code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &quot;package:imgly_editor/imgly_editor.dart&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step is to initialize the editor by providing an instance of &lt;code&gt;EditorSettings&lt;/code&gt;. This setup requires a license key, which you can obtain from CreativeEditor SDK, and an optional &lt;code&gt;userId&lt;/code&gt; that helps track monthly active users (MAUs) across different devices.&lt;/p&gt;
&lt;p&gt;Here’s a basic example of how to open the video editor:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; VideoEditorSolution&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  /// Opens the editor.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; openEditor&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; settings &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; EditorSettings&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        license&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;YOUR_LICENSE&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        userId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;YOUR_USER_ID&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; preset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; EditorPreset&lt;/span&gt;&lt;span&gt;.video;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		/// Open the editor and retrieve the editing result.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; IMGLYEditor&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;openEditor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        preset&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; preset,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        settings&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This launches the editor with the video preset, enabling users to trim, cut, and add filters, text overlays, music, and more to their videos.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 814px) 814px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;814&quot; height=&quot;836&quot; src=&quot;https://img.ly/_astro/flutter-video-editor_Z1vXnTi.webp&quot; srcset=&quot;/_astro/flutter-video-editor_mCglm.webp 640w, /_astro/flutter-video-editor_bQizb.webp 750w, /_astro/flutter-video-editor_Z1vXnTi.webp 814w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;use-cases-building-a-tiktok-like-experience&quot;&gt;Use Cases: Building a TikTok-Like Experience&lt;/h3&gt;
&lt;p&gt;Now that we have integrated the basic editor configuration into your Flutter app, let’s revisit some prominent use cases and how to configure the editor to support them.&lt;/p&gt;
&lt;h3 id=&quot;short-form-video-creation&quot;&gt;Short-Form Video Creation&lt;/h3&gt;
&lt;p&gt;For a short-form video, TikTok-like user experience, the most important factor is the ease of use of any video editor and content such as stickers or audio overlays.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The following SDK capabilities can help you achieve that goal:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control Video Duration and Position in Time&lt;/strong&gt;: Users should be able to trim video clips on a timeline and position them relative to other tracks, such as audio clips.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filters &amp;#x26; Effects&lt;/strong&gt;: Filters are essential for setting the tone of the video. Whether users want a retro, high contrast, or soft pastel look, filters provide creative control over the visual style.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text &amp;#x26; Stickers&lt;/strong&gt;: Allow users to add text captions—especially important for content often watched on mute. Stickers and emojis can add a playful element to videos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Music &amp;#x26; Audio&lt;/strong&gt;: Like TikTok, you can allow users to add background music or sound effects. Providing a library of popular tracks or sound snippets can enhance the content creation experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video Templates&lt;/strong&gt;: Give your users a head start with customizable video templates for different themes, topics, and occasions. &lt;a href=&quot;https://IMG.LY&quot;&gt;IMG.LY&lt;/a&gt;’s CE.SDK comes with a web video editor to help build and deliver these templates to your Flutter app. You can explore a range of different templates in our &lt;a href=&quot;https://img.ly/showcases/cesdk/video-ui/web?template=month-in-review&quot;&gt;video editor demo&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By integrating these features, you’ll empower users to create engaging, shareable content, elevating your app’s video creation experience to the level of consumer-grade social media apps.&lt;/p&gt;
&lt;h3 id=&quot;influencers-and-marketing&quot;&gt;Influencers and Marketing&lt;/h3&gt;
&lt;p&gt;If your app is targeting influencers or businesses, the ability to quickly create branded video content is key. You can offer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Branded Templates&lt;/strong&gt;: Pre-made templates that align with a brand’s style, allowing users to easily drop in their video footage and text while maintaining brand consistency. Again, check out the web editor demo to see how to build &lt;a href=&quot;https://img.ly/showcases/cesdk/video-ui/web?template=month-in-review&quot;&gt;video templates&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watermarks&lt;/strong&gt;: Adding a watermark or logo as a brand signifier to videos can help with brand visibility and make sure content always points back to the creator.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setup allows influencers and brands to create professional-looking videos that they can share across social media platforms with ease.&lt;/p&gt;
&lt;h3 id=&quot;ecommerce-and-user-generated-content&quot;&gt;eCommerce and User-Generated Content&lt;/h3&gt;
&lt;p&gt;Video is a powerful medium to showcase products, especially on marketplaces that can leverage user-generated content and enable vendors as well as customers to create product or review videos.&lt;/p&gt;
&lt;p&gt;For example, users could create unboxing videos, product reviews, or tutorials using the same trimming, filter, and music tools offered in a TikTok-style editor. These videos can then be shared on social media or used within the app to enhance the shopping experience.&lt;/p&gt;
&lt;p&gt;Likewise, showcasing products using authentic demo videos tap into familiar patterns of video consumption and can boost sales for vendors.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;customization-options-with-creativeeditor-sdk&quot;&gt;Customization Options with CreativeEditor SDK&lt;/h3&gt;
&lt;p&gt;We have designed the Flutter plugin to be adaptable to match your brand and use case-specific requirements. Soon the plugin will also support more advanced customization options such as the ability to &lt;strong&gt;activate or deactivate features by default.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;UI Customization&lt;/strong&gt;: Modify the look and feel of the editor to match your app’s branding. We provide options for &lt;strong&gt;theming&lt;/strong&gt;, &lt;strong&gt;styling color palettes&lt;/strong&gt;, and &lt;strong&gt;callbacks&lt;/strong&gt; to hook into editor events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video Presets&lt;/strong&gt;: You can create custom video presets based on your users’ needs. For instance, you may want to limit video length or optimize for certain resolutions, depending on the target platform.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video Templates&lt;/strong&gt;: You can use the CE.SDK web UI to generate any number of video templates including collages, text designs, and animations to give your users professional-looking starting points for their video designs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assets&lt;/strong&gt;: Provide custom filters, fonts, and stickers to enhance the user experience. For niche apps, this can be an excellent way to engage users by offering content that feels unique to your community.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Integrating a video editor into your Flutter app will improve your UX, and help boost engagement, retention, and potential distribution of your product, whether you’re building a social media platform, an influencer tool, or an e-commerce app. With CreativeEditor SDK, you can create a TikTok-like video editing experience or offer specialized tools for businesses and creators.&lt;/p&gt;
&lt;p&gt;By following the steps outlined in this post, you can bring professional-level video editing features to your users. Explore CE.SDK’s video capabilities and &lt;a href=&quot;https://img.ly/docs/cesdk/flutter/prebuilt-solutions/video-editor-9e533a/&quot;&gt;dive into the docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Stay tuned for more updates, and please &lt;a href=&quot;https://img.ly/forms/contact-sales?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=plugins1&quot;&gt;reach out&lt;/a&gt; if you have any questions. Thank you for reading.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Over 3,000 creative professionals gain early access to our new features, demos, and updates—don’t miss out, and&lt;/strong&gt; &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;&lt;strong&gt;subscribe&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to our newsletter.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Jan</dc:creator><media:content url="https://blog.img.ly/2024/09/how-to-flutter-photo-video-design-editor-sdk.png" medium="image"/><category>Video Editing</category><category>Flutter</category><category>Tutorial</category><category>Learning</category></item><item><title>How to Build a TikTok Clone for iOS with Swift &amp; CreativeEditor SDK</title><link>https://img.ly/blog/how-to-build-a-tiktok-clone-for-ios/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-a-tiktok-clone-for-ios/</guid><description>Learn how to build a TikTok clone for iOS with Swift and CreativeEditor SDK with this step-by-step in-depth tutorial.</description><pubDate>Thu, 30 Mar 2023 11:20:26 GMT</pubDate><content:encoded>&lt;p&gt;Now that TikTok is facing a ban in the US any day now, we better wait in the wings with an alternative ready to go and scoop up those millions of hobby dancers, micro bloggers, and would-be influencers.&lt;/p&gt;
&lt;p&gt;In this article, you’ll learn how to use Swift, SwiftUI, and the IMG.LY CreativeEditor SDK to build a simple iOS app for recording, editing, and viewing short videos. This app features views providing core functionalities similar to making a post in a video-sharing platform like TikTok.&lt;/p&gt;
&lt;p&gt;The editing controller is the central hub. It allows users to capture video clips and enhance their videos by adding filters, stickers, music, and other effects. CreativeEditor SDK calls the overall project a “scene” and the parts (clips, stickers, text, etc.) “blocks”. Once the scene is ready, the user can compose it into a standard video file and export it. Finally, the playback controller showcases the finished projects using Apple’s standard VideoPlayer struct. For recording and editing, the CreativeEditor SDK’s Camera and Video Editor handle the heavy lifting, enabling swift development of a robust app. You can easily extend its features, filters, and creative options as your users demand.&lt;/p&gt;
&lt;p&gt;Follow along to see how to capture video, enhance it, and export the finished product as a polished movie file.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-your-project&quot;&gt;Setting Up Your Project&lt;/h2&gt;
&lt;p&gt;You can try out the TikTok clone by cloning the &lt;a href=&quot;https://github.com/imgly/tiktok-clone-ios-cesdk&quot;&gt;GitHub repository supporting the article&lt;/a&gt;. Otherwise, follow this step-by-step tutorial and learn how to build the TikTok clone with CreativeEditor SDK by yourself.&lt;/p&gt;
&lt;p&gt;To add the CreativeEditor SDK to an Xcode project, include it with your other package dependencies. Use the URL below to add the package, either to your &lt;code&gt;Package.swift&lt;/code&gt; file or using &lt;code&gt;File &gt; Add Packages...&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://github.com/imgly/IMGLYUI-swift&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The package includes a number of different frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IMGLYUI, an umbrella framework of all of the different editors and a camera.&lt;/li&gt;
&lt;li&gt;IMGLYCamera, a standalone camera View. This is the same camera as the editors use.&lt;/li&gt;
&lt;li&gt;IMGLYApparelEditor, IMGLYDesignEditor, IMGLYPostcardEditor, and IMGLYVideoEditor which are four different configurations to support different use cases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these frameworks uses the IMG.LY Engine for image processing. To include them in your project, navigate to the ‘Frameworks, Libraries, and Embedded Content’ section in your app’s General settings. Click the ’+’ button and select the desired frameworks. Initially, consider adding the IMGLYUI package, as it encompasses all other packages. Before releasing your app, review and include only the necessary components to minimize app size and avoid unused code.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Adding a framework to the app&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 590px) 590px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;590&quot; height=&quot;646&quot; src=&quot;https://img.ly/_astro/frameworks_ZpiIEt.webp&quot; srcset=&quot;/_astro/frameworks_ZpiIEt.webp 590w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once Xcode downloads and resolves the packages, you just need to &lt;code&gt;include IMGLYVideoEditor&lt;/code&gt; in any of the files that will reference the editor in your app. When you’re working with the underlying engine directly you’ll also want to &lt;code&gt;include IMGLYEngine&lt;/code&gt;. You’ll only work with the engine as you start to customize the workflow.&lt;/p&gt;
&lt;h2 id=&quot;asking-for-permissions&quot;&gt;Asking for Permissions&lt;/h2&gt;
&lt;p&gt;Before any app can access the phone’s microphone, camera, documents or photo library, the user must specifically give permission to record audio or video as well as access the user’s Photo library. If your app doesn’t secure these permissions properly before trying to use the camera the app will crash and also probably won’t get through Apple’s app review. In earlier versions of iOS you could develop an app on the Simulator without asking permissions, but always on the device the app will crash. In current versions of iOS the app crashes regardless of platform. The dialogue requesting permissions is a system dialog and looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;permission dialog&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 820px) 820px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;820&quot; height=&quot;453&quot; src=&quot;https://img.ly/_astro/permission1_ZWFkBh.webp&quot; srcset=&quot;/_astro/permission1_1LPnuv.webp 640w, /_astro/permission1_OFysD.webp 750w, /_astro/permission1_ZWFkBh.webp 820w&quot;&gt;&lt;/p&gt;
&lt;p&gt;You do not have the ability to change this dialog. You can supply the reason you are asking for the permission using a key in the &lt;code&gt;info.plist&lt;/code&gt; file in the Xcode project. In the example above “Lets you record videos” is in the &lt;code&gt;info.plist&lt;/code&gt;. For the video camera you will have to ask for video and audio permission. The first step is to add a short message to the user in the &lt;code&gt;info.plist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Info plist example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 723px) 723px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;723&quot; height=&quot;102&quot; src=&quot;https://img.ly/_astro/infoplist_FispA.webp&quot; srcset=&quot;/_astro/infoplist_1Jamxn.webp 640w, /_astro/infoplist_FispA.webp 723w&quot;&gt;&lt;/p&gt;
&lt;p&gt;For video the key to add is &lt;code&gt;NSCameraUsageDescription&lt;/code&gt; and for the microphone you need to add &lt;code&gt;NSMicrophoneUsageDescription&lt;/code&gt;. Whenever your app attempts to use the camera, iOS will first check to see if the user has already granted access. If not, it will then display the dialogs using your entries in the &lt;code&gt;info.plist&lt;/code&gt; or crash if you have not provided entries. The user might be surprised by these dialog boxes and may accidentally tap &lt;code&gt;Don&apos;t Allow&lt;/code&gt; if they are trying to quickly launch the camera. It is better practice to set up some kind of onboarding view and secure the permissions before you need them. You might have a view that explains what you are going to ask for and then displays the permission dialog.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;switch&lt;/span&gt;&lt;span&gt; AVCaptureDevice.&lt;/span&gt;&lt;span&gt;authorizationStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .video) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .authorized&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user has authroized permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .denied&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user has previously denied permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //perhaps we should ask them again&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .restricted&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user cannot grant permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .notDetermined&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user has never been asked for permission&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //so let&apos;s ask them now&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    AVCaptureDevice.&lt;/span&gt;&lt;span&gt;requestAccess&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .video) { accessGranted &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; accessGranted {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        //the user has authorized permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        //the user has denied permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @unknown&lt;/span&gt;&lt;span&gt; default:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The snippet above lets your app read the permission status for the video camera and ask for permission if it has not been granted. To request access to the microphone pass &lt;code&gt;.audio&lt;/code&gt; to the &lt;code&gt;AVCaptureDevice.authorizationStatus(for:)&lt;/code&gt; and &lt;code&gt;AVCaptureDevice.requestAccess(for:)&lt;/code&gt; functions. The &lt;code&gt;AVCaptureDevice.requestAccess(for:)&lt;/code&gt; command is what displays the system dialog actually requesting access. The &lt;code&gt;accessGranted&lt;/code&gt; variable reports back to your app what the user chose. You can take care of getting the permissions any time but when the CreativeEditor SDK first launches it will attempt to access the camera, so the dialog will appear if the user has not already granted permission.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.restricted&lt;/code&gt; case is fairly new. In this case, there are policies on the phone that prohibit the user from granting permission to your app. In addition to asking permissions during onboarding, it is good practice to check for permission every time before you attempt to run any code that uses the camera. The user can change permissions using the Settings app on their phone at any time. If the user has denied permissions and you present the video camera anyway, your app will record black video frames and silence on audio tracks.&lt;/p&gt;
&lt;p&gt;In addition to asking for camera and microphone permissions, your app will probably want to access the photos on the user’s phone. You will need to add an entry to the &lt;code&gt;info.plist&lt;/code&gt; for &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt;. As with the camera and the microphone, the dialog will appear when the Video Editor first attempts access, but you can give your user some ability to grant permission during onboarding. For the user’s photos, you can check the authorization status using&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; PHPhotoLibrary.&lt;/span&gt;&lt;span&gt;authroizationStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .readWrite)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with the camera, the photo library has &lt;code&gt;PHPhotoLibrary.requestAuthroization(for: .readWrite)&lt;/code&gt; but instead of just returning a “granted” or “denied” status, this command returns the actual new status. In addition to the status values for the camera, the photo library may return a &lt;code&gt;.limited&lt;/code&gt; status meaning that the user has granted permission to only some of the photos. If the user has chosen to share only some of their photos, Apple provides some views you can present so that the user can manage the photos. Any videos that your app saves to the user’s photo library will always be available when the user has chosen &lt;code&gt;.limited&lt;/code&gt;. You can read more about how to work with permissions and the user’s photo library by reading this Apple article &lt;a href=&quot;https://developer.apple.com/documentation/photokit/delivering-an-enhanced-privacy-experience-in-your-photos-app&quot;&gt;Delivering an Enhanced Privacy Experience in Your Photos App&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;making-video-recordings&quot;&gt;Making Video Recordings&lt;/h2&gt;
&lt;p&gt;The start of a great TikTok is the camera. When our user wants to make a new video clip with our app, they tap on the button at the bottom of the initial screen to start the creation workflow.&lt;/p&gt;
&lt;p&gt;Here there is a decision to make. TikTok and the CreativeEditor SDK can each start with the creation controls and a video preview. When using the CreativeEditor SDK though, you can also start with some video that came from somewhere else. So if you already have some camera code you like, or if you want to start with some video clip from somewhere else, you can insert that into the editor when it launches. You can do that by leveraging the &lt;code&gt;onCreate&lt;/code&gt; modifier of the editor.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; engine &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; EngineSettings&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;license&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;your license code&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                              userID&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;your unique user id&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;VideoEditor&lt;/span&gt;&lt;span&gt;(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .imgly.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;({ engine &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fromVideo&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dog_water&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //set other defaults&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, the app initializes an instance of the Video Editor view and then reads the &lt;code&gt;dog_water.mov&lt;/code&gt; file from the app bundle. launches the editor with that.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Starting scene with a dog video&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 480px) 480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;480&quot; height=&quot;1039&quot; src=&quot;https://img.ly/_astro/prepopulated_2uzsKx.webp&quot; srcset=&quot;/_astro/prepopulated_2uzsKx.webp 480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Whether starting with a video or starting with a blank canvas a user can add video clips to their creation using the embedded camera.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;difference between tiktok camera and creativeeditor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 640px) 640px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;640&quot; height=&quot;674&quot; src=&quot;https://img.ly/_astro/updated_editor_compare_1tFFER.webp&quot; srcset=&quot;/_astro/updated_editor_compare_1tFFER.webp 640w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The image above compares two different camera controllers: the left one from the TikTok app and the right showing the default settings of the MobileCamera, either standalone or within a Video Editor scene. Key controls in each are marked with blue numbers:&lt;/p&gt;
&lt;p&gt;1 - Flip camera button&lt;br&gt;
2 - Flash button&lt;br&gt;
3 - Recording time indicator&lt;br&gt;
4 - Add video and finish buttons&lt;br&gt;
5 - Cancel button&lt;/p&gt;
&lt;p&gt;While the TikTok camera integrates several editing functions during video capture, the Video Editor separates these tasks, focusing the camera purely on recording. Both systems allow users to start and stop video recording to compile a series of clips before moving to the editing phase.&lt;/p&gt;
&lt;h3 id=&quot;configuring-the-camera&quot;&gt;Configuring the Camera&lt;/h3&gt;
&lt;p&gt;The camera used inside of the Video Editor UI isn’t customizable. You have limited options to customize when you use the standalone camera though. You can set the colors of the buttons, helpful if you have a preferred color scheme, and you can set a maximum duration for the video recording allowed.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CameraConfiguration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  recordingColor&lt;/span&gt;&lt;span&gt;: .orange,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  highlightColor&lt;/span&gt;&lt;span&gt;: .blue,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  maxTotalDuration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  allowExceedingMaxDuration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Camera&lt;/span&gt;&lt;span&gt;(engine, &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;: config) { result &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; //handle the recorded video collection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above sets the record button to orange while recording and the clip view to orange. There is a maximum duration of 30 seconds for all videos in the collection and the button to save the clips highlighted in blue.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;customized camera&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 480px) 480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;480&quot; height=&quot;1039&quot; src=&quot;https://img.ly/_astro/customcamera_Z2tXDPj.webp&quot; srcset=&quot;/_astro/customcamera_Z2tXDPj.webp 480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;If your app requires further customization, you’ll probably want to make your own camera view and work with the IMG.LY Creative Engine directly.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://img.ly/docs/cesdk/ios/get-started/overview-e18f40/&quot;&gt;documentation website&lt;/a&gt; is a great resource for understanding how much you can add to and customize the IMGLYUI resources and when you’ll need to drop down to the level of the engine.&lt;/p&gt;
&lt;h3 id=&quot;presenting-the-controller&quot;&gt;Presenting the Controller&lt;/h3&gt;
&lt;p&gt;As demonstrated above, both the Video Editor and the Camera are View structures that can be presented directly or through overlays such as overlay or fullScreenCover. However, they are typically enclosed in a NavigationView to utilize the navigation bar for displaying export and other buttons. Therefore, when triggering the Video Editor from a button, it’s crucial to embed it within a NavigationView.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Edit the Video&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  isPresented &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt;fullScreenCover&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;isPresented&lt;/span&gt;&lt;span&gt;: $isPresented) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   NavigationView&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     VideoEditor&lt;/span&gt;&lt;span&gt;(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      //any setup modifiers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;building-your-creation&quot;&gt;Building Your Creation&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;compare editors&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 640px) 640px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;640&quot; height=&quot;674&quot; src=&quot;https://img.ly/_astro/updated_editor_compare-1_ZncnjS.webp&quot; srcset=&quot;/_astro/updated_editor_compare-1_ZncnjS.webp 640w&quot;&gt;&lt;/p&gt;
&lt;p&gt;After capturing a video, the app displays an editor to complete the creation. The editing tools are in the red circles. TikTok provides about ten different tools on the editing screen. Additionally, TikTok provided some editing tools on the capture screen. The CreativeEditor SDK apps provide a scrolling set of asset libraries along the bottom for items to add to the scene. But when any of the elements of the scene, like your video or an audio clip, are highlighted, the bottom row becomes tools to manipulate that element. The basic Asset Library types are uploads, videos, audio, images, text, shapes, and stickers.&lt;/p&gt;
&lt;p&gt;How to configure and customize each of these tools is beyond the scope of this tutorial. But, a lot of the customization of the assets, fonts, blurs and even filters can be done without code. By design, the CreativeEditor SDK downloads assets from a central server. This is a great way for you to be able to push updated filters, stickers, and other assets to your users without going through the app update process. In the next section, we’ll walk through that and provide some links for further research.&lt;/p&gt;
&lt;h3 id=&quot;configuring-the-editors-assets&quot;&gt;Configuring the Editors Assets&lt;/h3&gt;
&lt;p&gt;The IMG.LY Engine looks for assets for stickers, vector shapes, LUT filters, color filters, color palettes, blurs, and fonts using a URL by default. The design of the system is that you would have your own server supporting your app. However, assets in the app bundle are also accessible by URL, so as long as the assets are in the folder format that the engine expects, it doesn’t matter whether they are local or remote.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://img.ly/docs/cesdk/ios/serve-assets-b0827c/&quot;&gt;documentation for this process&lt;/a&gt; you can find a URL to the default asset bundle. Once you download that bundle you can add it to your app. The bundle is structured much like a regular iOS Assets.catalog with some JSON containing metadata about the asset and a file that provides the actual asset. For some of the assets like filters and vectors, no file is needed as everything can be defined in the JSON.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;asset files and metadata listing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;656&quot; src=&quot;https://img.ly/_astro/assets_ZLXreD.webp&quot; srcset=&quot;/_astro/assets_21dhby.webp 640w, /_astro/assets_ZLXreD.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the image above, you can see the JSON to describe the ape sticker and the &lt;code&gt;emoji_ape&lt;/code&gt; image file after the IMGLYAssets bundle has been added to our app. You can edit these assets and add your own.&lt;/p&gt;
&lt;p&gt;Once you’re done editing the asset bundle, you can use it instead of the default bundle during the &lt;code&gt;onCreate&lt;/code&gt; modifier like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;VideoEditor&lt;/span&gt;&lt;span&gt;(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .imgly.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;({ engine &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;: VideoEditor.defaultScene)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              // Add asset sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     let&lt;/span&gt;&lt;span&gt; bundleURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;IMGLYAssets&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bundle&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.&lt;/span&gt;&lt;span&gt;addDefaultAssetSources&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;baseURL&lt;/span&gt;&lt;span&gt;: bundleURL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.&lt;/span&gt;&lt;span&gt;addDemoAssetSources&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sceneMode&lt;/span&gt;&lt;span&gt;: engine.scene.&lt;/span&gt;&lt;span&gt;getMode&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       withUploadAssetSources&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.asset.&lt;/span&gt;&lt;span&gt;addSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TextAssetSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;: engine))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we create a blank &lt;code&gt;scene&lt;/code&gt; using &lt;code&gt;VideoEditor.defaultScene&lt;/code&gt; static method. Then set a URL to point to the bundle in the app instead of a remote bundle and make that the source of &lt;code&gt;addDefaultAssetSources&lt;/code&gt;. Now our app will use the assets from the bundle.&lt;/p&gt;
&lt;h3 id=&quot;exporting-the-finished-video&quot;&gt;Exporting the Finished Video&lt;/h3&gt;
&lt;p&gt;Once the user has finished with their creation, they tap the share button and the Video Editor composes all of the video, audio, filters, and static items into a single &lt;code&gt;.mp4&lt;/code&gt; file. Then it displays a share sheet. If you’d rather do something different, like save the creation locally so you can play it back, you can override this default behavior.&lt;/p&gt;
&lt;p&gt;In your app, after the &lt;code&gt;.onCreate&lt;/code&gt; modifier, you can define a &lt;code&gt;.onExport&lt;/code&gt; modifier. When the user taps on the share button you can export the video and then save it to the documents directory to playback later.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://img.ly/docs/cesdk/ios/user-interface/events-514b70/&quot;&gt;documentation for modifiers&lt;/a&gt; you can find the source code for the Video Editor’s default behavior. Instead of duplicating it in detail here, we can just summarize and provide a little snippet for our change.&lt;/p&gt;
&lt;p&gt;First, the &lt;code&gt;.onCreate&lt;/code&gt; handler takes an &lt;code&gt;engine&lt;/code&gt; and a &lt;code&gt;eventHandler&lt;/code&gt; parameter. The &lt;code&gt;engine&lt;/code&gt; is the underlying IMG.LY Engine object we’ve been using. The &lt;code&gt;eventHandler&lt;/code&gt; lets us send information back to the Video Editor UI about the progress or any errors.&lt;/p&gt;
&lt;p&gt;In the documentation, there is a helper function to export the video. After it runs it returns the &lt;code&gt;data&lt;/code&gt; of the exported video and a &lt;code&gt;mimeType&lt;/code&gt;, which will be &lt;code&gt;.mp4&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; data: Date, mimeType: MIMEType&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; mode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; mainEngine.scene.&lt;/span&gt;&lt;span&gt;getMode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; mode &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; .video &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; CallbackError.unknownSceneMode; &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;(data, mimeType) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; exportVideo&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code in the documentation handles static files from the &lt;code&gt;DesignEditor&lt;/code&gt; as well as videos from the &lt;code&gt;VideoEditor&lt;/code&gt;. The above code makes it so that only video can be exported, since our app only works with video.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;exportVideo&lt;/code&gt; function, the code first checks to make sure it has a properly formed &lt;code&gt;scene&lt;/code&gt; object to work with. Then it starts the export using &lt;code&gt;mainEngine.block.exportVideo(_, mimeType:)&lt;/code&gt;. This begins an async throwing stream during the export. The VideoExport objects in the stream are either &lt;code&gt;.progress&lt;/code&gt; or &lt;code&gt;.finished&lt;/code&gt;. When a &lt;code&gt;.progress&lt;/code&gt; object arrives, the &lt;code&gt;exportVideo&lt;/code&gt; function sends an update to the &lt;code&gt;eventHandler&lt;/code&gt; to display in the UI. When the &lt;code&gt;.finished&lt;/code&gt; object arrives, it has an associated object that is the data for the video.&lt;/p&gt;
&lt;p&gt;Now instead of opening the share sheet, we can write the video to the documents directory for the app.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;do&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; documentDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask, &lt;/span&gt;&lt;span&gt;appropriateFor&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Documents directory not found&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; filePath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; documentDirectory.&lt;/span&gt;&lt;span&gt;appending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;component&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;().uuidString)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: filePath)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; NSError) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;could not write finished file: &lt;/span&gt;&lt;span&gt;\(error.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;eventHandler.&lt;/span&gt;&lt;span&gt;send&lt;/span&gt;&lt;span&gt;(.&lt;/span&gt;&lt;span&gt;exportCompleted&lt;/span&gt;&lt;span&gt; {})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above takes the &lt;code&gt;data&lt;/code&gt; from the &lt;code&gt;exportVideo&lt;/code&gt; function and writes it to the app’s documents directory, giving it a UUID string value as a filename. Then it updates the UI to let it know the export is done but sends an empty action block.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-playback-controller&quot;&gt;Setting Up a Playback Controller&lt;/h2&gt;
&lt;p&gt;For this example app, the playback controller will play any clips in the app’s &lt;code&gt;Documents&lt;/code&gt; directory. Each clip plays on a loop. The user can swipe up to get the next clip and tap to pause or restart the clip. If there are no clips, the user will be encouraged to make a new one.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screen shot of player&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 963px) 963px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;963&quot; height=&quot;982&quot; src=&quot;https://img.ly/_astro/viewer_Z21kKPe.webp&quot; srcset=&quot;/_astro/viewer_WarKA.webp 640w, /_astro/viewer_2gQt5P.webp 750w, /_astro/viewer_2mRkuK.webp 828w, /_astro/viewer_Z21kKPe.webp 963w&quot;&gt;&lt;/p&gt;
&lt;p&gt;When the video player view appears, the first task is to see if there are any videos to load. Any video files in the Documents directory are assumed to be ready to play.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; loadVideos&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a handle to the documents directory for this app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; documentDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask, &lt;/span&gt;&lt;span&gt;appropriateFor&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Documents directory not found&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; files &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;contentsOfDirectory&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: documentDirectory, &lt;/span&gt;&lt;span&gt;includingPropertiesForKeys&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videos &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above reads all of the filenames from the documents directory into a &lt;code&gt;files&lt;/code&gt; array and then assigns that array to a &lt;code&gt;videos&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;Each time the app is launched or the &lt;code&gt;VideoEditor&lt;/code&gt; view is dismissed, the video player view will get the &lt;code&gt;onAppear&lt;/code&gt; message. This is a good place to check for videos using the &lt;code&gt;loadVideos&lt;/code&gt; function. After the videos are loaded, the app can start to play the first one.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; setupVideoPlayer&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; currentVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;No videos to play&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.shouldHideEmptyDirectoryText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       let&lt;/span&gt;&lt;span&gt; endMonitor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NotificationCenter.default.&lt;/span&gt;&lt;span&gt;publisher&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: NSNotification.Name.AVPlayerItemDidPlayToEndTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: currentVideo)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.currentVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; currentVideo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we first check to see if there are any video files. If there are, then hide the message about videos and create a new &lt;code&gt;AVPlayerItem&lt;/code&gt; using the &lt;code&gt;URL&lt;/code&gt; of the first video. The &lt;code&gt;endMonitor&lt;/code&gt;, &lt;code&gt;shouldHideEmptyDirectoryText&lt;/code&gt;, and &lt;code&gt;player&lt;/code&gt; are both being watched by the &lt;code&gt;View&lt;/code&gt;. The AVPlayer gets rendered in a regular SwiftUI View.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;VideoPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;: player)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;640&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;360&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;onAppear&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;onReceive&lt;/span&gt;&lt;span&gt;(endMonitor) { &lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: .zero)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the video reaches the end, it will &lt;code&gt;seek(to: .zero)&lt;/code&gt; which rewinds the video and loops it.&lt;/p&gt;
&lt;p&gt;In the TikTok app, you can advance to the next video by swiping up. Additionally, you can start and stop any video by tapping on the screen. We can add both of those features using gesture recognizers.&lt;/p&gt;
&lt;p&gt;The code for play and pause is attached to a Tap Gesture modifier we can append to the player.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onTapGesture&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; player.rate &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.rate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.rate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can just adjust the playback rate. A value of &lt;code&gt;1.0&lt;/code&gt; is a normal speed and &lt;code&gt;0.0&lt;/code&gt; is paused. The player also has a &lt;code&gt;.pause&lt;/code&gt; and &lt;code&gt;.play&lt;/code&gt; methods, but sometimes these seem buggy.&lt;/p&gt;
&lt;p&gt;Swipe is a little more difficult since SwiftUI only has swiping on List items. So we can use a &lt;code&gt;drag&lt;/code&gt; gesture and then evaluate the translations when it’s complete.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;gesture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    DragGesture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;minimumDistance&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;coordinateSpace&lt;/span&gt;&lt;span&gt;: .local)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;onEnded&lt;/span&gt;&lt;span&gt; { value &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            // Check for a vertical, upward swipe with minimal horizontal deviation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if&lt;/span&gt;&lt;span&gt; abs&lt;/span&gt;&lt;span&gt;(value.translation.width) &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             value.translation.height &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                // It was an up swipe!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		viewModel.&lt;/span&gt;&lt;span&gt;advanceVideo&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then advancing the video is a simple matter of refreshing the &lt;code&gt;player&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; advanceVideo&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; currentVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.currentVideo &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;No current video.&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; currentIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;firstIndex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;: currentVideo)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  var&lt;/span&gt;&lt;span&gt; nextVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;after&lt;/span&gt;&lt;span&gt;: currentIndex&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; nextVideo &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    nextVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.currentvideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos[nextVideo]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.currentVideo&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code will get the next file URL from the array of videos. When it reaches the end, it will loop back and get the file at index 0. Then it creates a new player. Because the view is observing that &lt;code&gt;player&lt;/code&gt; variable, it will update with the new video.&lt;/p&gt;
&lt;p&gt;With this view, the user can create new videos by tapping the creation button and swipe to view all of their created videos.&lt;/p&gt;
&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to Go From Here&lt;/h2&gt;
&lt;p&gt;This tutorial has focused on how the CreativeEditor SDK can help you quickly make a video creation app like TikTok. Good next steps would be to further customize the editing tools and build out the network for sharing and tagging videos. Something that is important to consider is the data structure for each video. The iOS system is optimized to read only as much of a video file off of disk as is needed at any moment. Your app should use those optimizations to run faster. So, don’t load the whole video into memory as a &lt;code&gt;Data&lt;/code&gt; object. Your data structures should keep the large video files somehow separate from the much smaller metadata (likes, comments, etc.). Consider storing the &lt;code&gt;URL&lt;/code&gt; or filename of the video in the same object as the likes and comments. This will also allow you to cache video files after they have been downloaded so that you don’t need to redownload them when other data such as comments or number of likes changes.&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you’ve gotten a better understanding for how a tool like the &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt; can bring your ideas to market faster. Feel free to reach out to us with any questions, comments, or suggestions.&lt;/p&gt;
&lt;p&gt;Looking for more video capabilities? Check out our solutions for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;or follow us on&lt;/strong&gt; &lt;a href=&quot;https://www.linkedin.com/company/img.ly&quot;&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a href=&quot;https://x.com/imgly&quot;&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2023/03/Build_TikTok_app_iOS_VE-SDK-1.jpg" medium="image"/><category>How-To</category><category>iOS</category><category>Video Editing</category><category>Social Media</category><category>Tech</category><category>Learning</category><category>Tutorial</category></item><item><title>How to Add Stickers and Overlays to a Video in Flutter</title><link>https://img.ly/blog/how-to-add-stickers-and-overlays-to-a-video-in-flutter/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-stickers-and-overlays-to-a-video-in-flutter/</guid><description>Learn how to apply stickers and overlays to a video in Flutter and make your app&apos;s video content more personalizable and user-oriented. </description><pubDate>Fri, 24 Mar 2023 09:32:33 GMT</pubDate><content:encoded>&lt;p&gt;When working with interactive content, it may happen that you will need to add remote or local assets on top of your video file. The assets varying from images and stickers to videos and fonts are usually grouped and deployed within your application and are available at runtime. This tutorial will teach you how to apply stickers and overlays to a video in Flutter and make your video content more personalizable and user-oriented. We start by considering the prerequisites for successful video integration, then talk about how Flutter manages overlays in order to personalize our video file.&lt;/p&gt;
&lt;h2 id=&quot;how-does-flutter-handle-video-assets&quot;&gt;How does Flutter handle video assets?&lt;/h2&gt;
&lt;p&gt;Unlike images, Flutter displays a video file by employing a special &lt;strong&gt;&lt;code&gt;video_player&lt;/code&gt;&lt;/strong&gt; plugin that provides playback, stores a file, and manages speed and sound control. For iOS-based applications, the video is integrated via &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avplayer&quot;&gt;AVPlayer&lt;/a&gt;, whereas the Android system employs &lt;a href=&quot;https://developer.android.com/media/media3/exoplayer&quot;&gt;ExoPlayer&lt;/a&gt;. Crucially, to create a simple video player, one should follow the steps below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, to ensure that pubspec.yaml file contains the &lt;code&gt;video_player&lt;/code&gt; dependency;&lt;/li&gt;
&lt;li&gt;Then permit access to videos for the application;&lt;/li&gt;
&lt;li&gt;Make a &lt;code&gt;VideoPlayerController&lt;/code&gt; and set it up;&lt;/li&gt;
&lt;li&gt;Finally, don’t forget to display and play the video player;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, let’s closely look at the setup process.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites:&lt;/h3&gt;
&lt;p&gt;We start by adding the following dependencies of &lt;code&gt;video_player&lt;/code&gt; plugin to pubspec.yaml file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  flutter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sdk&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flutter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  video_player&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;^2.4.7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, change configurations of both &lt;code&gt;android&lt;/code&gt; and &lt;code&gt;ios&lt;/code&gt; systems to guarantee that an application has the proper authorization for video streaming: e.g. for Android-oriented programs, proceed to the &lt;code&gt;&amp;#x3C;project root&gt;/android/app/src/main/AndroidManifest.xml&lt;/code&gt; directory – where it’s necessary to add the next line to the &lt;code&gt;AndroidManifest.xml&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;manifest&lt;/span&gt;&lt;span&gt; xmlns:android&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;http://schemas.android.com/apk/res/android&gt;&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;application&lt;/span&gt;&lt;span&gt; ...&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;application&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;uses-permission&lt;/span&gt;&lt;span&gt; android:name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;android.permission.INTERNET&quot;&lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;manifest&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For iOS-based apps the &lt;code&gt;Info.plist&lt;/code&gt; file is stored at &lt;code&gt;&amp;#x3C;project root&gt;/ios/Runner/Info.plist&lt;/code&gt;. Once again, don’t forget to add the following specification to the original code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&gt;NSAppTransportSecurity&amp;#x3C;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&gt;NSAllowsArbitraryLoads&amp;#x3C;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Successful video integration in Flutter is managed through the &lt;code&gt;VideoPlayer&lt;/code&gt; widget and a &lt;code&gt;VideoPlayerController&lt;/code&gt;, which sets the connection to the asset and &lt;code&gt;initialize&lt;/code&gt; the controller for playback.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/// Stateful widget to fetch and then display video content.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; VideoApp&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatefulWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; VideoApp&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  _VideoAppState&lt;/span&gt;&lt;span&gt; createState&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; _VideoAppState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _VideoAppState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoApp&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  late&lt;/span&gt;&lt;span&gt; VideoPlayerController&lt;/span&gt;&lt;span&gt; _controller;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; initState&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;initState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    _controller &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VideoPlayerController&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;assets/bee.mp4&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /// Specify the path to your video asset here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ..&lt;/span&gt;&lt;span&gt;initialize&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((_) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /// Ensure the first frame is shown after the video is initialized,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /// even before the play button has been pressed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        setState&lt;/span&gt;&lt;span&gt;(() {});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; dispose&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    _controller.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that video tends to take up as much space on the screen as possible by default, which can significantly deteriorate the video’s quality. Therefore, the Flutter team suggests employing the &lt;code&gt;AspectRatio&lt;/code&gt;widget to adjust the video proportions.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; AspectRatio&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    aspectRatio&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; _controller.value.aspectRatio)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;adding-overlay-stickers-and-text-to-a-video-in-flutter&quot;&gt;Adding Overlay Stickers and Text to a Video in Flutter&lt;/h2&gt;
&lt;p&gt;To see the complete code of our demo application, you can clone the &lt;a href=&quot;https://github.com/nataliakzm/Adding_Stickers_and_Overlays_to_video_in_Flutter&quot;&gt;GitHub repository&lt;/a&gt; or put the following command to your Terminal:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; git@github.com:nataliakzm/Adding_Stickers_and_Overlays_to_video_in_Flutter.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/add-overlay-flutter.mp4&quot; controls muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Similarly to how we handled &lt;a href=&quot;https://img.ly/blog/how-to-resize-images-in-flutter/&quot;&gt;resizing images in Flutter&lt;/a&gt;,  the same method is used when overlaying an object on a video.  The above example consists basically of three containers, one with a sticker, another with the textual watermark and the last with a local video asset.  However, the critical difference here is that a &lt;code&gt;Container&lt;/code&gt; filled with a sticker and a &lt;code&gt;Container&lt;/code&gt; filled with a textual watermark cannot be simply listed one by one in your code but rather must be both placed within a &lt;code&gt;Stack&lt;/code&gt; widget.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Stack&lt;/code&gt; container is designed as a “mother”-widget which retains multiple layers of widgets on the screen. &lt;code&gt;Stack&lt;/code&gt; structures these branches of children into a hierarchical system from bottom to top. Thus, the uppermost widget goes to the background, and the bottommost item is displayed in the foreground.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Stack&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;Widget&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    BottomWidget&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    MiddleWidget&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    TopWidget&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ]),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the size of the &lt;code&gt;Stack&lt;/code&gt; widget is aimed to be the largest size among layers, we will place our video asset on this layer. Then, to implement the overlays, it’s essential to position and/or align each child’s container.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Stack&lt;/span&gt;&lt;span&gt;(children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  VideoPlayer&lt;/span&gt;&lt;span&gt;(_controller),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  /// Don&apos;t forget to align the position of the Сontainer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Positioned&lt;/span&gt;&lt;span&gt;( bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;( width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;, height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 40&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /// In case you want to check the position/size of the Container uncomment the next line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        //color: Color(0xff0360da),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Align&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            alignment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Alignment&lt;/span&gt;&lt;span&gt;.center,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            /// Integrate Sticker overlay into a video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;assets/sticker.png&apos;&lt;/span&gt;&lt;span&gt;)))),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Positioned&lt;/span&gt;&lt;span&gt;( top&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;( width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;, height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 40&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Align&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            alignment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Alignment&lt;/span&gt;&lt;span&gt;.center,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            /// Integrate Text overlay into a video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;IMG.LY&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                style&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; TextStyle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Colors&lt;/span&gt;&lt;span&gt;.white,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    fontSize&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    fontWeight&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; FontWeight&lt;/span&gt;&lt;span&gt;.bold))))),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        ]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  /// If there is no video, a blank page will be return   : Container()),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summing-up&quot;&gt;Summing up&lt;/h2&gt;
&lt;p&gt;Thus, you can see from the code below that integrating multiple layers over interactive assets can be a tricky task which requires bearing in mind the position and order of each layer, as well as managing how the children are aligned to a video file and the screen size of the user. Conversely, the discussed method allows personalizing an application without significant modifications to the original file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:flutter/cupertino.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:flutter/material.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/// Import video_player package to integrate a video asset&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:video_player/video_player.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; runApp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; VideoApp&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/// Stateful widget to fetch and then display video content.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; VideoApp&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatefulWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; VideoApp&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  _VideoAppState&lt;/span&gt;&lt;span&gt; createState&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; _VideoAppState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _VideoAppState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoApp&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  late&lt;/span&gt;&lt;span&gt; VideoPlayerController&lt;/span&gt;&lt;span&gt; _controller;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; initState&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;initState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    _controller &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VideoPlayerController&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;assets/bee.mp4&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /// Specify the path to your video asset here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ..&lt;/span&gt;&lt;span&gt;initialize&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((_) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /// Ensure the first frame is shown after the video is initialized,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        /// even before the play button has been pressed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        setState&lt;/span&gt;&lt;span&gt;(() {});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; MaterialApp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        debugShowCheckedModeBanner&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        home&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Scaffold&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            appBar&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AppBar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;How to add Stickers and Overlays&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                backgroundColor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Color&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0xff0360da&lt;/span&gt;&lt;span&gt;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Center&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; _controller.value.isInitialized&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    /// First, we specify the AspectRatio of a video frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ?&lt;/span&gt;&lt;span&gt; AspectRatio&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        aspectRatio&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; _controller.value.aspectRatio,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        /// Then, we initialize a Stack widget&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Stack&lt;/span&gt;&lt;span&gt;(children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          VideoPlayer&lt;/span&gt;&lt;span&gt;(_controller),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          /// Don&apos;t forget to align the position of the Сontainer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          Positioned&lt;/span&gt;&lt;span&gt;( bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                              child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;( width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;, height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 40&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  /// Uncomment the following line to check the position/size of the Container&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  //color: Color(0xff0360da),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Align&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      alignment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Alignment&lt;/span&gt;&lt;span&gt;.center,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      /// Integrate Image overlay into a video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;assets/sticker.png&apos;&lt;/span&gt;&lt;span&gt;)))),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          Positioned&lt;/span&gt;&lt;span&gt;( top&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                              child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;( width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;, height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 40&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Align&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      alignment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Alignment&lt;/span&gt;&lt;span&gt;.center,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;																			/// Integrate some Text overlay into a video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                      child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;IMG.LY&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                          style&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; TextStyle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                              color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Colors&lt;/span&gt;&lt;span&gt;.white,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                              fontSize&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                              fontWeight&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; FontWeight&lt;/span&gt;&lt;span&gt;.bold))))),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        ]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    /// If there is no video, a blank page will be return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    :&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            floatingActionButton&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; FloatingActionButton&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;extended&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                backgroundColor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; Color&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0xff0360da&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                foregroundColor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Colors&lt;/span&gt;&lt;span&gt;.white,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                onPressed&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    _controller.value.isPlaying&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        ?&lt;/span&gt;&lt;span&gt; _controller.&lt;/span&gt;&lt;span&gt;pause&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        :&lt;/span&gt;&lt;span&gt; _controller.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                icon&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Icon&lt;/span&gt;&lt;span&gt;(_controller.value.isPlaying&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ?&lt;/span&gt;&lt;span&gt; Icons&lt;/span&gt;&lt;span&gt;.pause&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    :&lt;/span&gt;&lt;span&gt; Icons&lt;/span&gt;&lt;span&gt;.play_arrow),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Play for IMG.LY&apos;&lt;/span&gt;&lt;span&gt;))));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; dispose&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    _controller.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;adding-overlays-to-a-video-with-flutter-package-for-videoeditor-sdk&quot;&gt;Adding Overlays to a Video with &lt;a href=&quot;https://pub.dev/packages/video_editor_sdk/install&quot;&gt;Flutter package for VideoEditor SDK&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;However, if you would like to offer more advanced sticker and overlay functionality to your user and you might need a more complex UI structure. Since this can be an extremely time consuming undertaking you might want to opt for a ready-to-use solution such as &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt; which among a host of other essential video editing features allows users to easily add stickers and different overlays to their videos.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/vesdk-demo-overlay.mp4&quot; controls muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;To replicate the example above, go to the &lt;a href=&quot;https://img.ly/docs/vesdk/flutter/getting-started/&quot;&gt;official documentation&lt;/a&gt; to discover how to get started with VideoEditor SDK or follow our guide on how to integrate &lt;a href=&quot;https://img.ly/blog/a-modern-video-editor-sdk-for-your-flutter-app/&quot;&gt;video editor for Flutter&lt;/a&gt; into your app. Try to upload your video file and modify it with various stickers, text and other overlays.  Alternatively, you can download our free mobile demo app from the &lt;a href=&quot;https://apps.apple.com/us/app/img-ly-photo-video-editor/id589839231&quot;&gt;App Store&lt;/a&gt; or &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.photoeditorsdk.android.app&quot;&gt;Google Play&lt;/a&gt;, and test a comprehensive set video editing tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! To stay in the loop, subscribe to our &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;Newsletter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Natalia</dc:creator><media:content url="https://blog.img.ly/2022/11/sticker-in-flutter-video.png" medium="image"/><category>How-To</category><category>Flutter</category><category>Video Editing</category><category>Tech</category><category>Tutorial</category></item><item><title>How To Build a Video Editor With Wasm in React</title><link>https://img.ly/blog/how-to-build-a-video-editor-with-wasm-in-react/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-a-video-editor-with-wasm-in-react/</guid><description>Use the ffmpeg.wasm library to build a React video editor that performs video processing directly in the browser.</description><pubDate>Mon, 16 Jan 2023 16:31:57 GMT</pubDate><content:encoded>&lt;p&gt;You may think that video processing can only be performed on a server, but this is not true. Thanks to WebAssembly (Wasm), you can run high-performance code written in C or C++ in your browser. This opens up a lot of possibilities and gives you the ability to build a client-side video editor. Let’s now learn how to create a Wasm-based video editor in React.&lt;/p&gt;
&lt;p&gt;Follow this tutorial and learn how to build the application below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Upload and crop your video, and covert it as a GIF&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 671px) 671px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;671&quot; height=&quot;943&quot; src=&quot;https://img.ly/_astro/ffmpeg-video-editor-wasm_1i2zGn.webp&quot; srcset=&quot;/_astro/ffmpeg-video-editor-wasm_1FMujk.webp 640w, /_astro/ffmpeg-video-editor-wasm_1i2zGn.webp 671w&quot;&gt;&lt;/p&gt;
&lt;p&gt;This React video editor allows users to upload a video, select a portion of it, convert it to GIF, and download the resulting image file – all of this in your browser.&lt;/p&gt;
&lt;h2 id=&quot;what-is-webassembly&quot;&gt;What is WebAssembly?&lt;/h2&gt;
&lt;p&gt;WebAssembly (also called Wasm) is a new type of code that modern web browsers can run and understand. Specifically, Wasm provides new functionality to web development and brings significant performance benefits. This applies to both frontend and backend, since Wasm can be used by both clients and servers.&lt;/p&gt;
&lt;p&gt;Wasm code should not be written by hand. This is because Wasm is a binary instruction format. So, Wasm is designed to be a compilation target for source languages such as C, C++, Rust, and others. Learn more about Wasm on the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/WebAssembly&quot;&gt;WebAssembly MDN Web Docs page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Wasm provides a way to run code written in several languages on the Web at near-native speed. This means that WebAssembly forms can be easily imported and executed by a Web client or server application. In other words, you can use WebAssembly functions via JavaScript and achieve results that were not previously possible on a browser, especially in terms of performance.&lt;/p&gt;
&lt;h2 id=&quot;what-is-ffmpegwasm&quot;&gt;What is &lt;code&gt;ffmpeg.wasm&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;As stated on the &lt;a href=&quot;https://github.com/ffmpegwasm/ffmpeg.wasm&quot;&gt;GitHub page of the project&lt;/a&gt;, &lt;code&gt;ffmpeg.wasm&lt;/code&gt; is a WebAssembly and JavaScript port of FFmpeg. If you are not familiar with it, FFmpeg is an open-source software suite that includes several libraries and programs for managing audio, video, streams, and other media files. All of these tools can be invoked and executed via the &lt;code&gt;ffmpeg&lt;/code&gt; command-line instruction.&lt;/p&gt;
&lt;p&gt;Therefore, &lt;code&gt;ffmpeg.wasm&lt;/code&gt; enables video and audio processing in JavaScript-based web applications. In detail, it enables video and audio recording, format transcoding, video and audio editing, and video scaling. Even though it is a WebAssembly-based library, you can use it in your JavaScript code just like any other &lt;code&gt;npm&lt;/code&gt; library. This is the power of Wasm.&lt;/p&gt;
&lt;p&gt;Since it transpiles to Wasm, you can take advantage of &lt;code&gt;ffmpeg.wasm&lt;/code&gt; directly in your browser without performance concerns. This means that &lt;code&gt;ffmpeg.wasm&lt;/code&gt; opens the door to client-side audio and video processing. Let’s now learn how to use &lt;code&gt;ffmpeg.wasm&lt;/code&gt; to build a client-side video editor in React!&lt;/p&gt;
&lt;h2 id=&quot;building-a-video-editor-in-react-with-wasm&quot;&gt;Building a Video Editor in React with WASM&lt;/h2&gt;
&lt;p&gt;In this step-by-step tutorial, you will learn how to build a Wasm-based video editor in React with &lt;code&gt;ffmpeg.wasm&lt;/code&gt;. This React video editor application will allow you to upload a video, trim it, and convert it to GIF in the browser, without using any backend functionality or external API.&lt;/p&gt;
&lt;p&gt;Clone the &lt;a href=&quot;https://github.com/imgly/video-editor-wasm-react&quot;&gt;GitHub repository that supports the tutorial&lt;/a&gt; and try the video editor application by launching the following commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/video-editor-wasm-react&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd video-editor-wasm-react&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s waste no more time and see how to build a video editor application in React.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;This is the list of libraries the video editor application you are about to build depends on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/download/package-manager&quot;&gt;Node.js and npm 8+ and higher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebook/react/blob/main/CHANGELOG.md#1820-june-14-2022&quot;&gt;React 18.2+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[antd](https://www.npmjs.com/package/antd)&lt;/code&gt; &gt;= 4.3&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[video-react](https://www.npmjs.com/package/video-react)&lt;/code&gt; &gt;= 0.15&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;antd&lt;/code&gt; is one of the most popular UI libraries for React. In this tutorial, its &lt;code&gt;[Slider](https://ant.design/components/slider/)&lt;/code&gt; component will be used to implement the video cutting feature. So, any other UI library including a slider component with a &lt;code&gt;range&lt;/code&gt; option will do.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;video-react&lt;/code&gt; is one of the most advanced and reliable HTML5 video players for React. You can replace it with any other React video player library.&lt;/p&gt;
&lt;h3 id=&quot;initializing-a-react-project&quot;&gt;Initializing a React Project&lt;/h3&gt;
&lt;p&gt;Let’s initialize a new &lt;a href=&quot;https://create-react-app.dev/docs/getting-started/&quot;&gt;Create React App&lt;/a&gt; React project called &lt;code&gt;video-editor-wasm-react&lt;/code&gt; with the command below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npx create-react-app video-editor-wasm-react&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your &lt;code&gt;video-editor-wasm-react&lt;/code&gt; directory should now contain the following files:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;video-editor-wasm-react&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── node_modules&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── public&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── favicon.ico&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── logo192.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── logo512.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── manifest.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── robots.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.test.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── index.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── logo.svg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── reportWebVitals.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── setupTests.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter the &lt;code&gt;video-editor-wasm-react&lt;/code&gt; folder in your terminal and launch a React local server with the commands below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd video-editor-wasm-react&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, visit the &lt;code&gt;[http://localhost:3000/](http://localhost:3000/)&lt;/code&gt; page in your browser, and you should be seeing the default Create React App screen.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The default Create React App screen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 983px) 983px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;983&quot; height=&quot;728&quot; src=&quot;https://img.ly/_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_ZvCahg.webp&quot; srcset=&quot;/_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_1hpMyh.webp 640w, /_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_Zy6hVM.webp 750w, /_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_Z184VNu.webp 828w, /_astro/s_D2E103F8349A30F6AA8E27CD2BA4B6EDB946A8DEF5B8272009321B4F51D679F9_1624366125534_image_ZvCahg.webp 983w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Before installing &lt;code&gt;ffmpeg.wasm&lt;/code&gt;, you need to get your server ready. As stated &lt;a href=&quot;https://github.com/ffmpegwasm/ffmpeg.wasm#installation&quot;&gt;in the official documentation&lt;/a&gt;, &lt;code&gt;fmmpeg.wasm&lt;/code&gt; depends on &lt;code&gt;[SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)&lt;/code&gt;. This means that only browsers that support &lt;code&gt;SharedArrayBuffer&lt;/code&gt; can run &lt;code&gt;ffmpeg.wasm&lt;/code&gt;. You can find a complete list of all browsers &lt;a href=&quot;https://caniuse.com/sharedarraybuffer&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the most popular browsers, &lt;code&gt;SharedArrayBuffer&lt;/code&gt; is only available to &lt;a href=&quot;https://developer.chrome.com/blog/enabling-shared-array-buffer/#cross-origin-isolation&quot;&gt;cross-origin isolated&lt;/a&gt; webpages. To enable this, you need to set the following headers in your server hosting the React application:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Cross-Origin-Embedder-Policy: require-corp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Cross-Origin-Opener-Policy: same-origin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a Create-React-App application, you achieve this by creating a &lt;code&gt;src/setupProxy.js&lt;/code&gt; file and making sure it contains the following code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;module.exports = function (app) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    app.use(function (req, res, next) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        res.setHeader(&quot;Cross-Origin-Opener-Policy&quot;, &quot;same-origin&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        res.setHeader(&quot;Cross-Origin-Embedder-Policy&quot;, &quot;require-corp&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        next()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your local development server is now ready to use &lt;code&gt;ffmpeg.wasm&lt;/code&gt;!&lt;/p&gt;
&lt;h3 id=&quot;installing-the-projects-dependencies&quot;&gt;Installing the Project’s Dependencies&lt;/h3&gt;
&lt;p&gt;It is now time to add the aforementioned required dependencies to your project.&lt;/p&gt;
&lt;p&gt;First, install &lt;code&gt;ffmpeg.wasm&lt;/code&gt; with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install @ffmpeg/ffmpeg @ffmpeg/core&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s install &lt;a href=&quot;https://ant.design/&quot;&gt;Antd Design&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install antd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, it is time to install &lt;a href=&quot;https://video-react.js.org/&quot;&gt;Video-React&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install video-react redux&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;video-react&lt;/code&gt; also requires &lt;code&gt;[redux](https://www.npmjs.com/package/redux)&lt;/code&gt; to work.&lt;/p&gt;
&lt;p&gt;You have now everything you need to start developing your React video editor!&lt;/p&gt;
&lt;h3 id=&quot;uploading-a-video-to-the-video-editor&quot;&gt;Uploading a Video to the Video Editor&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;VideoUpload&lt;/code&gt; component as below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// src/components/VideoUpload.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Button, Upload } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;antd&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; VideoUpload&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;disabled&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {}, &lt;/span&gt;&lt;span&gt;onRemove&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {} }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;Upload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{disabled}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        beforeUpload&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          return&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        accept&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;video/*&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if&lt;/span&gt;&lt;span&gt; (info.fileList &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; info.fileList.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onChange&lt;/span&gt;&lt;span&gt;(info.fileList[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].originFileObj);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        showUploadList&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt;&gt;Upload Video&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;Upload&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        danger&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;disabled}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          onRemove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Remove&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; VideoUpload;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, this component uses the &lt;a href=&quot;https://ant.design/components/upload/&quot;&gt;Antd Design Upload&lt;/a&gt; component to allow users to upload a video file. Then, a “Remove” button gives users the ability to remove the uploaded video. Note that when the &lt;code&gt;Upload&lt;/code&gt; component is enabled, the “Remove” button is disabled and vice versa. This way, users can deal with only one video at a time.&lt;/p&gt;
&lt;h3 id=&quot;playing-a-video-in-react&quot;&gt;Playing a Video in React&lt;/h3&gt;
&lt;p&gt;You now need a &lt;code&gt;VideoPlayer&lt;/code&gt; component to play the uploaded video. You can build it as below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// src/components/VideoPlayer.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  BigPlayButton,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ControlBar,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  LoadingSpinner,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Player,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  PlayToggle,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;video-react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;video-react/dist/video-react.css&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useEffect, useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  src&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onPlayerChange&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onChange&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  startTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setPlayer&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;playerState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setPlayerState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (playerState) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      onChange&lt;/span&gt;&lt;span&gt;(playerState);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [playerState]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onPlayerChange&lt;/span&gt;&lt;span&gt;(player);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (player) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      player.&lt;/span&gt;&lt;span&gt;subscribeToStateChange&lt;/span&gt;&lt;span&gt;(setPlayerState);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [player]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;video-player&apos;&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;Player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          setPlayer&lt;/span&gt;&lt;span&gt;(player);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        startTime&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{startTime}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{src} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;BigPlayButton&lt;/span&gt;&lt;span&gt; position&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;center&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;LoadingSpinner&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;ControlBar&lt;/span&gt;&lt;span&gt; autoHide&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;disableDefaultControls&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;PlayToggle&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span&gt;ControlBar&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;Player&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a simple &lt;code&gt;video-react&lt;/code&gt;-based video player component, which only allows playing or stop the video file received with the &lt;code&gt;src&lt;/code&gt; prop.&lt;/p&gt;
&lt;h3 id=&quot;cutting-a-video-in-react&quot;&gt;Cutting a Video in React&lt;/h3&gt;
&lt;p&gt;Your goal is to allow users to select a portion of the video and watch it before converting it into GIF. Implement this with the logic below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Slider } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;antd&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { sliderValueToVideoTime } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;../utils/utils&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; VideoEditor&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;sliderValues&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setSliderValues&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // logic to handle videoFile, videoPlayer, and videoPlayerState...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; min&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValues[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // when the slider values are updated, updating the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // video time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (min &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; videoPlayerState &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; videoPlayer) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          videoPlayer.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, min))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [sliderValues])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (videoPlayer &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; videoPlayerState) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          // allowing users to watch only the portion of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          // the video selected by the slider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sliderValues&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          const&lt;/span&gt;&lt;span&gt; minTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, min)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          const&lt;/span&gt;&lt;span&gt; maxTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, max)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if&lt;/span&gt;&lt;span&gt; (videoPlayerState.currentTime &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; minTime) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              videoPlayer.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(minTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if&lt;/span&gt;&lt;span&gt; (videoPlayerState.currentTime &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; maxTime) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              // looping logic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              videoPlayer.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(minTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [videoPlayerState])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          // other video editor components...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;Slider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;videoPlayerState}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{sliderValues}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            range&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              setSliderValues&lt;/span&gt;&lt;span&gt;(values)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            tooltip&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              formatter: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component equips users with the ability to select a portion of the uploaded video with the Antd Design &lt;code&gt;Slider&lt;/code&gt; component and watch it in loop, before converting it into a GIF file.&lt;/p&gt;
&lt;p&gt;Specifically, the logic here uses the &lt;code&gt;sliderValueToVideoTime()&lt;/code&gt; function below to transform the slider values into time coordinates:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// src/utils/utils.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export function sliderValueToVideoTime(duration, sliderValue) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return Math.round(duration * sliderValue / 100)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the actual video processing slider will be performed by the next component through &lt;code&gt;ffmpeg&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;converting-a-video-to-gif-in-react&quot;&gt;Converting a Video to GIF in React&lt;/h3&gt;
&lt;p&gt;To convert a video to GIF, create a &lt;code&gt;VideoConversionButton&lt;/code&gt; component as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// src/components/VideoConversionButton.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Button } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;antd&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { fetchFile } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@ffmpeg/ffmpeg&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { sliderValueToVideoTime } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../utils/utils&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; VideoConversionButton&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoPlayerState&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  sliderValues&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoFile&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ffmpeg&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onConversionStart&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onConversionEnd&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onGifCreated&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; convertToGif&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // starting the conversion process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onConversionStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; inputFileName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;gif.mp4&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; outputFileName&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;output.gif&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // writing the video file to memory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ffmpeg.&lt;/span&gt;&lt;span&gt;FS&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;writeFile&apos;&lt;/span&gt;&lt;span&gt;, inputFileName, &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; fetchFile&lt;/span&gt;&lt;span&gt;(videoFile));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sliderValues;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; minTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, min);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; maxTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, max);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // cutting the video and converting it to GIF with an FFMpeg command&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; ffmpeg.&lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;-i&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      inputFileName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;-ss&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      `${&lt;/span&gt;&lt;span&gt;minTime&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;-to&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      `${&lt;/span&gt;&lt;span&gt;maxTime&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;-f&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;gif&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      outputFileName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // reading the resulting file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ffmpeg.&lt;/span&gt;&lt;span&gt;FS&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;readFile&apos;&lt;/span&gt;&lt;span&gt;, outputFileName);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // converting the GIF file created by FFmpeg to a valid image URL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; gifUrl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      new&lt;/span&gt;&lt;span&gt; Blob&lt;/span&gt;&lt;span&gt;([data.buffer], { type: &lt;/span&gt;&lt;span&gt;&apos;image/gif&apos;&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onGifCreated&lt;/span&gt;&lt;span&gt;(gifUrl);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // ending the conversion process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onConversionEnd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; convertToGif&lt;/span&gt;&lt;span&gt;()}&gt;Convert to GIF&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Button&lt;/span&gt;&lt;span&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; VideoConversionButton;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component uses &lt;code&gt;ffmpeg.wasm&lt;/code&gt; to launch an &lt;code&gt;ffmpeg&lt;/code&gt; command. This command takes care of clipping the video to the time limits defined by &lt;code&gt;sliderValues&lt;/code&gt; and converting it to GIF. Note that all these operations are performed client-side in the browser.&lt;/p&gt;
&lt;p&gt;In detail, the &lt;code&gt;ffmpeg&lt;/code&gt; &lt;code&gt;-ss&lt;/code&gt; flag defines the time from which to start reading the video file, while &lt;code&gt;-to&lt;/code&gt; defines the end time. Then, the &lt;code&gt;gif&lt;/code&gt; option specifies that you want to produce a GIF file. Learn more about the flags and options offered by &lt;code&gt;ffmpeg&lt;/code&gt; &lt;a href=&quot;https://www.ffmpeg.org/ffmpeg.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The resulting GIF image is stored in the &lt;code&gt;output.gif&lt;/code&gt; memory file, loaded as a valid &lt;code&gt;&quot;image/gif&quot;&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;Blob&lt;/a&gt;, and finally converted to a valid image URL with the &lt;code&gt;[URL.createObjectURL()](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static)&lt;/code&gt; native function.&lt;/p&gt;
&lt;h3 id=&quot;putting-it-all-together&quot;&gt;Putting It All Together&lt;/h3&gt;
&lt;p&gt;Now, let’s see the full code of the &lt;code&gt;VideoEditor&lt;/code&gt; component:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// src/components/VideoEditor.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { createFFmpeg } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@ffmpeg/ffmpeg&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useEffect, useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Slider, Spin } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;antd&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { VideoPlayer } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./VideoPlayer&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { sliderValueToVideoTime } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../utils/utils&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; VideoUpload &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./VideoUpload&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; VideoConversionButton &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./VideoConversionButton&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ffmpeg&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createFFmpeg&lt;/span&gt;&lt;span&gt;({ log: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; VideoEditor&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;ffmpegLoaded&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFFmpegLoaded&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;videoFile&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setVideoFile&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;videoPlayerState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setVideoPlayerState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;videoPlayer&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setVideoPlayer&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;gifUrl&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setGifUrl&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;sliderValues&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setSliderValues&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;processing&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setProcessing&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // loading ffmpeg on startup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ffmpeg.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setFFmpegLoaded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, []);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; min&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValues[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // when the slider values are updated, updating the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // video time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (min &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; videoPlayerState &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; videoPlayer) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      videoPlayer.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, min));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [sliderValues]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (videoPlayer &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; videoPlayerState) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // allowing users to watch only the portion of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // the video selected by the slider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sliderValues;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; minTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, min);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; maxTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; sliderValueToVideoTime&lt;/span&gt;&lt;span&gt;(videoPlayerState.duration, max);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (videoPlayerState.currentTime &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; minTime) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        videoPlayer.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(minTime);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (videoPlayerState.currentTime &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; maxTime) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // looping logic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        videoPlayer.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(minTime);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [videoPlayerState]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // when the current videoFile is removed,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // restoring the default state&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;videoFile) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setVideoPlayerState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setSliderValues&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setVideoPlayerState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setGifUrl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [videoFile]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;Spin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        spinning&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{processing &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;ffmpegLoaded}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        tip&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;ffmpegLoaded &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &apos;Waiting for FFmpeg to load...&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;Processing...&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          {videoFile &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoPlayer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(videoFile)}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              onPlayerChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;videoPlayer&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                setVideoPlayer&lt;/span&gt;&lt;span&gt;(videoPlayer);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;videoPlayerState&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                setVideoPlayerState&lt;/span&gt;&lt;span&gt;(videoPlayerState);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&gt;Upload a video&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;upload-div&apos;&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoUpload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;!!&lt;/span&gt;&lt;span&gt;videoFile}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;videoFile&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              setVideoFile&lt;/span&gt;&lt;span&gt;(videoFile);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;slider-div&apos;&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&gt;Cut Video&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;Slider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            disabled&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;videoPlayerState}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{sliderValues}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            range&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              setSliderValues&lt;/span&gt;&lt;span&gt;(values);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            tooltip&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              formatter: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;conversion-div&apos;&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoConversionButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onConversionStart&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              setProcessing&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onConversionEnd&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              setProcessing&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            ffmpeg&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{ffmpeg}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            videoPlayerState&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{videoPlayerState}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            sliderValues&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{sliderValues}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            videoFile&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{videoFile}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            onGifCreated&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;girUrl&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              setGifUrl&lt;/span&gt;&lt;span&gt;(girUrl);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {gifUrl &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;gif-div&apos;&lt;/span&gt;&lt;span&gt;}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&gt;Resulting GIF&amp;#x3C;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{gifUrl}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;gif&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;GIF file generated in the client side&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{gifUrl}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              download&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;test.gif&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;ant-btn ant-btn-default&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              Download&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;Spin&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; VideoEditor;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since &lt;code&gt;ffmpeg&lt;/code&gt; takes time to load, you need to let users know that they need to wait a while before they can use the video editor component. Also, &lt;code&gt;ffmpeg&lt;/code&gt; operations take time to execute. This wait logic is implemented by using the Antd Design &lt;code&gt;[Spin](https://ant.design/components/spin/)&lt;/code&gt; component.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://paper-attachments.dropbox.com/s_C8CCC2508B96793E40906CD26AF4C92ACE1FB33CF33FE115E6ACE3BA49F22F52_1663925760548_chrome-capture-2022-8-23.gif&quot; alt=&quot;Preventing users from using the video editor component before ffmpeg is loaded&quot;&gt;&lt;/p&gt;
&lt;p&gt;Also, note that by passing the &lt;code&gt;log: true&lt;/code&gt; option to the &lt;code&gt;createFFmpeg()&lt;/code&gt; function, &lt;code&gt;ffmpeg&lt;/code&gt; will log useful debug info in the console. For example, after &lt;code&gt;ffmpeg&lt;/code&gt; gets loaded, you should be able to see the following lines in the browser console:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[info] use ffmpeg.wasm v0.11.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;createFFmpeg.js:43 [info] load ffmpeg-core&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;createFFmpeg.js:43 [info] loading ffmpeg-core&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;createFFmpeg.js:43 [info] ffmpeg-core loaded&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What the &lt;code&gt;VideoComponent&lt;/code&gt; does is use all the components presented earlier to allow users to upload a video, select a portion through a slider, convert it to GIF, and download the resulting GIF file.&lt;/p&gt;
&lt;p&gt;Et voilà! You just learned how to build a client-side video editor in React without using any backend functionality!&lt;/p&gt;
&lt;h2 id=&quot;commercial-alternative&quot;&gt;Commercial Alternative&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/docs/vesdk/flutter/getting-started/integration/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt; by &lt;a href=&quot;https://img.ly&quot;&gt;IMG.LY&lt;/a&gt; provides powerful video editing features, including cropping and trimming videos in your project. You will receive staples of video editing, including straightening videos, filters, brightness, color adjustments, and more.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Building a client-side video processing and editing application that uses only the browser possible. With WebAssembly, you can run high-performance code written in C or C++ in JavaScript. This allows you to perform complex operations at lightning speed directly in the browser, without having to delegate the work to a server.&lt;/p&gt;
&lt;p&gt;In this tutorial, you learned how to use &lt;code&gt;ffmpeg.wasm&lt;/code&gt; to build a video editor in React. &lt;code&gt;ffmpeg.wasm&lt;/code&gt; is the Wasm port of &lt;code&gt;ffmpeg&lt;/code&gt; and enables video and audio processing and editing directly in the browser. Using it to build a web application to upload a video, cut it, and export it to GIF requires only a few lines of code, and here we have seen how to implement such a video editing application.&lt;/p&gt;
&lt;p&gt;If your app goes beyond merely displaying video, and you want to allow your users to also edit video or create video based templates in the browser, explore our &lt;strong&gt;&lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt;&lt;/strong&gt; for the Web – a performant video editing solution based on WASM and WebCodecs. Try the editor for yourself in action with &lt;a href=&quot;https://img.ly/showcases/cesdk/video-ui/web&quot;&gt;our showcases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! Let us know what you think on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;! To stay in the loop, subscribe to our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2023/01/video-editor-ffmpeg-wasm-1.jpg" medium="image"/><category>How-To</category><category>Video Editor</category><category>Web Development</category><category>Web Application</category><category>Video Editing</category><category>Tech</category><category>Tutorial</category></item><item><title>FFmpeg - The Ultimate Guide</title><link>https://img.ly/blog/ultimate-guide-to-ffmpeg/</link><guid isPermaLink="true">https://img.ly/blog/ultimate-guide-to-ffmpeg/</guid><description>This guide covers the ins and outs of FFmpeg starting with fundamental concepts and moving to media transcoding and video and audio processing providing practical examples along the way.</description><pubDate>Mon, 21 Nov 2022 12:16:19 GMT</pubDate><content:encoded>&lt;p&gt;In this guide, we’ll go through the hot topics of FFmpeg. But before that, we’ll cover some base ground to help you understand basic media concepts and FFmpeg. Feel free to skip the parts that are already trivial for you!&lt;/p&gt;
&lt;h2 id=&quot;introduction-to-ffmpeg&quot;&gt;Introduction to FFmpeg&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ffmpeg.org/about.html&quot;&gt;FFmpeg.org&lt;/a&gt;’s definition is the following: “FFmpeg is the leading multimedia framework, able to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created. It supports the most obscure ancient formats up to the cutting edge. No matter if they were designed by some standards committee, the community or a corporation.”&lt;/p&gt;
&lt;p&gt;I think of FFmpeg as the go-to application for audio/video manipulation in an automated or scripted manner.&lt;/p&gt;
&lt;p&gt;When you need to implement a service that manipulates video, or just have 300 media files that need to be converted into a different format, FFmpeg is your - nerdy - friend.&lt;/p&gt;
&lt;p&gt;FFmpeg can do large chunks of the basic functionalities of a modern Non-linear (NLE) video editors, e.g., Davinci Resolve Studio or Premiere Pro. But, it does not have a graphical interface in that sense as those behemoths do, and unarguably it is way less friendly.&lt;/p&gt;
&lt;p&gt;In a general NLE, you might do things like these:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click to import a file&lt;/li&gt;
&lt;li&gt;Drop it into the timeline&lt;/li&gt;
&lt;li&gt;Trim and Cut&lt;/li&gt;
&lt;li&gt;Add an overlay image&lt;/li&gt;
&lt;li&gt;Crop that overlay&lt;/li&gt;
&lt;li&gt;Add vignette&lt;/li&gt;
&lt;li&gt;Add some color changing effects, e.g. change the hue&lt;/li&gt;
&lt;li&gt;Add an extra audio track to the mix&lt;/li&gt;
&lt;li&gt;Change the volume&lt;/li&gt;
&lt;li&gt;Add some effects, e.g.: echo&lt;/li&gt;
&lt;li&gt;Export into various formats&lt;/li&gt;
&lt;li&gt;Export into a deployable video format&lt;/li&gt;
&lt;li&gt;Export the master audio in wav&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Learn how to &lt;a href=&quot;https://img.ly/blog/how-to-crop-and-trim-videos-in-flutter/#get-started&quot;&gt;crop and trim videos in Flutter&lt;/a&gt;. Or, to achieve the exact same thing, you could also execute this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -ss&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -i&lt;/span&gt;&lt;span&gt; train.jpg&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -ss&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; voice_recording.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -filter_complex&lt;/span&gt;&lt;span&gt; &quot;[0:v]hue=h=80:s=1[main] ; [1:v]crop=w=382:h=304:x=289:y=227[train] ; [main][train]overlay=x=200:y=200,vignette=PI/4[video] ; [2:a]volume=1.5,aecho=0.8:0.9:100:0.3[speech] ; [0:a][speech]amix=duration=shortest,asplit[audio1][audio2]&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -map&lt;/span&gt;&lt;span&gt; &apos;[video]&apos;&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; &apos;[audio1]&apos;&lt;/span&gt;&lt;span&gt; -metadata&lt;/span&gt;&lt;span&gt; title=&quot;Editor&apos;s cut&quot;&lt;/span&gt;&lt;span&gt; bbb_edited.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    -map&lt;/span&gt;&lt;span&gt; &apos;[audio2]&apos;&lt;/span&gt;&lt;span&gt; bbb_edited_audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, it isn’t friendly at all, but it is very, very powerful once you become friends with FFmpeg.&lt;/p&gt;
&lt;p&gt;Check out this comparison of the original and the edited one:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1520px) 1520px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1520&quot; height=&quot;488&quot; src=&quot;https://img.ly/_astro/img-1-edit-before-after_Z1QXYCT.webp&quot; srcset=&quot;/_astro/img-1-edit-before-after_ZGfT24.webp 640w, /_astro/img-1-edit-before-after_27k274.webp 750w, /_astro/img-1-edit-before-after_oiRf3.webp 828w, /_astro/img-1-edit-before-after_XVrkH.webp 1080w, /_astro/img-1-edit-before-after_1CaUoV.webp 1280w, /_astro/img-1-edit-before-after_Z1QXYCT.webp 1520w&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you want to try this command out, get the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#example-material&quot;&gt;example&lt;/a&gt; files and see it for yourself!&lt;/p&gt;
&lt;h3 id=&quot;installing-ffmpeg&quot;&gt;Installing FFmpeg&lt;/h3&gt;
&lt;p&gt;FFmpeg is available for most common and even uncommon platforms and architectures. You can be on Linux, Mac OS X or Microsoft Windows, and you’ll be able to run or link to FFmpeg.&lt;/p&gt;
&lt;p&gt;Installing FFmpeg is easy on most platforms! There is no installer, usually just a compressed archive you need to get for your platform and architecture.&lt;/p&gt;
&lt;p&gt;In the case of Linux, most distributions include a pre-built FFmpeg in their software repositories. Therefore, you can install FFmpeg from those even more quickly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/download.html#build-windows&quot;&gt;Download for Microsoft Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/download.html#build-mac&quot;&gt;Download for Mac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/download.html#build-linux&quot;&gt;Download for Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ffmpeg-history&quot;&gt;FFmpeg history&lt;/h3&gt;
&lt;p&gt;The project was started in 2000 by the awesome &lt;a href=&quot;https://bellard.org&quot;&gt;Fabrice Bellard&lt;/a&gt;. The name is a concatenation of “FF” meaning “fast-forward” and MPEG, the name of a video standards group. It has been very well, active and alive since then, &lt;a href=&quot;https://ffmpeg.org/releases/&quot;&gt;releasing&lt;/a&gt; a new release about every three months.&lt;/p&gt;
&lt;h3 id=&quot;ffmpeg-supported-codecs-and-formats&quot;&gt;FFmpeg supported codecs and formats&lt;/h3&gt;
&lt;p&gt;The default FFmpeg shipped with my Ubuntu Linux distribution supports about 460 codecs and 370 formats.&lt;/p&gt;
&lt;p&gt;See it for yourself:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -codecs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -formats&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;compilation-of-ffmpeg&quot;&gt;Compilation of FFmpeg&lt;/h3&gt;
&lt;p&gt;Keep in mind that the supported codecs and formats (and filters, demuxers, muxers, input and output methods, etc.) are highly dependent on the so-called compilation flags.&lt;/p&gt;
&lt;p&gt;This means that the above number only represents the fact that it supports at least this many codecs and formats. Still, there are even more that the package builders excluded for various reasons, e.g.: licensing, architecture, size considerations, etc.&lt;/p&gt;
&lt;p&gt;Since FFmpeg is &lt;a href=&quot;https://ffmpeg.org/download.html#repositories&quot;&gt;open source&lt;/a&gt;, you can &lt;a href=&quot;https://trac.ffmpeg.org/wiki/CompilationGuide&quot;&gt;compile FFmpeg&lt;/a&gt; for yourself at any time.&lt;/p&gt;
&lt;p&gt;Suppose for example, that you care about your layer’s size (therefore the bootstrap speed) in AWS Lambda. In this case, you can compile an FFmpeg binary that only contains the mp3 encoder for example, and nothing else. For a full tutorial on &lt;a href=&quot;https://img.ly/blog/how-to-run-ffmpeg-on-aws-spot-instances-for-scalable-low-cost-video-processing/&quot;&gt;running FFmpeg on AWS Spot Instances&lt;/a&gt;, see our cloud guide. Prefer Google Cloud? Our guide on &lt;a href=&quot;https://img.ly/blog/ffmpeg-on-google-cloud-platform-guide/&quot;&gt;running FFmpeg on Google Cloud Platform&lt;/a&gt; shows you how.&lt;/p&gt;
&lt;p&gt;Also, you might not want to run into licensing issues and leave out stuff that would cause problems for your use case. Therefore you choose to leave out particular codecs/formats. I highly recommend checking out the “—enable-gpl”, “—enable-nonfree” and “—enable-version3” &lt;a href=&quot;https://github.com/FFmpeg/FFmpeg/blob/master/configure&quot;&gt;compilation flags&lt;/a&gt; in this case, as well as &lt;a href=&quot;https://ffmpeg.org/legal.html&quot;&gt;this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Or you might want to have a standalone FFmpeg binary in your project (e.g.: embedded, or some cloud instance), that does not depend on any operating system libraries. Then you want to make a so-called static build, that compiles in all the libraries into a single binary file, and does not depend on your OS’ libraries and the runtime loading of other FFmpeg libraries. Search around for “—enable-static” in this case.&lt;/p&gt;
&lt;p&gt;Finally, you can find pre-built static FFmpeg builds &lt;a href=&quot;https://johnvansickle.com/ffmpeg/&quot;&gt;right here&lt;/a&gt; too. Alternatively, you can &lt;a href=&quot;https://img.ly/blog/how-to-run-ffmpeg-inside-a-docker-container/&quot;&gt;package FFmpeg in a Docker container&lt;/a&gt; for consistent environments - our Docker guide covers this approach.&lt;/p&gt;
&lt;h3 id=&quot;ffmpegs-strengths&quot;&gt;FFmpeg’s strengths&lt;/h3&gt;
&lt;p&gt;FFmpeg reads and writes most video and audio formats that matter for most of us. It is a very capable and high-performance tool for converting and manipulating these formats.&lt;/p&gt;
&lt;p&gt;But FFmpeg can do even more! For examples of these operations integrated into an automated pipeline, read our article on a &lt;a href=&quot;https://img.ly/blog/building-a-production-ready-batch-video-processing-server-with-ffmpeg/&quot;&gt;batch video processing server.&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;filtering&quot;&gt;Filtering&lt;/h3&gt;
&lt;p&gt;FFmpeg has vast amounts of filters for audio and video. Therefore, video manipulation is also a key feature of FFmpeg.&lt;/p&gt;
&lt;h3 id=&quot;hardware-acceleration&quot;&gt;Hardware acceleration&lt;/h3&gt;
&lt;p&gt;It does support many kinds of hardware accelerations! Video encoding is a very resource-intensive operation, and you might come across quite a few hardware devices or features that might speed up your process!&lt;/p&gt;
&lt;p&gt;Most notably, if you have an NVIDIA card, you can increase your H.264 or H.265 encoding and decoding throughput by multipliers compared to your CPU. But other things, such as VDPAU, VAAPI, or OpenCL, can be leveraged to boost your pipeline’s throughput.&lt;/p&gt;
&lt;p&gt;Learn more about the supported hardware acceleration methods &lt;a href=&quot;https://trac.ffmpeg.org/wiki/HWAccelIntro&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;versatile-inputoutput-methods&quot;&gt;Versatile input/output methods&lt;/h3&gt;
&lt;p&gt;FFmpeg is also very capable when it comes to accessing input and output data.&lt;/p&gt;
&lt;p&gt;Just to name a few: it can use your webcam, record from your microphone, grab your screen, or capture from your Blackmagic DeckLink. But FFmpeg can download directly from a web address, open all kinds of streams, read from a pipe, a socket, and of course, from files.&lt;/p&gt;
&lt;p&gt;The same holds true for outputting the data. It can write to your webcam, play audio on your microphone… Just kidding:) It can output to files, streams, pipes, sockets and so on.&lt;/p&gt;
&lt;h3 id=&quot;running-example-commands&quot;&gt;Running example commands&lt;/h3&gt;
&lt;p&gt;This article is full of FFmpeg commands that are working examples. The reason for that is that you could test these out for yourself! But the command line interfaces of different operating systems are slightly different, so the commands in this article are meant to be executed in a Linux bash shell.&lt;/p&gt;
&lt;p&gt;To adopt these command lines to Microsoft Windows, you might need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Change (cd) into the directory where you extracted the ffmpeg.exe. Alternatively, add that directory to the &lt;a href=&quot;https://duckduckgo.com/?t=ffab&amp;#x26;q=add+binary+to+path+windows&quot;&gt;path&lt;/a&gt; to make it callable from anywhere.&lt;/li&gt;
&lt;li&gt;You might need to replace “ffmpeg” to “ffmpeg.exe”&lt;/li&gt;
&lt;li&gt;You will need to replace ”&lt;strong&gt;\&lt;/strong&gt;“-s (backslashes) at the end of the lines with ”&lt;strong&gt;^&lt;/strong&gt;“-s (hats)&lt;/li&gt;
&lt;li&gt;You’ll need to replace the &lt;code&gt;fontfile&lt;/code&gt; argument’s value to something like this: &lt;code&gt;fontfile=/Windows/Fonts/arial.ttf&lt;/code&gt; to get commands with the drawtext filter working.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;MacOS users will need steps #1 and #4.&lt;/p&gt;
&lt;h2 id=&quot;introduction-to-media-concepts&quot;&gt;Introduction to media concepts&lt;/h2&gt;
&lt;p&gt;Now let’s have a quick overview of media concepts. These concepts will be vital for us if we want to understand the latter sections of this article and FFmpeg’s workings. To keep this section brief, it is a higher-level, simplified explanation of these concepts.&lt;/p&gt;
&lt;h3 id=&quot;audio&quot;&gt;Audio&lt;/h3&gt;
&lt;p&gt;We’ll briefly cover the following terms:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sampling rate&lt;/li&gt;
&lt;li&gt;Bitrate&lt;/li&gt;
&lt;li&gt;Channels&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;sampling-rate&quot;&gt;Sampling Rate&lt;/h3&gt;
&lt;p&gt;The sampling rate is the factor that shows how many times we measure/scan/sample the input data stream.&lt;/p&gt;
&lt;p&gt;The image below shows the measurement windows (quantization) as gray bars.&lt;/p&gt;
&lt;p&gt;Why does this matter? Because it is a balancing act. If we measure the signal less often, we’ll lose more details (bad). Also, by having fewer samples, we’ll have less data in the end. Therefore the file size will be smaller (good).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1600px) 1600px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1600&quot; height=&quot;1200&quot; src=&quot;https://img.ly/_astro/img-2-sampling-rate_16IiDT.webp&quot; srcset=&quot;/_astro/img-2-sampling-rate_ZEjEs8.webp 640w, /_astro/img-2-sampling-rate_2lFo7Q.webp 750w, /_astro/img-2-sampling-rate_ZptRzw.webp 828w, /_astro/img-2-sampling-rate_2dctkz.webp 1080w, /_astro/img-2-sampling-rate_ZlnVMi.webp 1280w, /_astro/img-2-sampling-rate_16IiDT.webp 1600w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here are some ballpark values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;8 kHz (GSM - Low quality)&lt;/li&gt;
&lt;li&gt;44.1 kHz (CD - High quality)&lt;/li&gt;
&lt;li&gt;48 kHz (Very high quality)&lt;/li&gt;
&lt;li&gt;88.2 kHz (Insane - usually for production only)&lt;/li&gt;
&lt;li&gt;96 kHz (Insane - usually for production only)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are no definite “right answers” here. The question is what is “good enough” for your use case? GSM focuses on speech, and not even quality but understandability and the least possible amount of data. Therefore, they found that 8 kHz is enough (there are quite a few more tricks), for their purposes.&lt;/p&gt;
&lt;p&gt;The “CD quality” aimed for high quality. Therefore they chose 44.1 kHz, that number has some history in it, but the main reason for aiming above 40 kHz lies in physics and how the human ear works.&lt;/p&gt;
&lt;p&gt;There were two very smart guys whose &lt;a href=&quot;https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem&quot;&gt;theorem&lt;/a&gt; basically says that if you want a quite good signal representation, you have to sample it at twice the speed as its original frequency. Human hearing generally &lt;a href=&quot;https://en.wikipedia.org/wiki/Hearing_range&quot;&gt;works&lt;/a&gt; up until about 20 kHz, so if you want “good quality”, you should aim for at least 40 kHz. And 40 kHz + some headroom + some more physics + historical reasons = 44.1 kHz! :)&lt;/p&gt;
&lt;p&gt;As for the higher rates, those are only used when very high-quality audio editing is needed.&lt;/p&gt;
&lt;h3 id=&quot;bitrate&quot;&gt;Bitrate&lt;/h3&gt;
&lt;p&gt;Bitrate represents the amount of data per second that results from our transcoding/quantization process. If it is 1411 kbit/s, that means that for every second of audio data, about 1411 kbit of output data will be produced.&lt;/p&gt;
&lt;p&gt;Therefore, you can say that 1 minute of audio with 1411 kbit/sec will require:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(1411 kbit / 8) kbyte * 60 second = 10582 kbyte = 10.33 mbyte&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now, it is only easy like that with raw audio data and with a few simple codecs, e.g. PCM in WAVs.&lt;/p&gt;
&lt;p&gt;Codecs compressing hard might throw your numbers around a little, as input data might be compressible with different rates. Variable bitrate is usually happening to save space. The encoder might output a lower bitrate if the data is “simple” and does not require high precision.&lt;/p&gt;
&lt;p&gt;Here are some ballpark values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;13 kbits/s (GSM quality)&lt;/li&gt;
&lt;li&gt;320 kbit/s (High-quality MP3)&lt;/li&gt;
&lt;li&gt;1411 kbit/s (16bit WAV, CD quality, PCM)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;channels&quot;&gt;Channels&lt;/h3&gt;
&lt;p&gt;Inside of most audio formats, you can have more audio channels. This means multiple, separated audio streams can be in the same file.&lt;/p&gt;
&lt;p&gt;Many times, multiple channels have their own name:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you have a single microphone, you will most probably record it into a single channel called Mono.&lt;/li&gt;
&lt;li&gt;General music from the FM radio or streaming services usually has two channels in a so-called “Stereo” configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With stereo, there could be several methods how the audio “image” can be made richer by leveraging audio &lt;a href=&quot;https://en.wikipedia.org/wiki/Panning%5F(audio)&quot;&gt;panning&lt;/a&gt;, time and phase-shifting and much more. There is a special recording technique too, called &lt;a href=&quot;https://en.wikipedia.org/wiki/Binaural_recording&quot;&gt;Binaural recording&lt;/a&gt;, which is super awesome. Wear headphones for &lt;a href=&quot;https://www.youtube.com/watch?v=aQH-jwE_kfo&quot;&gt;this&lt;/a&gt;, and don’t be scared:)&lt;/p&gt;
&lt;p&gt;For example, here are &lt;a href=&quot;https://peach.blender.org/&quot;&gt;Big Buck Bunny&lt;/a&gt;’s audio waveforms in &lt;a href=&quot;https://www.audacityteam.org/&quot;&gt;Audacity&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1756px) 1756px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1756&quot; height=&quot;307&quot; src=&quot;https://img.ly/_astro/img-3-waveforms_QyR5N.webp&quot; srcset=&quot;/_astro/img-3-waveforms_d7t0d.webp 640w, /_astro/img-3-waveforms_20zbLg.webp 750w, /_astro/img-3-waveforms_2hIR4S.webp 828w, /_astro/img-3-waveforms_1Awtx2.webp 1080w, /_astro/img-3-waveforms_Z31aji.webp 1280w, /_astro/img-3-waveforms_UYkTp.webp 1668w, /_astro/img-3-waveforms_QyR5N.webp 1756w&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can see that there are two lines of waveforms and also that they are pretty similar. That is normal, as you usually hear the same thing with your two ears, but the matter is in the subtle differences between the two. That’s where directionality, richness, and all kinds of other effects lie.&lt;/p&gt;
&lt;p&gt;But why stop at two? The list continues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.1, as it is often called, means three channels: 2 for stereo and one for the LFE (“low-frequency effects” a.k.a.: “bass”).&lt;/li&gt;
&lt;li&gt;5.1 is similar, with five directional channels (2 front, 1 center, 2 rear) and the LFE.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So channels are just separate “recordings” or “streams” of audio signals.&lt;/p&gt;
&lt;h3 id=&quot;image-properties&quot;&gt;Image properties&lt;/h3&gt;
&lt;p&gt;For images, there are quite a few parameters, but we’ll check out only these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resolution&lt;/li&gt;
&lt;li&gt;Bit-depth&lt;/li&gt;
&lt;li&gt;Transparency&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;resolution&quot;&gt;Resolution&lt;/h3&gt;
&lt;p&gt;An image consists of pixels, single points that have a single color. The resolution of an image determines how many columns and rows of pixels are in an image. In other words: an image has a width and a height.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1520px) 1520px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1520&quot; height=&quot;572&quot; src=&quot;https://img.ly/_astro/img-4-resolution-1_Z28Fb9g.webp&quot; srcset=&quot;/_astro/img-4-resolution-1_2oaDvQ.webp 640w, /_astro/img-4-resolution-1_Z1a5LF7.webp 750w, /_astro/img-4-resolution-1_2cdshh.webp 828w, /_astro/img-4-resolution-1_Z2vj565.webp 1080w, /_astro/img-4-resolution-1_Zv4bSK.webp 1280w, /_astro/img-4-resolution-1_Z28Fb9g.webp 1520w&quot;&gt;&lt;/p&gt;
&lt;p&gt;This image shows the first 10 pixels in the first row.&lt;/p&gt;
&lt;p&gt;Here are some ballpark values for resolution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“HD” or “Full HD” or “1K” or “1080p” means 1920x1080 pixels.&lt;/li&gt;
&lt;li&gt;“4K” could mean a few values, but it should be about 3840x2160 pixels.&lt;/li&gt;
&lt;li&gt;A regular 16mp photo you make of your cat is about 4608x3456 pixels.&lt;/li&gt;
&lt;li&gt;General social media image posts are about 1080x1080 pixels.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;bit-depth&quot;&gt;Bit-depth&lt;/h3&gt;
&lt;p&gt;Bit-depth represents the number of bits used for storing a single pixel’s color value. This is the same balancing game, and you need to decide between quality or file size.&lt;/p&gt;
&lt;p&gt;General ballpark values for bit-depth:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Bits&lt;/th&gt;&lt;th&gt;Colors&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;Black &amp;#x26; White&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;256&lt;/td&gt;&lt;td&gt;B/W or Limited color palette&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;24&lt;/td&gt;&lt;td&gt;16.7m&lt;/td&gt;&lt;td&gt;3x&lt;strong&gt;8 bit&lt;/strong&gt; for R-G-B “True color”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;1073m&lt;/td&gt;&lt;td&gt;3x&lt;strong&gt;10 bit&lt;/strong&gt; for R-G-B “Deep color”&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;These last two sometimes are referred to as “8 bit” or “10 bit” respectively, especially when talking about videos. That means 8/10 bits per single color channel.&lt;/p&gt;
&lt;h3 id=&quot;transparency&quot;&gt;Transparency&lt;/h3&gt;
&lt;p&gt;Some image formats support an additional channel together with the red, green, and blue components: the alpha channel. The alpha channel determines how transparent a single pixel is, and it can have different bit-depths, it is usually either 1, 8 or 16 bits.&lt;/p&gt;
&lt;p&gt;If the alpha channel is 1 bit, then the format can encode a pixel to be either transparent or non-transparent. If it is 8 or more bits, then the format can encode 256 or more steps of transparency.&lt;/p&gt;
&lt;h3 id=&quot;video-properties&quot;&gt;Video properties&lt;/h3&gt;
&lt;p&gt;Video data is built by single images shown right after each other. This brings in most attributes of images and a few more!&lt;/p&gt;
&lt;p&gt;So a video has a &lt;code&gt;resolution&lt;/code&gt; that is its width and height.&lt;/p&gt;
&lt;p&gt;Then the first obvious parameter of a video is the &lt;code&gt;framerate&lt;/code&gt;, which defines how many images are shown in a second. Common values for this are 24, 25, 30, or 60.&lt;/p&gt;
&lt;p&gt;A video file also has a &lt;code&gt;codec&lt;/code&gt; assigned to it, which is the format describing how all those images were compressed into this video file. There are many more attributes of videos, but this is a good start.&lt;/p&gt;
&lt;h3 id=&quot;video-codecs&quot;&gt;Video codecs&lt;/h3&gt;
&lt;p&gt;Compression is a super important thing when it comes to video because you have thousands of images to keep together. If you aren’t doing it in a smart way, then the resulting video will be very, very large.&lt;/p&gt;
&lt;p&gt;Just imagine a 2-minute video, with 30 fps. That means it will have 60 s * 2 * 30 fps = 3600 frames! I have just taken a screenshot of an HD video, which was 730 kbyte in JPEG format. Now 3600 frame * 730 kbyte equals 2.5 gigabytes!&lt;/p&gt;
&lt;p&gt;Can you imagine that? I hope not, and that’s because compression brings that way, way down, to the level of tens of megabytes. These days a video of that size is quite high quality and about 2 hours long. Also, don’t forget, that JPEG is already compressed, a single frame would be 6 mbyte when uncompressed. Now that 2-minute video would be 21 gigabytes if we’d store it uncompressed.&lt;/p&gt;
&lt;p&gt;Standard codecs such as H.264 and H.265 are doing very clever and complex operations to achieve high compression ratios with good quality.&lt;/p&gt;
&lt;p&gt;Just think about that, most frames in a video are quite similar, only containing small differences. So if we could only store that little difference between frames, we’d won a huge bonus! And that’s just one of the many tricks codecs do.&lt;/p&gt;
&lt;p&gt;Codec designers are also exploiting the weaknesses and features of the human eye. Such as the fact that we are more sensitive to light intensity changes than color changes (say hello to &lt;a href=&quot;https://en.wikipedia.org/wiki/YUV&quot;&gt;YUV&lt;/a&gt;). And they can get away with lower quality details for parts &lt;a href=&quot;https://en.wikipedia.org/wiki/Motion_blur#Biology&quot;&gt;that are moving fast&lt;/a&gt;, and so on.&lt;/p&gt;
&lt;p&gt;Because why lose precious bits for things that you can’t even notice?!&lt;/p&gt;
&lt;p&gt;There are many codecs out there, with different goals in mind, although the majority focus on keeping the file size low.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H.264, H.265: These are the most common ones, with the widest support in browsers, phones, players, etc. It focuses on small file sizes with good quality. (At the cost of resource intensiveness.)&lt;/li&gt;
&lt;li&gt;Apple ProRes, DNxHD: These are common formats for production. They focus on quality and ease of processing and not on file size.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;audio-codecs&quot;&gt;Audio codecs&lt;/h3&gt;
&lt;p&gt;The goal of audio codecs is the same as what we saw with the video codecs. It is just harder to demonstrate it as audio does not consist of single image frames but audio frames/packets. So an analog audio signal is of an almost infinite, or at least very high quality if you think of it.&lt;/p&gt;
&lt;p&gt;At the lowest level, the speed and amplitude resolution is very high. We could say “atomic”, as we need to measure and store the speed and direction of atoms. So if you want to store that exactly, that will require a super high-quality measurement, which will also result in a very high bitrate data stream.&lt;/p&gt;
&lt;p&gt;Thankfully, the sound is at least not propagating with light speed so we can save quite a lot just by that fact. (There’s no need for an extreme sampling rate.) Then our hearing is very limited if we take the previous paragraph as a scale, so we win there again. We don’t need most of that high precision that is there.&lt;/p&gt;
&lt;p&gt;But still, if we take our hearing capability and want to store raw audio data with about 44.1 kHz of sample rate with about 1 Mbit/sec bitrate, we’d still get quite a lot of data. Check the calculations in the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#bitrate&quot;&gt;audio bitrate&lt;/a&gt; section above.&lt;/p&gt;
&lt;p&gt;So raw audio can be compressed further, which is what many popular codecs do. They also exploit the human senses, but this time the human ear. We started with the basics that the human ear has a limit on the frequencies it can detect. Therefore, we can save a lot by cutting out the range of frequencies outside our hearing range. Unless you are a bat, you are fine between 20-20khz! :)&lt;/p&gt;
&lt;p&gt;But there are other tricks, for example, &lt;a href=&quot;https://en.wikipedia.org/wiki/Auditory_masking&quot;&gt;auditory masking&lt;/a&gt;. That means that the presence of one frequency can affect your capability to detect a different frequency. From the codec’s viewpoint, it can skip encoding a few frequencies if it is smart enough to know which ones you’ll not notice. I’m sure there are a lot more tricks, let me know if you know about a few more interesting ones!&lt;/p&gt;
&lt;p&gt;Here is a list of common codecs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MP3, AAC, OGG: These are common lossy audio formats.&lt;/li&gt;
&lt;li&gt;PCM (e.g. in a WAV container), FLAC: These are lossless formats.&lt;/li&gt;
&lt;li&gt;MIDI: It is a funny format. It is like a music sheet that might sound different on different players or settings. It is usually not made from real audio data, but from recording a digital keyboard or as an output from an audio composing software.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;containers&quot;&gt;Containers&lt;/h3&gt;
&lt;p&gt;Now we got through the fundamental building blocks, the image, the video, the video codecs, and the audio codecs, and we reached the top of this iceberg: the containers.&lt;/p&gt;
&lt;p&gt;A container is a format specification, that combines all these streams into a single file format. It defines how to put all these data together, how to attach metadata (e.g. author, description, etc), how to synchronize these streams, and sometimes a container even contains indexes to aid seeking.&lt;/p&gt;
&lt;p&gt;So, for example, a MOV container can contain an H.264 video stream and an AAC audio stream together.&lt;/p&gt;
&lt;p&gt;Common containers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MOV&lt;/li&gt;
&lt;li&gt;MP4&lt;/li&gt;
&lt;li&gt;MKV&lt;/li&gt;
&lt;li&gt;WebM&lt;/li&gt;
&lt;li&gt;WAV (audio only)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;example-material&quot;&gt;Example Material&lt;/h2&gt;
&lt;p&gt;I will use these example materials as inputs in the following parts of this article. If you’d like to follow along, save these files for yourself!&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Resource&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Big Buck Bunny&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_60fps_normal.mp4&quot;&gt;http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_60fps_normal.mp4&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Train&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-examples/train.jpg&quot;&gt;train.jpg&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Smiley&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-examples/smiley.png&quot;&gt;smiley.png&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Voice recording&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-examples/voice_recording.wav&quot;&gt;voice_recording.wav&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Big Buck Bunny’s audio&lt;/td&gt;&lt;td&gt;ffmpeg -i bbb_sunflower_1080p_60fps_normal.mp4 -map 0:1 bbb_audio.wav&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;n&lt;/p&gt;
&lt;p&gt;And we will make our own audio file by extracting the audio from the Big Buck Bunny movie! We’ll use this file as an example, so after downloading the video file, please execute this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:1&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the middle of this article, you’ll understand this command, but for now, just make sure to have the WAV file next to your video file to test out the commands later in the article.&lt;/p&gt;
&lt;p&gt;We’ll use these files in the following parts of this article. Therefore make sure to get them!&lt;/p&gt;
&lt;h2 id=&quot;ffplay-and-ffprobe&quot;&gt;FFplay and FFprobe&lt;/h2&gt;
&lt;p&gt;FFmpeg is the name of the main binary and the project itself, but it is shipped together with two other binaries, ffplay and ffprobe.&lt;/p&gt;
&lt;p&gt;Let’s check them out quickly, right in the command line!&lt;/p&gt;
&lt;h3 id=&quot;ffplay&quot;&gt;FFplay&lt;/h3&gt;
&lt;p&gt;FFplay is a basic video player, that can be used for playing media. It’s not a friendly video player, but it is a good testing ground for various things.&lt;/p&gt;
&lt;p&gt;To execute it, just simply supply a media file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffplay&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to test this exact command, you’ll need to get the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#example-material&quot;&gt;example&lt;/a&gt; files.&lt;/p&gt;
&lt;p&gt;For example, it can be used to preview filters (we’ll discuss those &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#filtering&quot;&gt;later&lt;/a&gt;), but let’s see an example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffplay&lt;/span&gt;&lt;span&gt; -vf&lt;/span&gt;&lt;span&gt; &quot;drawtext=text=&apos;HELLO THERE&apos;:y=h-text_h-10:x=(w/2-text_w/2):fontsize=200:f&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1962px) 1962px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1962&quot; height=&quot;1145&quot; src=&quot;https://img.ly/_astro/img-5-big-bunny_Z1KSyDO.webp&quot; srcset=&quot;/_astro/img-5-big-bunny_Z2tEJ77.webp 640w, /_astro/img-5-big-bunny_Z2gSLvG.webp 750w, /_astro/img-5-big-bunny_1Aamch.webp 828w, /_astro/img-5-big-bunny_Z14XPyG.webp 1080w, /_astro/img-5-big-bunny_1Yyeb4.webp 1280w, /_astro/img-5-big-bunny_1AAWwo.webp 1668w, /_astro/img-5-big-bunny_Z1KSyDO.webp 1962w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;ffprobe&quot;&gt;FFprobe&lt;/h3&gt;
&lt;p&gt;FFprobe, as its name implies, is a tool for getting information about media files.&lt;/p&gt;
&lt;p&gt;This command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffprobe&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will return us some general information about the video file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Input #0, mov,mp4,m4a,3gp,3g2,mj2, from &apos;bbb_sunflower_1080p_60fps_normal.mp4&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    title           : Big Buck Bunny, Sunflower version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    artist          : Blender Foundation 2008, Janus Bager Kristensen 2013&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:0[0x1](und): Video: h264 [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:1[0x2](und): Audio: mp3 [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:2[0x3](und): Audio: ac3 [...]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have abbreviated it heavily, as we’ll check this out later.&lt;/p&gt;
&lt;p&gt;But FFprobe is way more powerful than just this!&lt;/p&gt;
&lt;p&gt;With the following command, we can get the same listing in JSON format, which is machine-readable!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffprobe&lt;/span&gt;&lt;span&gt; -v&lt;/span&gt;&lt;span&gt; error&lt;/span&gt;&lt;span&gt; -hide_banner&lt;/span&gt;&lt;span&gt; -print_format&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;span&gt; -show_streams&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The explanation of this command is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“&lt;strong&gt;-v error -hide_banner&lt;/strong&gt;”: This part hides extra output, such as headers and the default build information.&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;-print_format json&lt;/strong&gt;”: Obviously, this causes ffprobe to output a JSON.&lt;/li&gt;
&lt;li&gt;“&lt;strong&gt;-show_streams&lt;/strong&gt;” is the main switch that requests the stream information.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;streams&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;index&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;codec_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;h264&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;codec_long_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1920&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;bit_rate&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;4001453&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;duration&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;634.533333&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;############################&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;[~50 lines removed]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;index&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;codec_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mp3&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;channels&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;bit_rate&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;160000&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;############################&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;[~40 lines removed]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;index&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;codec_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ac3&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;channels&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;############################&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;[~20 lines removed]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this output, you can see three streams of data in this video file. The first (index: 0) is a video stream, that is an HD video with an H.264 codec. Then we have two audio streams, the first (index: 1) is a simple mp3 stream with stereo audio, and the second (index: 2) is an ac3 stream with 6 channels, most likely in an 5.1 configuration.&lt;/p&gt;
&lt;p&gt;I have removed quite a lot of output for brevity, but you can get way more information out of these streams, e.g. fps for the video stream and so on.&lt;/p&gt;
&lt;p&gt;Other than &lt;strong&gt;-show_streams&lt;/strong&gt;, there are 3 more: &lt;strong&gt;-show_format&lt;/strong&gt;, &lt;strong&gt;-show_packets&lt;/strong&gt; and &lt;strong&gt;-show_frames&lt;/strong&gt;. Unless you are really deep in the rabbit hole, you’ll not need the last two, but &lt;strong&gt;-show_format&lt;/strong&gt; could be useful:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffprobe&lt;/span&gt;&lt;span&gt; -v&lt;/span&gt;&lt;span&gt; error&lt;/span&gt;&lt;span&gt; -hide_banner&lt;/span&gt;&lt;span&gt; -print_format&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;span&gt; -show_format&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;format&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;filename&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bbb_sunflower_1080p_60fps_normal.mp4&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;nb_streams&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;nb_programs&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;format_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov,mp4,m4a,3gp,3g2,mj2&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;format_long_name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;QuickTime / MOV&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;start_time&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0.000000&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;duration&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;634.533333&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;355856562&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;bit_rate&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;4486529&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;probe_score&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;tags&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;major_brand&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;isom&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;minor_version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;compatible_brands&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;isomavc1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;creation_time&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2013-12-16T17:59:32.000000Z&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Big Buck Bunny, Sunflower version&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;artist&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Blender Foundation 2008, Janus Bager Kristensen 2013&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;comment&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;genre&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Animation&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;composer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Sacha Goedegebure&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an overview of “what is this file”. As we see, it is a MOV file (format_name), with three streams (nb_streams), and it is 634 seconds long. Also, there are some tags where we can see the title, the artist, and other information.&lt;/p&gt;
&lt;h2 id=&quot;ffmpeg-concepts&quot;&gt;FFmpeg concepts&lt;/h2&gt;
&lt;p&gt;Here is a quick intro to how FFmpeg actually works!&lt;/p&gt;
&lt;p&gt;For those who are just joining in: please get the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#example-material&quot;&gt;example assets&lt;/a&gt; if you want to test out the commands shown in this chapter!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1592px) 1592px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1592&quot; height=&quot;948&quot; src=&quot;https://img.ly/_astro/img-6-input-output_1VpFon.webp&quot; srcset=&quot;/_astro/img-6-input-output_1ca7gh.webp 640w, /_astro/img-6-input-output_KPHvd.webp 750w, /_astro/img-6-input-output_1qXtyE.webp 828w, /_astro/img-6-input-output_8zrzA.webp 1080w, /_astro/img-6-input-output_Wreqk.webp 1280w, /_astro/img-6-input-output_1VpFon.webp 1592w&quot;&gt;&lt;/p&gt;
&lt;p&gt;FFmpeg opens the file, decodes it into memory, then encodes the in-memory packets back and puts them into some container: some output file. The term “codec” is a mix of the words “&lt;strong&gt;cod&lt;/strong&gt;er &amp;#x26; &lt;strong&gt;e&lt;/strong&gt;n&lt;strong&gt;c&lt;/strong&gt;oder”. Those are the magic parts before and after the “decoded frames”.&lt;/p&gt;
&lt;p&gt;The decoded frames are uncompressed images in-memory, e.g. the most basic pixel format for video frames is called “rgb24”. This just stores red, green, and blue values right after each other in 3x8 bits, or 3x1 byte, which could hold 16m colors.&lt;/p&gt;
&lt;p&gt;The importance of this is that other than &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#editing-without-reencoding&quot;&gt;a few exceptions&lt;/a&gt;, you can only manipulate or encode the decoded frames. So when we get to different audio/video filters or transcoding, you’ll need the decoded frames for all that. But don’t worry, FFmpeg does this automatically for you.&lt;/p&gt;
&lt;h3 id=&quot;inputs&quot;&gt;Inputs&lt;/h3&gt;
&lt;p&gt;So you see and probably guessed, that FFmpeg must access the input data somehow. FFmpeg knows how to handle most media files, as the awesome people who develop FFmpeg and the related libraries made encoders and decoders for most formats available!&lt;/p&gt;
&lt;p&gt;Don’t think that it is a trivial thing. Many formats are reverse engineered, a hard task requiring brilliant people.&lt;/p&gt;
&lt;p&gt;So although we often refer to input files, the input could come from many sources, such as the network, a hardware device and so on. We’ll learn more about that &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#inputs&quot;&gt;later&lt;/a&gt; on in this article.&lt;/p&gt;
&lt;p&gt;Many media files are containers for different streams, meaning that a single file might contain multiple streams of content.&lt;/p&gt;
&lt;p&gt;For example, a .mov file might contain one or more streams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;video tracks&lt;/li&gt;
&lt;li&gt;audio tracks (e.g. for the different languages or audio formats such as stereo or 5.1)&lt;/li&gt;
&lt;li&gt;subtitle tracks&lt;/li&gt;
&lt;li&gt;thumbnails&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these are streams of data from the viewpoint of FFmpeg. Input files and their streams are numerically differentiated with a 0-based index. So, for example, 1:0 means the first(0) stream of the second(1) input file. We’ll &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#mapping&quot;&gt;learn more&lt;/a&gt; about that later too!&lt;/p&gt;
&lt;p&gt;Important to note that FFmpeg can open any number of input files simultaneously, and the filtering and mapping will decide what it will do with those. Again more on that later!&lt;/p&gt;
&lt;h3 id=&quot;streams&quot;&gt;Streams&lt;/h3&gt;
&lt;p&gt;As we have seen in the previous section, streams are the fundamental building blocks of containers. So every input file must have at least one stream. And that’s what you can list by the simple &lt;code&gt;ffmpeg -i&lt;/code&gt; command for example.&lt;/p&gt;
&lt;p&gt;A stream might contain an audio format such as MP3, or a video format such as an H.264 stream.&lt;/p&gt;
&lt;p&gt;Also, a stream, depending on the codec, might contain multiple “things”. For example, an mp3 or a WAV stream might include various audio channels.&lt;/p&gt;
&lt;p&gt;So the building block hierarchy, in this case is: File → Stream → Channels.&lt;/p&gt;
&lt;h3 id=&quot;outputs&quot;&gt;Outputs&lt;/h3&gt;
&lt;p&gt;Of course, an output could be a local file, but it doesn’t need to be. It could be a socket, a stream and so on. In the same way as with inputs, you could have multiple outputs, and the mapping determines what goes into which output file.&lt;/p&gt;
&lt;p&gt;The output also must have some format or container. Most of the time FFmpeg can and will guess that for us, mostly from the extension, but we can specify it too.&lt;/p&gt;
&lt;h3 id=&quot;mapping&quot;&gt;Mapping&lt;/h3&gt;
&lt;p&gt;Mapping refers to the act of connecting input file streams with output file streams. So if you give 3 input files and 4 output files to FFmpeg, you must also define what should go to where.&lt;/p&gt;
&lt;p&gt;If you give a single input and a single output, then FFmpeg will guess it for you without specifying any mapping, but make sure you know how exactly that happens, to avoid surprises. More on all that later!&lt;/p&gt;
&lt;h3 id=&quot;filtering-1&quot;&gt;Filtering&lt;/h3&gt;
&lt;p&gt;Filtering stands for the feature of FFmpeg to modify the decoded frames (audio or video). Other applications might call them effects, but i’m sure there is a reason why FFmpeg calls them filters.&lt;/p&gt;
&lt;p&gt;There are two kinds of filtering supported by FFmpeg, simple and complex. In this article we’ll only discuss the complex filters, as it is a superset of the simple filters, and this way, we avoid confusion and redundant content.&lt;/p&gt;
&lt;p&gt;Simple filters are a single chain of filters between a single input and output. Complex filters can have more chains of filters, with any number of inputs and outputs.&lt;/p&gt;
&lt;p&gt;The following figure extends the previous overview image with the filtering module:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1686px) 1686px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1686&quot; height=&quot;1520&quot; src=&quot;https://img.ly/_astro/img-7-encode-decode_Z2fYIgb.webp&quot; srcset=&quot;/_astro/img-7-encode-decode_13jrWu.webp 640w, /_astro/img-7-encode-decode_1P8TzA.webp 750w, /_astro/img-7-encode-decode_ZMUCC2.webp 828w, /_astro/img-7-encode-decode_10OkPy.webp 1080w, /_astro/img-7-encode-decode_kekdC.webp 1280w, /_astro/img-7-encode-decode_KN3vN.webp 1668w, /_astro/img-7-encode-decode_Z2fYIgb.webp 1686w&quot;&gt;&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;complex filter graph&lt;/code&gt; is built from &lt;code&gt;filter chains&lt;/code&gt;, which are built from &lt;code&gt;filters&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So a single &lt;strong&gt;filter&lt;/strong&gt; does a single thing, for example, changes the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#volume&quot;&gt;volume&lt;/a&gt;. This filter is quite trivial, it has a single input, changes the volume, and it has a single output.&lt;/p&gt;
&lt;p&gt;For video, we could check out the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#scale&quot;&gt;scale&lt;/a&gt; filter, which is also quite straightforward: it has a single input, scales the incoming frames, and it has a single output too.&lt;/p&gt;
&lt;p&gt;You can &lt;strong&gt;chain&lt;/strong&gt; these filters, meaning that you connect the output of one to the input of the next one! So you can have a &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#volume&quot;&gt;volume&lt;/a&gt; filter after an &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#aecho&quot;&gt;echo&lt;/a&gt; filter, for example, and this way, you’ll add echo, and then you change the volume.&lt;/p&gt;
&lt;p&gt;This way, your chain will have a single input, and it will do several things with it and will output something at the end.&lt;/p&gt;
&lt;p&gt;Now, the “&lt;strong&gt;complex&lt;/strong&gt;” comes in when you have multiple chains of these filters!&lt;/p&gt;
&lt;p&gt;But before we go there, you should also know that some single filters might have multiple inputs or outputs!&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#overlay&quot;&gt;overlay&lt;/a&gt; filter puts 2 video streams above each other and will output a single video stream.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#split&quot;&gt;split&lt;/a&gt; filter splits a single video stream into 2+ video streams (by copying).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let’s discuss a complex example from a bird’s eye view! I have two video files, I want to put them above each other, and I want the output in two files/sizes, 720p and 1080p.&lt;/p&gt;
&lt;p&gt;Now, that’s where complex filtering will be faithful to its name: to achieve this, you’ll need several filter chains!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chain 1: &lt;code&gt;[input1.mp4] [input2.mp4]&lt;/code&gt; → &lt;strong&gt;overlay&lt;/strong&gt; → &lt;strong&gt;split&lt;/strong&gt; → &lt;code&gt;[overlaid1] [overlaid2]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Chain 2: &lt;code&gt;[overlaid1]&lt;/code&gt; → &lt;strong&gt;scale&lt;/strong&gt; → &lt;code&gt;[720p_output]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Chain 3: &lt;code&gt;[overlaid2]&lt;/code&gt; → &lt;strong&gt;scale&lt;/strong&gt; → &lt;code&gt;[1080p_output]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you see, you can connect chains, and you can connect chains to output files. There is a rule that you can only consume a chain once, and that’s why we used split instead of the same input for chains 2 and 3.&lt;/p&gt;
&lt;p&gt;The takeaway is this: with complex filter graphs (and mapping), you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build individual chains of filters&lt;/li&gt;
&lt;li&gt;connect input files to filter chains&lt;/li&gt;
&lt;li&gt;connect filter chains to filter chains&lt;/li&gt;
&lt;li&gt;connect filter chains to output files&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;ffmpegs-command-line-system&quot;&gt;FFmpeg’s command line system&lt;/h2&gt;
&lt;p&gt;For those who are just joining in: please get the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#example-material&quot;&gt;example assets&lt;/a&gt; if you want to test out the commands shown in this chapter!&lt;/p&gt;
&lt;h3 id=&quot;ffmpeg-cli&quot;&gt;FFmpeg CLI&lt;/h3&gt;
&lt;p&gt;Finally, we arrived at FFmpeg, and trust me, we’ll execute it quite a lot of times! Let’s see how FFmpeg’s command line options are organized, as that is the first tricky part we need to understand!&lt;/p&gt;
&lt;p&gt;FFmpeg mostly thinks about input and output files and their options together with global options. You specify input files with the “-i” flag followed by a file name. For the output file, specify it as-is without any preceding CLI (command line interface) flag.&lt;/p&gt;
&lt;h3 id=&quot;specifying-an-input-file&quot;&gt;Specifying an input file&lt;/h3&gt;
&lt;p&gt;Let’s specify just an input file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following image helps to understand the output:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1623px) 1623px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1623&quot; height=&quot;907&quot; src=&quot;https://img.ly/_astro/img-8-output_12XQP9.webp&quot; srcset=&quot;/_astro/img-8-output_Z1rgNnu.webp 640w, /_astro/img-8-output_Z2duH6y.webp 750w, /_astro/img-8-output_Z29hKMP.webp 828w, /_astro/img-8-output_ZaxUwk.webp 1080w, /_astro/img-8-output_Z1MW61C.webp 1280w, /_astro/img-8-output_12XQP9.webp 1623w&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, you get the “banner”, where you see the build information and lib versions. If you watch closely, you’ll see the compilation flags, starting with &lt;strong&gt;—&lt;/strong&gt;, e.g. —enable-shared.&lt;/li&gt;
&lt;li&gt;Then you get the same output as we have seen with ffprobe earlier.&lt;/li&gt;
&lt;li&gt;And then you get a complaint that there is no output file(s) specified. That’s fine for now.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can remove the banner here with “-hide_banner”, but for brevity’s sake I’ll not include that anymore in the commands here, and I will leave it out from the outputs too.&lt;/p&gt;
&lt;p&gt;Now, let’s get brave, and specify an output file!&lt;/p&gt;
&lt;h3 id=&quot;specifying-an-output&quot;&gt;Specifying an output&lt;/h3&gt;
&lt;p&gt;As I’ve said earlier, the output file is understood by FFmpeg as it is just a filename. But more specifically, it is after the input(s) specifications, and it is not a value of any other switches.&lt;/p&gt;
&lt;p&gt;Don’t be confused for now, but yes, FFmpeg can have as many inputs and outputs as you’d like. We’ll cover that in more detail soon!&lt;/p&gt;
&lt;p&gt;This command line specifies a single output file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before taking a look at the output, let me congratulate you! You have just converted a video file into an audio file, by keeping just the audio content!&lt;/p&gt;
&lt;p&gt;This is how you transcode! Of course, you’ll want to specify more parameters later on.&lt;/p&gt;
&lt;p&gt;So, here is the output:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1174px) 1174px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1174&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/img-9-output_yvlsv.webp&quot; srcset=&quot;/_astro/img-9-output_ZXLK5E.webp 640w, /_astro/img-9-output_Z1dxq3z.webp 750w, /_astro/img-9-output_Z1fKoIz.webp 828w, /_astro/img-9-output_256iWk.webp 1080w, /_astro/img-9-output_yvlsv.webp 1174w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let’s analyze it!&lt;/p&gt;
&lt;p&gt;(1) First, we have our input metadata printing, which we saw many times already.&lt;/p&gt;
&lt;p&gt;(2) Then we have something called “stream mapping”. We forced FFmpeg into a decision situation, as we specified an input file with 1 video and 2 audio streams. We said we wanted an audio output (guessed from the .wav extension). But we didn’t specify which audio stream we wanted, so let’s see what FFmpeg decided:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“&lt;strong&gt;Stream #0:2&lt;/strong&gt;” means “The first input file’s third stream” or “input file index 0’s stream with index 2.” This is the input.&lt;/li&gt;
&lt;li&gt;”&lt;strong&gt;-&gt; #0:0&lt;/strong&gt;” means the first output file’s first stream. This is the output.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg.html#Automatic-stream-selection&quot;&gt;Here&lt;/a&gt; you can learn more about how FFmpeg decide this.&lt;/li&gt;
&lt;li&gt;Later on, we’ll manually override the mapping.&lt;/li&gt;
&lt;li&gt;Summary: FFmpeg decided to convert the third stream in the input file (the ac3 5.1 audio) into the first stream of the output file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(3) Then we have our output metadata information. This reveals what FFmpeg will output. It usually copies most of the metadata, and here you also see the container/format information too.&lt;/p&gt;
&lt;p&gt;(4) And then we see the output summary. For example, the transcoding was 181x faster than the playback speed. Nice!&lt;/p&gt;
&lt;h3 id=&quot;understanding-the-command-line-order&quot;&gt;Understanding the command line order&lt;/h3&gt;
&lt;p&gt;Before going further, let’s understand FFmpeg’s command line arguments from a bird’s eye view!&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://ffmpeg.org/ffmpeg.html#Synopsis&quot;&gt;manual&lt;/a&gt;, you’ll see this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Parts in […] are meant to be optional, and parts in {…} are meant to be specified 1 or more times.)&lt;/p&gt;
&lt;p&gt;This is the general outline of how to specify inputs, outputs, input options, output options, and global options. The order matters, but it is easy to remember: global options, inputs and outputs. Also, i/o options come BEFORE the i/o specification.&lt;/p&gt;
&lt;p&gt;Let’s put these into pseudo command line options, to understand it better:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# One inputs, one output, nothing fancy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input1.mp4&lt;/span&gt;&lt;span&gt; output1.wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Two inputs, one output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input1.mp4&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input2.mp4&lt;/span&gt;&lt;span&gt; output1.wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Two inputs, two outputs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input1.mp4&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input2.mp4&lt;/span&gt;&lt;span&gt; output1.wav&lt;/span&gt;&lt;span&gt; output2.mp3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# One input, one output, with options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; [input1 &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input1.mp4&lt;/span&gt;&lt;span&gt; [output2 &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; output1.wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Two inputs, two outputs with options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; [input1 &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input1.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       [input2 &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; input2.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       [output1 &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; output1.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       [output2 &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; output2.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As for the global options, these are the ones you might care about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-hide_banner&lt;/strong&gt;: To skip printing the banner.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-y&lt;/strong&gt;: To overwrite the output even if it exists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, you can run this as many times as you want:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -hide_banner&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it will overwrite the output and be less verbose than earlier.&lt;/p&gt;
&lt;p&gt;Without explaining the options themselves, let’s just see some real-world examples with options:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1047px) 1047px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1047&quot; height=&quot;345&quot; src=&quot;https://img.ly/_astro/img-10-cmd-order_1VUsz0.webp&quot; srcset=&quot;/_astro/img-10-cmd-order_Z24PuYm.webp 640w, /_astro/img-10-cmd-order_2rcG5A.webp 750w, /_astro/img-10-cmd-order_tI8X3.webp 828w, /_astro/img-10-cmd-order_1VUsz0.webp 1047w&quot;&gt;&lt;/p&gt;
&lt;p&gt;And here it is with two inputs and two outputs:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1296px) 1296px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1296&quot; height=&quot;381&quot; src=&quot;https://img.ly/_astro/img-11-cmd-order_1tE6MK.webp&quot; srcset=&quot;/_astro/img-11-cmd-order_Z13ghw4.webp 640w, /_astro/img-11-cmd-order_Z130DqX.webp 750w, /_astro/img-11-cmd-order_Z1YzaGd.webp 828w, /_astro/img-11-cmd-order_4yOs1.webp 1080w, /_astro/img-11-cmd-order_1CBEPs.webp 1280w, /_astro/img-11-cmd-order_1tE6MK.webp 1296w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;mapping-files&quot;&gt;Mapping files&lt;/h3&gt;
&lt;p&gt;We saw above that this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… will result in an audio file that contains one of the audio streams from the input video chosen by FFmpeg. This &lt;a href=&quot;https://ffmpeg.org/ffmpeg.html#Automatic-stream-selection&quot;&gt;automatic stream selection&lt;/a&gt; is usually handy when it is trivial. For example, when you have one stream as input and one output file, you don’t need to specify any mapping manually.&lt;/p&gt;
&lt;p&gt;But in cases where it is not so trivial, you are usually better off manually specifying what you really want to do.&lt;/p&gt;
&lt;p&gt;The following image summarises what our current situation is:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1770px) 1770px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1770&quot; height=&quot;918&quot; src=&quot;https://img.ly/_astro/img-12-mapping_Z1pRjg7.webp&quot; srcset=&quot;/_astro/img-12-mapping_1bbT7B.webp 640w, /_astro/img-12-mapping_2dl19K.webp 750w, /_astro/img-12-mapping_1WXOdo.webp 828w, /_astro/img-12-mapping_5YBOe.webp 1080w, /_astro/img-12-mapping_fvTdb.webp 1280w, /_astro/img-12-mapping_Z2oS17K.webp 1668w, /_astro/img-12-mapping_Z1pRjg7.webp 1770w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The video stream was not matched, as the output format was an audio file (.wav). But then FFmpeg chose Stream #2, because it has more channels.&lt;/p&gt;
&lt;p&gt;So what if we’d like to get the stereo track instead? That is where mapping comes in! The mapping is a parameter of the OUTPUT file. Therefore the mapping arguments should come right before our output file definition!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:1&lt;/span&gt;&lt;span&gt; stereo_audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The argument &lt;strong&gt;-map 0:1&lt;/strong&gt; means, that in the &lt;code&gt;output&lt;/code&gt; (since we specify it as an output option) we’d like to have &lt;code&gt;Input #0&lt;/code&gt;’s (the first input file) &lt;code&gt;Stream #1&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;Let’s see the relevant parts from the output!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Input #0, mov,mp4,m4a,3gp,3g2,mj2, from &apos;bbb_sunflower_1080p_60fps_normal.mp4&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Stream mapping:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:1 -&gt; #0:0 (mp3 (mp3float) -&gt; pcm_s16le (native))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Output #0, wav, to &apos;stereo_audio_only.wav&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Metadata:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Stream #0:0(und): [...] stereo [...]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The “Stream #0:1 -&gt; #0:0” part means that we have successfully overridden the mapping, to get the mp3 stream (0:1) into our output! Also, the output metadata reveals that we’ll get a stereo result instead of the 5.1 earlier.&lt;/p&gt;
&lt;h3 id=&quot;multiple-outputs&quot;&gt;Multiple outputs&lt;/h3&gt;
&lt;p&gt;You can have multiple outputs from a single input, let’s see when that might be useful!&lt;/p&gt;
&lt;p&gt;Let’s say, we want to extract BOTH audio streams into two separate WAV files! It is super easy:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:1&lt;/span&gt;&lt;span&gt; stereo_audio_only.wav&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:2&lt;/span&gt;&lt;span&gt; ac3_audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See? I have just specified two output files with two mapping specifications! Also, I have sneaked in the “-y” to have it overwrite our previous file!&lt;/p&gt;
&lt;p&gt;Let’s check out the relevant parts of the output!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Input #0, mov,mp4,m4a,3gp,3g2,mj2, from &apos;bbb_sunflower_1080p_60fps_normal.mp4&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Stream mapping:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:1 -&gt; #0:0 (mp3 (mp3float) -&gt; pcm_s16le (native))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:2 -&gt; #1:0 (ac3 (native) -&gt; pcm_s16le (native))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Output #0, wav, to &apos;stereo_audio_only.wav&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Stream #0:0(und): [...] stereo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Output #1, wav, to &apos;ac3_audio_only.wav&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Stream #1:0(und): Audio: [...] 5.1(side)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the mapping reveals two lines, as we have two outputs! And indeed, you’ll get two .wav files as the output, one is stereo, and one is 5.1!&lt;/p&gt;
&lt;p&gt;There might be several other reasons why you’d want to get multiple outputs. Let’s briefly check out a few!&lt;/p&gt;
&lt;p&gt;Different formats:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; stereo_audio_only.wav&lt;/span&gt;&lt;span&gt;  stereo_audio_only.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wow, did you catch that? We just created a WAV and an mp3 in a single command line! I’ve reverted to the automatic stream selection for brevity’s sake.&lt;/p&gt;
&lt;p&gt;A bit closer to real-life needs, you might want different output qualities:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;0:1&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; stereo_audio_only_high_quality.mp3&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;0:1&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 64k&lt;/span&gt;&lt;span&gt;  stereo_audio_only_low_quality.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;strong&gt;-b:a 320k&lt;/strong&gt; means “&lt;strong&gt;b&lt;/strong&gt;itrate of &lt;strong&gt;a&lt;/strong&gt;udio should be around &lt;strong&gt;320 kbit/sec&lt;/strong&gt;”. So I have requested FFmpeg to make two mp3s for me, from the stereo stream of the input.&lt;/p&gt;
&lt;p&gt;Checking on the files, this is what we got:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt; 25Mb stereo_audio_only_high_quality.mp3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4,9Mb stereo_audio_only_low_quality.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One more common reason for having multiple outputs or using mapping is when we introduce filters into our pipeline, but that will be discussed later!&lt;/p&gt;
&lt;p&gt;Now you understand the foundations of how to communicate your basic requirements to FFmpeg via its command line! Great job! Now we can dive even deepert.&lt;/p&gt;
&lt;h2 id=&quot;hands-on-with-ffmpeg&quot;&gt;Hands-on with FFmpeg&lt;/h2&gt;
&lt;p&gt;In this section, we will discover and even try out some common features of FFmpeg!&lt;/p&gt;
&lt;p&gt;For those who are just joining in: please get the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#example-material&quot;&gt;example assets&lt;/a&gt; if you want to test out the commands shown in this chapter!&lt;/p&gt;
&lt;h3 id=&quot;inputs-1&quot;&gt;Inputs&lt;/h3&gt;
&lt;p&gt;Let’s see the common ways FFmpeg is fed with different data!&lt;/p&gt;
&lt;h3 id=&quot;file&quot;&gt;File&lt;/h3&gt;
&lt;p&gt;Of course, you have already seen that if you have a local file on your filesystem, FFmpeg is happy to read it!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:1&lt;/span&gt;&lt;span&gt; stereo_audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command which is exactly the same as one of our previous ones just reads a local file. Really, that’s it.&lt;/p&gt;
&lt;h3 id=&quot;network&quot;&gt;Network&lt;/h3&gt;
&lt;p&gt;Did you know, that FFmpeg can open a file directly on the network?!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; bbb_first_5_seconds.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command above opens the file directly from the network and saves the first 5 seconds into a local file!&lt;/p&gt;
&lt;p&gt;I wanted to spare bandwidth for these awesome guys over renderfarming.net, so I added the duration flag: &lt;strong&gt;-t 5&lt;/strong&gt;. FFmpeg doesn’t even download the full video for this operation. Isn’t that wonderful?!&lt;/p&gt;
&lt;h3 id=&quot;webcam&quot;&gt;Webcam&lt;/h3&gt;
&lt;p&gt;FFmpeg can also open your webcam!&lt;/p&gt;
&lt;p&gt;This is an example command for Linux:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; v4l2&lt;/span&gt;&lt;span&gt; -framerate&lt;/span&gt;&lt;span&gt; 25&lt;/span&gt;&lt;span&gt; -video_size&lt;/span&gt;&lt;span&gt; 640x480&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; /dev/video0&lt;/span&gt;&lt;span&gt; 10seconds_of_webcam.webm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would record 10 seconds of your webcam!&lt;/p&gt;
&lt;p&gt;Accessing the webcam happens differently on different platforms. Also specifying parameters is different for each platform, so for this reason, if you’d like to access your webcam with FFmpeg, please refer to the documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/Capture/Webcam#Linux&quot;&gt;Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/Capture/Webcam#Windows&quot;&gt;Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/Capture/Webcam#OSX&quot;&gt;OS X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;microphone&quot;&gt;Microphone&lt;/h3&gt;
&lt;p&gt;Let’s record some audio directly from your microphone!&lt;/p&gt;
&lt;p&gt;List microphones:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;arecord&lt;/span&gt;&lt;span&gt; -l&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start 10 seconds of recording:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; alsa&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; hw:0,0&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt; out.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command was meant to work on Linux, but you can check out how to do that on &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Capture/Desktop#Windows&quot;&gt;Microsoft Windows&lt;/a&gt; or &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Capture/Desktop#macOS&quot;&gt;macOS&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;pipe&quot;&gt;Pipe&lt;/h3&gt;
&lt;p&gt;Finally, FFmpeg can read from a pipe, and also output to a pipe.&lt;/p&gt;
&lt;p&gt;On Linux, you could do something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; wav&lt;/span&gt;&lt;span&gt; pipe:1&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; pv&lt;/span&gt;&lt;span&gt; &gt;&lt;/span&gt;&lt;span&gt; output.wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Alternative, without pv:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; wav&lt;/span&gt;&lt;span&gt; pipe:1&lt;/span&gt;&lt;span&gt; &gt;&lt;/span&gt;&lt;span&gt; output.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command would use the &lt;strong&gt;cat&lt;/strong&gt; program to simply read in the video file and output it to its standard output. Then this output is piped INTO FFmpeg, through its standard input. The combination “&lt;strong&gt;-i -&lt;/strong&gt;” means “read from standard input”. By the way, standard input would be your keyboard otherwise, if we wouldn’t use any redirection here.&lt;/p&gt;
&lt;p&gt;Then we specify the required output format for FFmpeg, with “&lt;strong&gt;-f wav&lt;/strong&gt;”. This is needed because now we’ll have no output file name, and FFmpeg will not be able to guess the format. Then we specify “&lt;strong&gt;pipe:1&lt;/strong&gt;” as an output, meaning we’d like FFmpeg to output to its standard output.&lt;/p&gt;
&lt;p&gt;From then, we pipe the data into a program called “&lt;strong&gt;pv&lt;/strong&gt;”, it is just a metering tool, that dumps information on the throughput (from its stdin to its stdout). Finally, we redirect pv’s output into a WAV file.&lt;/p&gt;
&lt;p&gt;You might ask why we’d want to do that, why we talk about this. Piping can be useful if you build a complex pipeline from different programs or if you want to spare reading and writing to a local file.&lt;/p&gt;
&lt;p&gt;For example, the node package &lt;a href=&quot;https://www.npmjs.com/package/fluent-ffmpeg&quot;&gt;fluent-ffmpeg&lt;/a&gt; can leverage this functionality by supplying input and output streams. For example, you can read from an S3 bucket and write to one directly.&lt;/p&gt;
&lt;p&gt;But be warned, hell is awaiting you on that road. No kidding. You need to research the limitations of this technique. For example, many formats can not be streamed in this manner, as they need random access to the output data to write the indices at the beginning of the file after processing.&lt;/p&gt;
&lt;h3 id=&quot;outputs-1&quot;&gt;Outputs&lt;/h3&gt;
&lt;p&gt;FFmpeg can output into many protocols, from local file storage and ftp to message queue protocols all the way to streaming protocols.&lt;/p&gt;
&lt;p&gt;For more information, check out the documentation &lt;a href=&quot;https://ffmpeg.org/ffmpeg-protocols.html#Protocols&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;transcoding-audio-with-ffmpeg&quot;&gt;Transcoding audio with FFmpeg&lt;/h2&gt;
&lt;p&gt;In this chapter, we’ll be going to see how to transcode into audio with FFmpeg!&lt;/p&gt;
&lt;p&gt;The general formula is:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; {input&lt;/span&gt;&lt;span&gt; audio&lt;/span&gt;&lt;span&gt; or&lt;/span&gt;&lt;span&gt; video&lt;/span&gt;&lt;span&gt; file&lt;/span&gt;&lt;span&gt; with&lt;/span&gt;&lt;span&gt; audio}&lt;/span&gt;&lt;span&gt; [output &lt;/span&gt;&lt;span&gt;options]&lt;/span&gt;&lt;span&gt; output_audio.ext&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;choosing-a-format&quot;&gt;Choosing a format&lt;/h3&gt;
&lt;p&gt;FFmpeg is quite smart, and by the extension, it can determine which codec to use. If you specify “audio.wav” or “audio.mp3” for example, FFmpeg will use the appropriate codec to do the encoding.&lt;/p&gt;
&lt;p&gt;It is perfectly guessing most of the time. But if you want to specify the format manually, then the “-f” flag is your friend.&lt;/p&gt;
&lt;p&gt;For this, you might want to consult the list of formats:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -formats&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, these three commands will do exactly the same, but the last two requires the &lt;strong&gt;-f&lt;/strong&gt; flag.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Output codec is determined from the extension&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; bbb_audio.mp3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# No extension in the filename&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; mp3&lt;/span&gt;&lt;span&gt; bbb_audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Piped output therefore no filename, so no extension to use for guessing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -f&lt;/span&gt;&lt;span&gt; mp3&lt;/span&gt;&lt;span&gt; pipe:1&lt;/span&gt;&lt;span&gt; &gt;&lt;/span&gt;&lt;span&gt; bbb_audio&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;setting-the-bitrate&quot;&gt;Setting the bitrate&lt;/h3&gt;
&lt;p&gt;In most cases. you want to specify the target bitrate you expect from your codec to output. If you are unsure what bitrate is, please read this article’s &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#bitrate&quot;&gt;audio bitrate&lt;/a&gt; section.&lt;/p&gt;
&lt;p&gt;To specify the audio bitrate, use the “&lt;strong&gt;-b:a&lt;/strong&gt;” option with a corresponding value, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-b:a 320k&lt;/strong&gt;: For the mp3 codec this is considered high quality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-b:a 128k&lt;/strong&gt;: Lower quality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-b:a 64k&lt;/strong&gt;: Low quality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; bbb_audio_320k.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;setting-the-sample-rate&quot;&gt;Setting the sample rate&lt;/h3&gt;
&lt;p&gt;You may want to specify the sample rate to ensure quality or low output file size. Half the sample rate could mean half the output file size. If you are unsure what the sample rate is, please read the “&lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#sampling-rate&quot;&gt;audio sample rate&lt;/a&gt;” section of this article.&lt;/p&gt;
&lt;p&gt;To specify the audio sample rate, use the “&lt;strong&gt;-ar&lt;/strong&gt;” option with a corresponding value, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-ar 48000&lt;/strong&gt;: For high quality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-ar 44100&lt;/strong&gt;: For CD quality (still high).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-ar 22500&lt;/strong&gt;: A bit of a compromise, not recommended for music, but for speech, it might be enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-ar 8000&lt;/strong&gt;: Low quality, e.g. if you only want “understandable” speech.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; bbb_audio_44100khz.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;setting-the-channel-count&quot;&gt;Setting the channel count&lt;/h3&gt;
&lt;p&gt;Setting the channel count can be useful, for example, if you have a stereo recording of a single person’s speech. In that case, you might be content with just a mono output half the size of the original recording.&lt;/p&gt;
&lt;p&gt;If you are unsure what an audio channel is, please read the “&lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#channels&quot;&gt;audio channels&lt;/a&gt;” section of this article.&lt;/p&gt;
&lt;p&gt;To specify the channel count use the “&lt;strong&gt;-ac&lt;/strong&gt;” option with a corresponding value, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-ac 1&lt;/strong&gt;: For mono&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-ac 2&lt;/strong&gt;: For stereo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-ac 6&lt;/strong&gt;: For 5.1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -ac&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; bbb_audio_mono.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;complete-command-line-for-converting-audio-with-ffmpeg&quot;&gt;Complete command line for converting audio with FFmpeg&lt;/h3&gt;
&lt;p&gt;This is how you produce a high-quality output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Convert wav to mp3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -ac&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; bbb_audio_hqfull.mp3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Convert wav to m4a (aac)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -ac&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; bbb_audio_hqfull.m4a&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Convert wav to ogg (vorbis)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -ac&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; bbb_audio_hqfull.ogg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/HighQualityAudio&quot;&gt;this&lt;/a&gt; documentation about good quality audio transcoding too!.&lt;/p&gt;
&lt;h3 id=&quot;lossless-formats&quot;&gt;Lossless formats&lt;/h3&gt;
&lt;p&gt;If you want to convert audio into a lossless format, here are a few choices for you:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Convert to flac (Free Lossless Audio Codec)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -compression_level&lt;/span&gt;&lt;span&gt; 12&lt;/span&gt;&lt;span&gt; bbb_audio_lossless_12.flac&lt;/span&gt;&lt;span&gt; # Best compression, slowest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -compression_level&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; bbb_audio_lossless_5.flac&lt;/span&gt;&lt;span&gt;   # Default&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; -compression_level&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; bbb_audio_lossless_0.flac&lt;/span&gt;&lt;span&gt;   # Least compression, fastest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Convert to wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cp&lt;/span&gt;&lt;span&gt; bbb_audio.wav&lt;/span&gt;&lt;span&gt; bbb_audio_lossless.wav&lt;/span&gt;&lt;span&gt; # Just kidding:)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Convert to wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; any_audio.ext&lt;/span&gt;&lt;span&gt; bbb_audio_lossless.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s good if you know that flac results in a smaller file than WAV, as WAV doesn’t actually compress by default:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;117M bbb_audio.wav&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;52M  bbb_audio_lossless_0.flac&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;45M  bbb_audio_lossless_5.flac&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;43M  bbb_audio_lossless_12.flac&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WAV is generally thought of as a lossless format, but keep in mind that the WAV container can contain lossy content too, but by default FFmpeg uses the pcm_s16le format, which is the 16 bit PCM, that could be understood as lossless.&lt;/p&gt;
&lt;p&gt;Learn more &lt;a href=&quot;https://en.wikipedia.org/wiki/WAV#Comparison_of_coding_schemes&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://trac.ffmpeg.org/wiki/audio%20types&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;transcoding-video-with-ffmpeg&quot;&gt;Transcoding video with FFmpeg&lt;/h2&gt;
&lt;p&gt;In this chapter, we’ll be going to see how to transcode a video file into the two most common formats!&lt;/p&gt;
&lt;h3 id=&quot;converting-to-h264&quot;&gt;Converting to H.264&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Advanced_Video_Coding&quot;&gt;H264&lt;/a&gt; is one of the most popular video codecs. Most devices, browsers and video players understand how to play it. It is efficient in storing video content, but as with most advanced video codecs, it is a resource intensive-process to encode and decode.&lt;/p&gt;
&lt;p&gt;A complete command line for a high-quality H.264 transcoding with high-quality AAC audio is the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-c:v &lt;/span&gt;&lt;span&gt;libx264&lt;/span&gt;&lt;span&gt; -preset&lt;/span&gt;&lt;span&gt; slow&lt;/span&gt;&lt;span&gt; -crf&lt;/span&gt;&lt;span&gt; 22&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-profile:v &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt; -g&lt;/span&gt;&lt;span&gt; 250&lt;/span&gt;&lt;span&gt; -pix_fmt&lt;/span&gt;&lt;span&gt; yuv420p&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;0:0&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:1&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-acodec &lt;/span&gt;&lt;span&gt;aac&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; bbb_transcoded_h264_HQ.mov&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to understand this command and to customize it to match your needs.&lt;/p&gt;
&lt;p&gt;To help you do that, let’s dissect this command!&lt;/p&gt;
&lt;p&gt;Global options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-y&lt;/strong&gt;: Overwrite the output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Input options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-i bbb_sunflower_1080p_60fps_normal.mp4&lt;/strong&gt;: The input file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Output options:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-c:v libx264&lt;/strong&gt;: Set the codec to libx264.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-preset slow&lt;/strong&gt;: libx264 has a lot of variables that you can be tune, and most of them balance the coding speed and the resulting file size. To make your life easier, there are &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.264#Preset&quot;&gt;presets&lt;/a&gt; by which you can easily declare what you need: small size or speed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-crf 22&lt;/strong&gt;: This is the constant rate factor, the main option for setting image quality. It is a number between 0-51, where 0 is lossless, and 51 is the worst quality. Generally, you want something between 17 and 28. This is the option to tune the balance between image quality and file size. Check my comparison video &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#comparing-crf-values-with-h264-and-h265&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-profile:v main -g 250 -pix_fmt yuv420p&lt;/strong&gt;: These are advanced options, guaranteeing you a quite backward compatible result. (See &lt;a href=&quot;https://ffmpeg.org/ffmpeg-codecs.html#Options-26&quot;&gt;this&lt;/a&gt;, &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.264#Profile&quot;&gt;this&lt;/a&gt;, and &lt;a href=&quot;https://ffmpeg.org/ffmpeg.html#Advanced-Video-options&quot;&gt;this&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-map 0:0 -map 0:1&lt;/strong&gt;: You might not need this: these options are selecting the correct video and audio streams. &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#ffprobe&quot;&gt;In our case&lt;/a&gt;, we have two audio streams, and we need the stereo one to avoid some issues with our aac stream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-acodec aac&lt;/strong&gt;: Select the AAC (Advanced Audio Coding) &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#audio-codecs&quot;&gt;codec&lt;/a&gt; for the audio in the output. We need to be more specific than just &lt;strong&gt;-f&lt;/strong&gt; for the format. We need to specify the audio codec here manually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-ar 44100&lt;/strong&gt;: Set the audio &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#sampling-rate&quot;&gt;sampling rate&lt;/a&gt; (learn more about that in previous chapters of this article).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;-b:a 320k&lt;/strong&gt;: Set the audio &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#bitrate&quot;&gt;bitrate&lt;/a&gt; (learn more about that in previous chapters of this article).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;30seconds_of_bb.mkv&lt;/strong&gt;: The output file name. All the options since the last -i (or the last output file) considered to be a modifier for this output.&lt;/p&gt;
&lt;p&gt;Let’s see the output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Input #0, mov,mp4,m4a,3gp,3g2,mj2, from &apos;bbb_sunflower_1080p_60fps_normal.mp4&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Stream mapping:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:0 -&gt; #0:0 (h264 (native) -&gt; h264 (libx264))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:1 -&gt; #0:1 (mp3 (mp3float) -&gt; aac (native))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Output #0, mov, to &apos;bbb_transcoded_h264_HQ.mov&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Stream #0:0(und): Video: h264 (libx264) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], q=-1--1, 60 fps, 15360 tbn, 60 tbc (default)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, 5.1(side), fltp, 320 kb/s (default)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frame=38074 fps= 35 q=-1.0 Lsize=  324855kB time=00:10:34.51 bitrate=4194.1kbits/s dup=2 drop=0 speed=0.58x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this, we understand that FFmpeg chose the mp3 stream from the input file because we told it to do so. (Remember, it has two audio streams in it, a stereo mp3 and a 5.1 ac3.) We also see that my machine could transcode with 35fps (0.58 times the playback speed), and our settings resulted in an average video bitrate of 4200 kbit/s.&lt;/p&gt;
&lt;p&gt;The video bitrate is an interesting question in this mode. With the CRF option, we specify the “constant visual quality” we want. To reach a constant visual quality, the encoder works hard to guess how much it can compress certain parts of every frame, and the result of that guess defines the final average video bitrate.&lt;/p&gt;
&lt;p&gt;If you want even better results with H.264, and you can afford a bit more processing time and a bit more complicated process, check out the &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.264#twopass&quot;&gt;2-pass encoding&lt;/a&gt; instead of the constant rate factor method introduced above.&lt;/p&gt;
&lt;p&gt;To learn more about these two different rate control methods, read the awesome &lt;a href=&quot;https://slhck.info/video/2017/03/01/rate-control.html&quot;&gt;Understanding Rate Control Modes&lt;/a&gt; article. And to learn more about the intricacies of H.264 encoding, check out the &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.264&quot;&gt;H264 encoding guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#comparing-crf-values-with-h264-and-h265&quot;&gt;later on&lt;/a&gt;, I will show you a comparison video that shows how different CRF values perform!&lt;/p&gt;
&lt;h3 id=&quot;converting-to-h265&quot;&gt;Converting to H.265&lt;/h3&gt;
&lt;p&gt;H.265 is the successor of H.264, according to the &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.265&quot;&gt;official FFmpeg manual&lt;/a&gt;, it offers 25-50% bitrate savings while retaining the same visual quality.&lt;/p&gt;
&lt;p&gt;A complete command line for a high-quality H.265 transcoding with high-quality AAC audio is the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-c:v &lt;/span&gt;&lt;span&gt;libx265&lt;/span&gt;&lt;span&gt; -preset&lt;/span&gt;&lt;span&gt; slow&lt;/span&gt;&lt;span&gt; -crf&lt;/span&gt;&lt;span&gt; 27&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-profile:v &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt; -g&lt;/span&gt;&lt;span&gt; 250&lt;/span&gt;&lt;span&gt; -pix_fmt&lt;/span&gt;&lt;span&gt; yuv420p&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;0:0&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; 0:1&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-acodec &lt;/span&gt;&lt;span&gt;aac&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; bbb_transcoded_h265_HQ.mov&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the result is:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;encoded 38074 frames in 3384.84s (11.25 fps), 1720.32 kb/s, Avg QP:35.29&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;H.265 also has multiple rate control algorithms, I used the CRF method here. If you want to use a different rate control algorithm, then you may check out the &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.265&quot;&gt;H.265 encoding guide&lt;/a&gt;. Also, check out the next section, where I’ll reveal how different CRF values perform!&lt;/p&gt;
&lt;p&gt;This command is almost the same as what we used in the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#converting-to-h264&quot;&gt;H.264 example&lt;/a&gt; above, so please refer to that section to understand the arguments.&lt;/p&gt;
&lt;p&gt;If we compare H.264 and H.265 with our commands above, taking into account this 10-minute long video on my system, these are the results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H.264 is 3 times faster (35 fps vs 11 fps)&lt;/li&gt;
&lt;li&gt;H.264 produces a 2 times larger file (318 mb vs 156 mb)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;comparing-crf-values-with-h264-and-h265&quot;&gt;Comparing CRF values with H.264 and H.265&lt;/h3&gt;
&lt;p&gt;I have created a video for your convenience, that shows the different crf values in action. The selected frame had some movement on it with the leaves in the bunny’s hand. Movement is important with video codecs, as usually that’s where quality losses are first visible.&lt;/p&gt;
&lt;p&gt;This video shows how the different CRF values perform, from 0-51 with the H.264 and H.265 formats!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/vid-1-comparison-264-265.mov&quot;&gt;H.264 &amp;#x26; H.265 CRF comparison video&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Can you guess which program I was using to make this?:))&lt;/p&gt;
&lt;h2 id=&quot;basic-editing-with-ffmpeg&quot;&gt;Basic editing with FFmpeg&lt;/h2&gt;
&lt;p&gt;In this section, we’ll achieve basic editing tasks by using FFmpeg only!&lt;/p&gt;
&lt;p&gt;We’ll just get a basic mp4 with default settings in these examples to keep things simple. But to encode the result in a proper, high quality way, please check the earlier sections where we learned how to encode into H.264 and H.265!&lt;/p&gt;
&lt;h3 id=&quot;trimming-from-the-beginning-of-the-clip&quot;&gt;Trimming from the beginning of the clip&lt;/h3&gt;
&lt;p&gt;It is possible to specify an in-point for a media file. By doing that, you essentially cut off the specified amount from the beginning of the input file. Therefore, FFmpeg will skip the first part of the file and only transcode the remainder!&lt;/p&gt;
&lt;p&gt;For this, you need the “&lt;strong&gt;-ss&lt;/strong&gt;” flag! The value can be specified in seconds (5 or 5.2) or as a timestamp (HOURS:MM:SS.MILLISECONDS).&lt;/p&gt;
&lt;p&gt;To get the outro only, we could seek all the way to the end of the video! (It is 00:10:34.53 or 635 seconds long!)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Get&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 635 - 4 = 631&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -ss&lt;/span&gt;&lt;span&gt; 631&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; last_4_seconds.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# 00:10:34.53 - 4 = 00:10:30.53&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -ss&lt;/span&gt;&lt;span&gt; 00:10:30.53&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; last_4_seconds.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seeking can be a bit tricky, so you may want to learn more about seeking &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Seeking&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;trimming-from-the-end-of-the-clip&quot;&gt;Trimming from the end of the clip&lt;/h3&gt;
&lt;p&gt;You can also set an out-point for an input file, therefore shortening it. There are two options for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-t&lt;/strong&gt;: This sets the duration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-to&lt;/strong&gt;: This sets the timestamp where the input video should stop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two are mutually exclusive, and also they do the same if no -ss is specified. The value can be specified in seconds (5 or 5.2) or as a timestamp (HOURS:MM:SS.MILLISECONDS).&lt;/p&gt;
&lt;p&gt;Let’s experiment with them!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# &quot;Get 30 seconds of the input.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; first_30_seconds.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 00:00:30.0&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; first_30_seconds.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# &quot;Get everything until the content&apos;s 30th second.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -to&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; first_30_seconds.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -to&lt;/span&gt;&lt;span&gt; 00:00:30.0&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; first_30_seconds.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All four above commands result in exactly the same video. (For nerds: even the md5sum is the same.)&lt;/p&gt;
&lt;p&gt;But let’s see how they perform when we introduce seeking!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# &quot;Seek to the 10th second and get me 30 seconds of the input.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -ss&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; part_between_10_and_40.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# &quot;Seek to the 10th second and get the content until the 30th second.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; -ss&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt; -to&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; part_between_10_and_30.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first command will result in a 30 second long video, while the second command will be 20 seconds long only!&lt;/p&gt;
&lt;p&gt;The figure below shows the difference:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;521&quot; src=&quot;https://img.ly/_astro/img-13-trimming_k6wLr.webp&quot; srcset=&quot;/_astro/img-13-trimming_Z151890.webp 640w, /_astro/img-13-trimming_2j0cm0.webp 750w, /_astro/img-13-trimming_DVKAT.webp 828w, /_astro/img-13-trimming_Z1NCTy9.webp 1080w, /_astro/img-13-trimming_Z1Fq4uO.webp 1280w, /_astro/img-13-trimming_Z7BfOo.webp 1668w, /_astro/img-13-trimming_k6wLr.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;editing-without-reencoding&quot;&gt;Editing without reencoding&lt;/h3&gt;
&lt;p&gt;FFmpeg can do something I’m not aware of in any other popular NLE: it can edit videos without reencoding them!&lt;/p&gt;
&lt;p&gt;The usual &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#ffmpeg-concepts&quot;&gt;workflow&lt;/a&gt; is to decode the data frames (a/v) into memory, modify them as much as we like and then encode them into a new video file. The problem with this is that unless you work with raw or lossless codecs, you’ll lose some quality in the process. Another issue with this approach is that it is computationally intensive.&lt;/p&gt;
&lt;p&gt;For certain operations, you can configure FFmpeg, to keep the data frames intact, and this way, you can avoid decoding and encoding them! This is incredibly faster than regular transcoding, usually hundreds of times faster.&lt;/p&gt;
&lt;p&gt;The “certain operations” are those that don’t need to modify the data frames themselves. For example, you can cut and trim this way. Also, you can manipulate streams while keeping others, like you can replace the audio track without touching the video frames.&lt;/p&gt;
&lt;p&gt;All this is a bit of magic, and there are caveats you need to prepare for, but it is good if you know about this, as it is often handy!&lt;/p&gt;
&lt;p&gt;The trick lies in two options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-c:v copy&lt;/strong&gt;: The “copy” video codec&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-c:a copy&lt;/strong&gt;: The “copy” audio codec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s see a few examples!&lt;/p&gt;
&lt;h4 id=&quot;remove-audio-while-keeping-the-video-without-reencoding&quot;&gt;Remove audio while keeping the video without reencoding&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -c:v&lt;/span&gt;&lt;span&gt; copy&lt;/span&gt;&lt;span&gt; -an&lt;/span&gt;&lt;span&gt; copied_video_only.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we used the “&lt;strong&gt;-an&lt;/strong&gt;” option, which removes all audio streams. I remembered it as “&lt;strong&gt;a&lt;/strong&gt;udio &lt;strong&gt;n&lt;/strong&gt;o”, but that is just my mnemonic:)&lt;/p&gt;
&lt;p&gt;Let’s see how fast it was:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frame=38072 fps=20950 q=-1.0 Lsize=  310340kB time=00:10:34.51 bitrate=4006.7kbits/s speed= 349x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So It processed the whole 10 minutes of video in 2 seconds, 349x faster than playback, with 20950 fps!&lt;/p&gt;
&lt;h4 id=&quot;remove-video-while-keeping-the-audio-without-reencoding&quot;&gt;Remove video while keeping the audio without reencoding&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -c:a&lt;/span&gt;&lt;span&gt; copy&lt;/span&gt;&lt;span&gt; -vn&lt;/span&gt;&lt;span&gt; copied_audio_only.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we used the “&lt;strong&gt;-vn&lt;/strong&gt;” option, which removes all video streams. I remembered it as “&lt;strong&gt;v&lt;/strong&gt;ideo &lt;strong&gt;n&lt;/strong&gt;o”.&lt;/p&gt;
&lt;p&gt;Let’s see how fast it was:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;size=   24772kB time=00:10:34.14 bitrate= 320.0kbits/s speed= 776x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;776x faster than playback, finished in about a second, not bad!&lt;/p&gt;
&lt;h4 id=&quot;cut-and-trim-without-reencoding&quot;&gt;Cut and trim without reencoding&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -ss&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;  -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -c:a&lt;/span&gt;&lt;span&gt; copy&lt;/span&gt;&lt;span&gt; -c:v&lt;/span&gt;&lt;span&gt; copy&lt;/span&gt;&lt;span&gt; part_from_10_to_20_copied.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There could be precision issues with seeking while you do this, so you may want to learn more about seeking and copying &lt;a href=&quot;https://trac.ffmpeg.org/wiki/Seeking&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;replace-audio-on-video-file-without-reencoding&quot;&gt;Replace audio on video file without reencoding&lt;/h4&gt;
&lt;p&gt;We have removed audio and video already, but what if we want to swap them?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;voice_recording.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;&quot;0:v&quot;&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; &quot;1:a&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-c:v &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; -c:a&lt;/span&gt;&lt;span&gt; copy&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bbb_with_replaced_audio.mov&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is quite a lot going on in here, so let’s explain the parts!&lt;/p&gt;
&lt;p&gt;First, we have two inputs (&lt;strong&gt;-i&lt;/strong&gt;), meaning we are better off manually specifying the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#mapping&quot;&gt;mapping&lt;/a&gt;. The command would work without the “&lt;strong&gt;-map&lt;/strong&gt;” options, but it would ignore our second input.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-map &quot;0:v&quot; -map &quot;1:a&quot;&lt;/code&gt; means that please use the first file’s (first) video stream and the second file’s (first) audio stream.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;-c:v copy -c:a copy&lt;/code&gt;, we require FFmpeg to copy the already encoded data packets without touching them. Therefore FFmpeg’s work is mostly really just copying bytes, no decoding, no encoding.&lt;/p&gt;
&lt;p&gt;Not surprisingly, that’s what we see in the stream mapping too:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Stream mapping:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:0 -&gt; #0:0 (copy)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #1:0 -&gt; #0:1 (copy)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Press [q] to stop, [?] for help&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frame=38072 fps=9750 q=-1.0 Lsize=  320645kB time=00:10:34.51 bitrate=4139.7kbits/s speed= 162x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And since it is just copying, it was crazy fast, 162x of the playback speed, or almost 10k frames per second!&lt;/p&gt;
&lt;p&gt;But!&lt;/p&gt;
&lt;p&gt;Execute the exact same command, but with “bbb_with_replaced_audio.&lt;strong&gt;mp4&lt;/strong&gt;” (.mp4 container instead of .mov) as an output file! You’ll get this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Could not find tag for codec pcm_s16le in stream #1, codec not currently supported in container&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The message is quite clear. You can not have a pcm_s16le (raw WAV, say that 10 times:)) stream in an MP4 container. I’m not sure if it is FFmpeg’s or the container’s lack of support, but we need to solve this. If you run into this situation, you might consider two solutions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Change the container: I’ve just tried MOV, and it worked.&lt;/li&gt;
&lt;li&gt;Encode the audio: We still copy the video data, and encoding audio isn’t that painful.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I just showed you option #1, so let’s see option #2:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;voice_recording.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;&quot;0:v&quot;&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; &quot;1:a&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-c:v &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-c:a &lt;/span&gt;&lt;span&gt;aac&lt;/span&gt;&lt;span&gt; -b:a&lt;/span&gt;&lt;span&gt; 320k&lt;/span&gt;&lt;span&gt; -ar&lt;/span&gt;&lt;span&gt; 44100&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;bbb_with_replaced_audio_aac.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This copies the video frames and encodes our WAV into a supported codec to be held in the mp4 container. You can refer back to the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#how-to-transcode-audio-with-ffmpeg&quot;&gt;audio encoding&lt;/a&gt; section if you want to learn more about that.&lt;/p&gt;
&lt;p&gt;Here is the output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Stream mapping:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #0:0 -&gt; #0:0 (copy)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Stream #1:0 -&gt; #0:1 (pcm_s16le (native) -&gt; aac (native))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Press [q] to stop, [?] for help&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frame=38072 fps=2176 q=-1.0 Lsize=  313058kB time=00:10:34.51 bitrate=4041.8kbits/s speed=36.3x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;“Only” 36x faster than playback, 2176 fps, still not that bad!&lt;/p&gt;
&lt;h2 id=&quot;filtering-overview&quot;&gt;Filtering overview&lt;/h2&gt;
&lt;p&gt;FFmpeg supports many audio and video &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html&quot;&gt;filters&lt;/a&gt;. Currently, there are 116 audio and 286 video filters, but there are a bit more if we count the hardware accelerated ones too.&lt;/p&gt;
&lt;p&gt;So how do we leverage them?&lt;/p&gt;
&lt;p&gt;There are two ways to define filters, but I’m going to explain the complex filter, as the difference is not much, but it is more versatile. So there is a global option for FFmpeg, called: &lt;strong&gt;&lt;code&gt;-filter_complex&lt;/code&gt;&lt;/strong&gt;. With quite a weird syntax, you can specify all your filters and their parameters right after this option.&lt;/p&gt;
&lt;p&gt;You can imagine the process with the following image:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1352px) 1352px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1352&quot; height=&quot;512&quot; src=&quot;https://img.ly/_astro/img-14-complex_filter_intro_G5hEn.webp&quot; srcset=&quot;/_astro/img-14-complex_filter_intro_117lQc.webp 640w, /_astro/img-14-complex_filter_intro_Yucac.webp 750w, /_astro/img-14-complex_filter_intro_Z1VS6KM.webp 828w, /_astro/img-14-complex_filter_intro_CDlGA.webp 1080w, /_astro/img-14-complex_filter_intro_1ezHqr.webp 1280w, /_astro/img-14-complex_filter_intro_G5hEn.webp 1352w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Basically, your filter graph can access all the inputs (-i a.mp4 -i b.mp4 -i c.mp4), and it can produce as many outputs as you like (-map might be needed).&lt;/p&gt;
&lt;h3 id=&quot;basic-syntax&quot;&gt;Basic syntax&lt;/h3&gt;
&lt;p&gt;Let’s take a look at a simple, basic example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  -t&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;HELLO THERE&apos;:y=20:x=30:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex1.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although &lt;code&gt;-filter_complex&lt;/code&gt; is a global option, I like to put it after the inputs and before the outputs as it is a bit easier to overlook the whole command that way. Thankfully the command line parser of FFmpeg is smart enough, and it works.&lt;/p&gt;
&lt;p&gt;The command above produces a 5-second-long video, where the text “HELLO THERE” is overlaid on the intro of Big Buck Bunny.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1397px) 1397px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1397&quot; height=&quot;912&quot; src=&quot;https://img.ly/_astro/img-15-hello-there_TGV9p.webp&quot; srcset=&quot;/_astro/img-15-hello-there_ZcTUvf.webp 640w, /_astro/img-15-hello-there_Zjr7vJ.webp 750w, /_astro/img-15-hello-there_Z2dDYkq.webp 828w, /_astro/img-15-hello-there_ZRoNox.webp 1080w, /_astro/img-15-hello-there_4xGVW.webp 1280w, /_astro/img-15-hello-there_TGV9p.webp 1397w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let’s understand the weird format for specifying filters!&lt;/p&gt;
&lt;p&gt;We’ll go bottom-up, and we’ll build it from there. So the most basic format is this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FILTER_NAME=ARGUMENT1=VALUE1:ARGUMENT2=VALUE2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;drawtext=text=&apos;HELLO THERE&apos;:y=20:x=30&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing before the first equal (=) sign is the filter’s name, which is the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#drawtext-1&quot;&gt;drawtext&lt;/a&gt; filter in this case. Then we have our first argument, “text” and its value “‘HELLO THERE’”. Right after that, separated with a colon (:) comes the next argument, “y” with a value of “20”.&lt;/p&gt;
&lt;p&gt;You can guess what each of the text, y, x, fontsize and fontfile arguments do, as it is quite self-explaining. But especially for the first time, you’ll heavily rely on the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html&quot;&gt;filtering documentation&lt;/a&gt; to understand every filter and every argument.&lt;/p&gt;
&lt;p&gt;Also, several characters are reserved, such as: &lt;code&gt;, : =&lt;/code&gt; and a few others depending on your environment, so sooner or later you need to learn about &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#toc-Notes-on-filtergraph-escaping&quot;&gt;escaping&lt;/a&gt; too.&lt;/p&gt;
&lt;p&gt;To recap, our pipeline looks like this now:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2384px) 2384px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2384&quot; height=&quot;594&quot; src=&quot;https://img.ly/_astro/img-16-complex_filter_multi_EsmbN.webp&quot; srcset=&quot;/_astro/img-16-complex_filter_multi_2gMSNr.webp 640w, /_astro/img-16-complex_filter_multi_Z171xGF.webp 750w, /_astro/img-16-complex_filter_multi_Z1K5OSl.webp 828w, /_astro/img-16-complex_filter_multi_1VImWl.webp 1080w, /_astro/img-16-complex_filter_multi_1vVNzi.webp 1280w, /_astro/img-16-complex_filter_multi_Z1czkU1.webp 1668w, /_astro/img-16-complex_filter_multi_aGdU2.webp 2048w, /_astro/img-16-complex_filter_multi_EsmbN.webp 2384w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;multiple-filters-in-a-chain&quot;&gt;Multiple filters in a chain&lt;/h3&gt;
&lt;p&gt;This previous command is a single filter chain that consists of a single filter only, but you could have more filters put right after each other! It means that the output of one filter will be the input for the next! The way to do this is by separating them with a comma!&lt;/p&gt;
&lt;p&gt;Let’s draw two boxes with the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#drawbox&quot;&gt;drawbox&lt;/a&gt; filter!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  -t&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;  drawbox=x=10:y=10:w=100:h=100:color=red  ,  drawbox=x=200:y=200:w=100:h=100:color=blue  &quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex2.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See? The output of the first filter is passed to the output of the second filter!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1596px) 1596px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1596&quot; height=&quot;1044&quot; src=&quot;https://img.ly/_astro/img-17-filter-bick-buck_2mfaYg.webp&quot; srcset=&quot;/_astro/img-17-filter-bick-buck_Z1lJ69B.webp 640w, /_astro/img-17-filter-bick-buck_Z1SUwKi.webp 750w, /_astro/img-17-filter-bick-buck_fycbY.webp 828w, /_astro/img-17-filter-bick-buck_ZC3JU5.webp 1080w, /_astro/img-17-filter-bick-buck_ZQcKyY.webp 1280w, /_astro/img-17-filter-bick-buck_2mfaYg.webp 1596w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let’s visualize our pipeline again:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2384px) 2384px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2384&quot; height=&quot;594&quot; src=&quot;https://img.ly/_astro/img-18-complex_filter_multi_2_2bafu7.webp&quot; srcset=&quot;/_astro/img-18-complex_filter_multi_2_Z1qWCdN.webp 640w, /_astro/img-18-complex_filter_multi_2_XFBaH.webp 750w, /_astro/img-18-complex_filter_multi_2_1E6QOl.webp 828w, /_astro/img-18-complex_filter_multi_2_1UA8rw.webp 1080w, /_astro/img-18-complex_filter_multi_2_Z1aU3Xr.webp 1280w, /_astro/img-18-complex_filter_multi_2_Z2qfGxK.webp 1668w, /_astro/img-18-complex_filter_multi_2_pgEz4.webp 2048w, /_astro/img-18-complex_filter_multi_2_2bafu7.webp 2384w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;input-and-output-pads&quot;&gt;Input and output pads&lt;/h3&gt;
&lt;p&gt;Now, we have skipped something this far, because for simple uses FFmpeg is smart enough to do it for us. And this is the specification of a chain’s input and output pads!&lt;/p&gt;
&lt;p&gt;Let’s draw just a single rectangle for now:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  -t&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -filter_complex&lt;/span&gt;&lt;span&gt; &quot;drawbox=x=10:y=10:w=100:h=100:color=red&quot;&lt;/span&gt;&lt;span&gt; filter_complex3.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FFmpeg sees that the input for our filter chain is a single video file, and the output is a single output video file. Therefore, it safely assumes that we want that single input as the input of our single filter chain. And that single output should be the single output of our single output chain.&lt;/p&gt;
&lt;p&gt;That’s really nice, as, in simple situations like this, we don’t need to assign and map inputs and outputs manually! But when we get more inputs, filter chains, or outputs, it is no longer possible. Therefore, we need to understand how to assign inputs and outputs!&lt;/p&gt;
&lt;p&gt;First of all, let’s compare the following two command lines. They result in exactly the same result, but the second one represents what FFmpeg does internally (roughly):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  -t&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -filter_complex&lt;/span&gt;&lt;span&gt; &quot;drawbox=x=10:y=10:w=100:h=100:color=red&quot;&lt;/span&gt;&lt;span&gt; filter_complex3.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  -t&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; -filter_complex&lt;/span&gt;&lt;span&gt; &quot;[0:v]drawbox=x=10:y=10:w=100:h=100:color=red[out_link_0]&quot;&lt;/span&gt;&lt;span&gt; -map&lt;/span&gt;&lt;span&gt; &quot;[out_link_0]&quot;&lt;/span&gt;&lt;span&gt; filter_complex3.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you see the difference? Before our filter chain, an “input pad” is defined: &lt;code&gt;[0:v]&lt;/code&gt;. The expected format between the square brackets is documented in the &lt;a href=&quot;https://ffmpeg.org/ffmpeg.html#Stream-selection&quot;&gt;stream selection&lt;/a&gt; section of the official documentation, and this article already &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#mapping&quot;&gt;covered&lt;/a&gt; it.&lt;/p&gt;
&lt;p&gt;But, a quick summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0:v&lt;/strong&gt;: This means the first video stream of the first input file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:v:0&lt;/strong&gt;: Means exactly the same thing but in a long form.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:0&lt;/strong&gt;: Means the first stream of the first input file (not recommended, as it could be anything in theory. It could be a subtitle stream, a thumbnail, a video or an audio stream…)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:a&lt;/strong&gt;: This means the first audio stream of the first input file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:a:0&lt;/strong&gt;: Means exactly the same thing but in a long form.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0:a:1&lt;/strong&gt;: Means the second (index #1) audio stream of the first input file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we can specify which input file should be connected to which input of the filter graph!&lt;/p&gt;
&lt;p&gt;Also, something similar is going on at the end! Do you see, the &lt;code&gt;[out_link_0]&lt;/code&gt; output pad definition at the end of our filter chain?&lt;/p&gt;
&lt;p&gt;The naming here is easier, as basically you can specify any arbitrary name in here. It roughly means, “please store the output data under this name”.&lt;/p&gt;
&lt;p&gt;And when you specify your output file, you can or need to map it by selecting one of your filter graph outputs! Therefore, we must add the -map “[out_link_0]” option before our output file.&lt;/p&gt;
&lt;p&gt;This map option means this: “Please save the data stream with this name into the following output file.”&lt;/p&gt;
&lt;p&gt;This is how you can visualize this input/output mapping:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2590px) 2590px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2590&quot; height=&quot;724&quot; src=&quot;https://img.ly/_astro/img-19-complex_filter_multi_3_cb9GV.webp&quot; srcset=&quot;/_astro/img-19-complex_filter_multi_3_Z2sAwc5.webp 640w, /_astro/img-19-complex_filter_multi_3_ZfzIQf.webp 750w, /_astro/img-19-complex_filter_multi_3_Z1SuENk.webp 828w, /_astro/img-19-complex_filter_multi_3_1NtTKc.webp 1080w, /_astro/img-19-complex_filter_multi_3_1F6bbb.webp 1280w, /_astro/img-19-complex_filter_multi_3_Z1dAxRT.webp 1668w, /_astro/img-19-complex_filter_multi_3_RAhvH.webp 2048w, /_astro/img-19-complex_filter_multi_3_ZCLv9A.webp 2560w, /_astro/img-19-complex_filter_multi_3_cb9GV.webp 2590w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;multiple-chains&quot;&gt;Multiple chains&lt;/h3&gt;
&lt;p&gt;Coming from the previous sections, you are now ready to see and understand an even more complicated configuration, which has multiple input files, output files, and filter chains!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;train.jpg&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;[0:v]drawbox=x=10:y=10:w=100:h=100:color=red[train_box] ; [1:v]drawbox=x=10:y=10:w=100:h=100:color=red[bbb_box]&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;&quot;[train_box]&quot;&lt;/span&gt;&lt;span&gt; filter_complex4_train.jpg&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;&quot;[bbb_box]&quot;&lt;/span&gt;&lt;span&gt; filter_complex4_bbb.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s see the output (two files next to each other):&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1470px) 1470px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1470&quot; height=&quot;656&quot; src=&quot;https://img.ly/_astro/img-20-filters_output_3_1wupPS.webp&quot; srcset=&quot;/_astro/img-20-filters_output_3_ZQfV9n.webp 640w, /_astro/img-20-filters_output_3_Zswwdy.webp 750w, /_astro/img-20-filters_output_3_1gyM4x.webp 828w, /_astro/img-20-filters_output_3_CwvA.webp 1080w, /_astro/img-20-filters_output_3_Z12PHKj.webp 1280w, /_astro/img-20-filters_output_3_1wupPS.webp 1470w&quot;&gt;&lt;/p&gt;
&lt;p&gt;We had two inputs, and we got two output files, an image, and a video, with a red rectangle on them, with a single command!&lt;/p&gt;
&lt;p&gt;Are you still here? I hope! Let’s understand what happened in that crazy command! We have two input files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-i train.jpg&lt;/strong&gt;: A simple image file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-t 5 -i bbb_sunflower_1080p_60fps_normal.mp4&lt;/strong&gt;: Our video file, but to make it quick, just the first five seconds of it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then the first thing to note is that we have two filter chains! They are separated with a ”&lt;strong&gt;;&lt;/strong&gt;”.&lt;/p&gt;
&lt;p&gt;Our first filter graph is this: &lt;code&gt;[0:v]...[train_box]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This requests the first input file as an input&lt;/li&gt;
&lt;li&gt;Draws a red box&lt;/li&gt;
&lt;li&gt;Saves the output into the “train_box” output pad&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our second filter graph is this: &lt;code&gt;[1:v]...[bbb_box]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This requests the second input file as an input&lt;/li&gt;
&lt;li&gt;Draws a red box&lt;/li&gt;
&lt;li&gt;Saves the output into the “bbb_box” output pad&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And finally, we got two outputs, each mapping to one of the outputs of the filter graph:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-map “[train_box]” filter_complex4_train.jpg&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-map “[bbb_box]” filter_complex4_bbb.mp4&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the same thing visually:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2898px) 2898px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2898&quot; height=&quot;988&quot; src=&quot;https://img.ly/_astro/img-21-complex_filter_multi_4_4Rlkm.webp&quot; srcset=&quot;/_astro/img-21-complex_filter_multi_4_1MDtiy.webp 640w, /_astro/img-21-complex_filter_multi_4_279DG.webp 750w, /_astro/img-21-complex_filter_multi_4_WpIQ9.webp 828w, /_astro/img-21-complex_filter_multi_4_Cj59z.webp 1080w, /_astro/img-21-complex_filter_multi_4_MmBkH.webp 1280w, /_astro/img-21-complex_filter_multi_4_2odyoG.webp 1668w, /_astro/img-21-complex_filter_multi_4_26HLAj.webp 2048w, /_astro/img-21-complex_filter_multi_4_Z2rPCuv.webp 2560w, /_astro/img-21-complex_filter_multi_4_4Rlkm.webp 2898w&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you are thinking about making it even more complex and making filter graphs that combine multiple inputs into one for example, you are on the right track! It is possible, and we will get to that!&lt;/p&gt;
&lt;p&gt;This was the introduction to the filtering system and its syntax.&lt;/p&gt;
&lt;h2 id=&quot;editing-video&quot;&gt;Editing video&lt;/h2&gt;
&lt;p&gt;Now let’s get to know a few filters and make some interesting stuff!&lt;/p&gt;
&lt;h3 id=&quot;resizing-or-scaling&quot;&gt;Resizing or scaling&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#scale&quot;&gt;scale&lt;/a&gt; filter is a simple one, yet it is quite powerful!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;scale=width=600:height=-1:force_original_aspect_ratio=decrease&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex5_scaled1.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The arguments speak for themselves, but a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specifying -1 to either width or height means rescaling while keeping the aspect ratio.&lt;/li&gt;
&lt;li&gt;“force_original_aspect_ratio” can be &lt;code&gt;increase&lt;/code&gt;, &lt;code&gt;decrease&lt;/code&gt;. Meaning it will increase or decrease the image to fit the specified bounding box while keeping the aspect ratio.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;adding-text&quot;&gt;Adding text&lt;/h3&gt;
&lt;p&gt;We have already covered this a little, so let’s dive deeper!&lt;/p&gt;
&lt;p&gt;This is what we used earlier:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;HELLO THERE&apos;:y=20:x=30:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex1.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s discover how to align the text!&lt;/p&gt;
&lt;p&gt;Many filters, including drawtext, support variables in some of its argument’s values. If you scroll down in the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#drawtext-1&quot;&gt;documentation of drawtext&lt;/a&gt;, you’ll find this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The parameters for x and y are expressions containing the following constants and functions: ”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And after this part, you’ll see many variables which you can include in your x and y variables!&lt;/p&gt;
&lt;p&gt;Let’s see:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Align the text to the center&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;HELLO THERE&apos;:y=h/2-text_h/2:x=w/2-text_w/2:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex6_center.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# y=h/2-text_h/2 means: y position = (image height / 2) - (text height / 2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Align the text to the right:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;HELLO THERE&apos;:y=30:x=w-text_w-20:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex6_right.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# x=w-text_w-20 means: x position = image width - text width - 20pixel padding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Align the text to the bottom:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;HELLO THERE&apos;:y=h-text_h-20:x=30:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex6_bottom.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# y=h-text_h-20 means: y position = image height - text height - 20pixel padding&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this is what we’ll get in the end:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1802px) 1802px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1802&quot; height=&quot;1043&quot; src=&quot;https://img.ly/_astro/img-22-filters_output_4_Zc05ub.webp&quot; srcset=&quot;/_astro/img-22-filters_output_4_ZlgkEK.webp 640w, /_astro/img-22-filters_output_4_Z1sYxVz.webp 750w, /_astro/img-22-filters_output_4_ZqN21H.webp 828w, /_astro/img-22-filters_output_4_Z2bweND.webp 1080w, /_astro/img-22-filters_output_4_Z1cq8p8.webp 1280w, /_astro/img-22-filters_output_4_Z27qRVh.webp 1668w, /_astro/img-22-filters_output_4_Zc05ub.webp 1802w&quot;&gt;&lt;/p&gt;
&lt;p&gt;I need to mention one good trick that might not be obvious at first. So the &lt;code&gt;text_h&lt;/code&gt; variable is a tricky one, because different text will be of different height! E.g.: ”____” and “WWW” will result in a different height.&lt;/p&gt;
&lt;p&gt;For this reason, you do not always want to use text_h or even just a constant y=value expression but rather, you need to align text by its &lt;a href=&quot;https://en.wikipedia.org/wiki/Baseline%5F(typography)&quot;&gt;&lt;strong&gt;baseline&lt;/strong&gt;&lt;/a&gt;. So just remember to use the “&lt;strong&gt;ascent&lt;/strong&gt;” variable whenever you need to align text vertically!&lt;/p&gt;
&lt;p&gt;Check out these two examples! Each has two drawtext filters printing ”_” and “_H”:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# This one uses y=200 for both, still the text isn&apos;t aligned properly!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;_&apos;:y=200:x=30:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf,drawtext=text=&apos;_H&apos;:y=200:x=500:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex7_bad_text.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# This one uses y=200-ascent for both and the text is aligned as expected!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;drawtext=text=&apos;_&apos;:y=200-ascent:x=30:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf,drawtext=text=&apos;_H&apos;:y=200-ascent:x=500:fontsize=200:fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex7_good_text.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s compare the difference:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1771px) 1771px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1771&quot; height=&quot;525&quot; src=&quot;https://img.ly/_astro/img-23-filters_output_4_textalign_2lsx6V.webp&quot; srcset=&quot;/_astro/img-23-filters_output_4_textalign_2qB7V4.webp 640w, /_astro/img-23-filters_output_4_textalign_xgGX5.webp 750w, /_astro/img-23-filters_output_4_textalign_Z1SnAfT.webp 828w, /_astro/img-23-filters_output_4_textalign_1It9OQ.webp 1080w, /_astro/img-23-filters_output_4_textalign_L5FXv.webp 1280w, /_astro/img-23-filters_output_4_textalign_JOoiO.webp 1668w, /_astro/img-23-filters_output_4_textalign_2lsx6V.webp 1771w&quot;&gt;&lt;/p&gt;
&lt;p&gt;See? This is the difference between aligning the “top left” or the “baseline” of the text!&lt;/p&gt;
&lt;h3 id=&quot;adding-an-overlay&quot;&gt;Adding an overlay&lt;/h3&gt;
&lt;p&gt;Overlaying is a very interesting thing to do with FFmpeg. Let’s jump right in!&lt;/p&gt;
&lt;h4 id=&quot;basic&quot;&gt;Basic&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;smiley.png&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;overlay&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex8_overlay1.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy as that!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1497px) 1497px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1497&quot; height=&quot;980&quot; src=&quot;https://img.ly/_astro/img-24-overlay_1_Z1nBChF.webp&quot; srcset=&quot;/_astro/img-24-overlay_1_ZncyFD.webp 640w, /_astro/img-24-overlay_1_IaJii.webp 750w, /_astro/img-24-overlay_1_ASKXl.webp 828w, /_astro/img-24-overlay_1_my3ED.webp 1080w, /_astro/img-24-overlay_1_Z1N7Swu.webp 1280w, /_astro/img-24-overlay_1_Z1nBChF.webp 1497w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Of course, the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#overlay&quot;&gt;overlay&lt;/a&gt; filter has a ton of options, but I wanted to demonstrate the easiest possible command line. We don’t even need to mess with input/output pads, as FFmpeg automatically understands the situation: two inputs for the overlay filter and its single output into a single output.&lt;/p&gt;
&lt;p&gt;But just to exercise, we could have executed it like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;smiley.png&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;[0:v][1:v]overlay[output]&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-map &lt;/span&gt;&lt;span&gt;&quot;[output]&quot;&lt;/span&gt;&lt;span&gt; filter_complex8_overlay2.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this would result in the same output! Check it out, now I have specified the two inputs for the overlay: &lt;code&gt;[0:v][1:v]&lt;/code&gt;!&lt;/p&gt;
&lt;h4 id=&quot;aligned&quot;&gt;Aligned&lt;/h4&gt;
&lt;p&gt;Let’s align the smiley into the center!&lt;/p&gt;
&lt;p&gt;As we have seen with the drawtext, the overlay filter’s arguments also support a few dynamic variables. We’ll use those to achieve what we want!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;smiley.png&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;overlay=x=main_w/2-overlay_w/2:y=main_h/2-overlay_h/2&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex8_overlay3.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 869px) 869px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;869&quot; height=&quot;627&quot; src=&quot;https://img.ly/_astro/img-25-overlay_2_UYX62.webp&quot; srcset=&quot;/_astro/img-25-overlay_2_1gM7AF.webp 640w, /_astro/img-25-overlay_2_1EXof0.webp 750w, /_astro/img-25-overlay_2_1JUWjv.webp 828w, /_astro/img-25-overlay_2_UYX62.webp 869w&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;preprocessing-the-input-for-overlay&quot;&gt;Preprocessing the input for overlay&lt;/h4&gt;
&lt;p&gt;Let’s get a bit creative!&lt;/p&gt;
&lt;p&gt;I want to make it &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#scale&quot;&gt;smaller&lt;/a&gt;, and I also want to &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#scale&quot;&gt;blur&lt;/a&gt; it!&lt;/p&gt;
&lt;p&gt;Now pause for a minute, and think about it, how you’d do that?!&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;Ready?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;smiley.png&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;[1:v]scale=w=200:h=-1,gblur=sigma=3[smiley] ; [0:v][smiley]overlay=x=100:y=100&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex8_overlay4.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 996px) 996px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;996&quot; height=&quot;703&quot; src=&quot;https://img.ly/_astro/img-26-overlay_3_6Y6rG.webp&quot; srcset=&quot;/_astro/img-26-overlay_3_ZvuUJA.webp 640w, /_astro/img-26-overlay_3_jhI7.webp 750w, /_astro/img-26-overlay_3_Z2rI2GV.webp 828w, /_astro/img-26-overlay_3_6Y6rG.webp 996w&quot;&gt;&lt;/p&gt;
&lt;p&gt;For this we needed to have two filter graphs!&lt;/p&gt;
&lt;p&gt;The first one is this: &lt;code&gt;[1:v]scale=w=200:h=-1,gblur=sigma=3[smiley]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scales the input image (the smiley).&lt;/li&gt;
&lt;li&gt;Then the scaled output is also blurred.&lt;/li&gt;
&lt;li&gt;Then the output is saved into the output pad named “smiley”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, we have our second filter graph: &lt;code&gt;[0:v][smiley]overlay=x=100:y=100&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This takes as input the first input file (the video).&lt;/li&gt;
&lt;li&gt;This also takes as input the output pad named “smiley”. (We are connecting two chains this time!)&lt;/li&gt;
&lt;li&gt;Then the overlay filter does its overlaying thing, and we trust FFmpeg to pair the unnamed output with the single output file we specified.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;reusing-content&quot;&gt;Reusing content&lt;/h4&gt;
&lt;p&gt;Let’s do one more, a really complicated one!&lt;/p&gt;
&lt;p&gt;Let’s have the outro overlaid over the intro!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; -ss&lt;/span&gt;&lt;span&gt; 00:09:40&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot; [1:v]scale=w=1920/2:h=-1[outro]; [0:v][outro]overlay&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter_complex8_overlay5.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1170px) 1170px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1170&quot; height=&quot;794&quot; src=&quot;https://img.ly/_astro/img-27-overlay_4_ZFl9Ib.webp&quot; srcset=&quot;/_astro/img-27-overlay_4_Z1UFLMT.webp 640w, /_astro/img-27-overlay_4_mGyKD.webp 750w, /_astro/img-27-overlay_4_ZiLCyz.webp 828w, /_astro/img-27-overlay_4_eB1jO.webp 1080w, /_astro/img-27-overlay_4_ZFl9Ib.webp 1170w&quot;&gt;&lt;/p&gt;
&lt;p&gt;We could have achieved it in several ways, e.g. we could use the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#trim&quot;&gt;trim&lt;/a&gt; filter, but to keep it easy, we just open the same file twice and &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#cutting-off-from-the-beginning-of-the-clip&quot;&gt;seek/trim&lt;/a&gt; them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-t 5 -i bbb_sunflower_1080p_60fps_normal.mp4&lt;/strong&gt;: Open the video, and keep the first five seconds of it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-t 5 -ss 00:09:40 -i bbb_sunflower_1080p_60fps_normal.mp4&lt;/strong&gt;: Open the same video again, but seek to the end and keep five seconds from there.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we have two filter graphs again, one scales down the outro, and the second is just an overlay.&lt;/p&gt;
&lt;p&gt;Are you excited?:) I hope these made-up examples opened up your eye for the possibilities, and I hope you’ll create very creative stuff with this knowledge!&lt;/p&gt;
&lt;h3 id=&quot;chroma-keying-green-screen-blue-screen&quot;&gt;Chroma keying, green screen, blue screen&lt;/h3&gt;
&lt;p&gt;In this section, we’ll use chroma keying to remove the background from Big Buck Bunny’s intro, and then we will put the transparent logo over the original video, as if it would be some kind of a logo overlay!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-ss &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt; -t&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-ss &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; bbb_sunflower_1080p_60fps_normal.mp4&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot; [0:v]chromakey=color=0xfdfdfd:similarity=0.1:blend=0.2 , scale=w=-1:h=300 , loop=loop=-1:start=0:size=120[intro] ; [1:v][intro]overlay=x=-40:y=-40&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-t &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt; filter_complex9.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So just to recap, Big Buck Bunny’s first few seconds are like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 854px) 854px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;854&quot; height=&quot;614&quot; src=&quot;https://img.ly/_astro/img-28-chroma_basic_ZCF03.webp&quot; srcset=&quot;/_astro/img-28-chroma_basic_ZMx3FJ.webp 640w, /_astro/img-28-chroma_basic_ZusNr3.webp 750w, /_astro/img-28-chroma_basic_1kYLiL.webp 828w, /_astro/img-28-chroma_basic_ZCF03.webp 854w&quot;&gt;&lt;/p&gt;
&lt;p&gt;And this is the result:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1114px) 1114px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1114&quot; height=&quot;764&quot; src=&quot;https://img.ly/_astro/img-29-chroma1_Z2g6XrH.webp&quot; srcset=&quot;/_astro/img-29-chroma1_Z1RBLwH.webp 640w, /_astro/img-29-chroma1_fjr5z.webp 750w, /_astro/img-29-chroma1_2nsk0h.webp 828w, /_astro/img-29-chroma1_14SEPM.webp 1080w, /_astro/img-29-chroma1_Z2g6XrH.webp 1114w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Also, the butterfly moves its wings repeatedly!&lt;/p&gt;
&lt;p&gt;Let’s examine the command!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;-ss 0.5 -t 2 -i bbb_sunflower_1080p_60fps_normal.mp4&lt;/strong&gt;: We read in the intro from 0.5 to 2.5 seconds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-ss 10 -i bbb_sunflower_1080p_60fps_normal.mp4&lt;/strong&gt;: We read in the video, starting from the 10th second.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we have two filter graphs, the first being this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[0:v]chromakey=color=0xfdfdfd:similarity=0.1:blend=0.2 , scale=w=-1:h=300 , loop=loop=-1:start=0:size=120[intro]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As we see, we have three filters in here!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#chromakey&quot;&gt;&lt;strong&gt;chromakey&lt;/strong&gt;&lt;/a&gt;: This one takes a color and a few parameters as input, and outputs transparent frames. The specified color + the blended areas will be the transparent sections. In our case we replaced the white-ish (#fdfdfd) background color with transparency.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#scale&quot;&gt;&lt;strong&gt;scale&lt;/strong&gt;&lt;/a&gt;: We resize the full 1080p image into something around 300px high.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#loop&quot;&gt;&lt;strong&gt;loop&lt;/strong&gt;&lt;/a&gt;: With the loop filter, we repeat all the 2 seconds worth of 120 frames (60*2) over and over again, to have the butterfly move its wings continuously.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then, finally we have the second filter graph:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[1:v][intro]overlay=x=-40:y=-40&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Nothing fancy, just an overlay of the original video and our chrome keyed intro.&lt;/p&gt;
&lt;h3 id=&quot;what-else&quot;&gt;What else?&lt;/h3&gt;
&lt;p&gt;You might want to check out a few more &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#toc-Video-Filters&quot;&gt;filters&lt;/a&gt;, that I didn’t cover here.&lt;/p&gt;
&lt;p&gt;Here are just a few interesting ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#colorcorrect&quot;&gt;colorcorrect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#colorchannelmixer&quot;&gt;colorchannelmixer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#colorize&quot;&gt;colorize&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#fps&quot;&gt;fps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#trim&quot;&gt;trim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#crop&quot;&gt;crop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#delogo&quot;&gt;delogo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#derain&quot;&gt;derain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#deshake&quot;&gt;deshake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#erosion&quot;&gt;erosion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#edgedetect&quot;&gt;edgedetect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#hflip&quot;&gt;hflip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#vflip&quot;&gt;vflip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#hstack&quot;&gt;hstack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#vstack&quot;&gt;vstack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#xstack&quot;&gt;xstack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#lumakey&quot;&gt;lumakey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#reverse&quot;&gt;reverse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#rotate&quot;&gt;rotate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#scroll&quot;&gt;scroll&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#pad&quot;&gt;pad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#vignette&quot;&gt;vignette&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#zoompan&quot;&gt;zoompan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;audio-manipulation&quot;&gt;Audio manipulation&lt;/h2&gt;
&lt;p&gt;In this chapter, we’ll be going to check out some &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#toc-Audio-Filters&quot;&gt;audio manipulation techniques&lt;/a&gt; with FFmpeg!&lt;/p&gt;
&lt;p&gt;First of all, let’s see our &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#example-material&quot;&gt;example&lt;/a&gt; file:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1920px) 1920px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1920&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/img-30-voice_recording_1UlWX5.webp&quot; srcset=&quot;/_astro/img-30-voice_recording_1ecLD4.webp 640w, /_astro/img-30-voice_recording_cilwF.webp 750w, /_astro/img-30-voice_recording_1XAAfW.webp 828w, /_astro/img-30-voice_recording_17KzOf.webp 1080w, /_astro/img-30-voice_recording_st2hO.webp 1280w, /_astro/img-30-voice_recording_2jkO3S.webp 1668w, /_astro/img-30-voice_recording_1UlWX5.webp 1920w&quot;&gt;&lt;/p&gt;
&lt;p&gt;It is a voice recording, and it is intentionally… well, quite bad.&lt;/p&gt;
&lt;p&gt;From the waveform, it is obvious that there are very different volume ranges in it. This is an example recording where each sentence was read in different strengths: “normal”, “whisper” or “powerful”, this is why you see repeating patterns of amplitude ranges on the image.&lt;/p&gt;
&lt;p&gt;It isn’t visible, but it has some noise too, and of course, it is not normalized or enhanced in any way. Yet.&lt;/p&gt;
&lt;p&gt;Please note that there are different scenarios, requirements, and ways to enhance audio. This is a simplified method to show the outline of the process in this article. I’m not an audio engineer, although I have some experience in the area. So if you know it better, feel free to fine-tune it for yourself even more, or contact me and recommend improvements!&lt;/p&gt;
&lt;p&gt;I’m showing an example here with a very rough input, one that you’d just reject in real life as it would be useless due to its quality. But it is an excellent example to show the different steps of the enhancing process and to see what can be done to it!&lt;/p&gt;
&lt;p&gt;The following steps are built upon each other, and we’ll reach the complete command at the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg//#putting-it-all-together&quot;&gt;end&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Don’t forget that these settings are specific to this voice recording. Sadly this can not be generalized too much.&lt;/p&gt;
&lt;h3 id=&quot;gate&quot;&gt;Gate&lt;/h3&gt;
&lt;p&gt;Let’s start with the &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#agate&quot;&gt;gate&lt;/a&gt; filter!&lt;/p&gt;
&lt;p&gt;A gate is like a switch that opens only if the signal is stronger than the threshold. So if the signal level is lower than the threshold, it cuts to complete silence. Although you might soften or delay this cut with the &lt;em&gt;knee&lt;/em&gt;, &lt;em&gt;attack&lt;/em&gt;, and &lt;em&gt;release&lt;/em&gt; arguments.&lt;/p&gt;
&lt;p&gt;We’ll use this filter as a basic noise reduction method now! This helps us remove the noise between words and sentences by cutting it to silence. It doesn’t remove noise in any other way, e.g. it doesn’t touch the static on the voice itself.&lt;/p&gt;
&lt;p&gt;Check this out!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;voice_recording.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;agate=threshold=0.01:attack=80:release=840:makeup=1:ratio=3:knee=8&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gate.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s hear it: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-5-gate.wav&quot;&gt;gate.wav&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And let’s see it:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 655px) 655px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;655&quot; height=&quot;599&quot; src=&quot;https://img.ly/_astro/img-31-a_compression_result_ZdNo4g.webp&quot; srcset=&quot;/_astro/img-31-a_compression_result_1rLY1L.webp 640w, /_astro/img-31-a_compression_result_ZdNo4g.webp 655w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the “silent” parts were attenuated heavily, while the above-the-threshold parts remained similar. Those parts were still affected by the knee, attack, and release arguments determining how hard (knee) and quick (attack/release) the cut is.&lt;/p&gt;
&lt;p&gt;I’ve left a quite high release timeout here to avoid sudden dips in the amplitude.&lt;/p&gt;
&lt;p&gt;This is where we are right now:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1920px) 1920px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1920&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/img-32-gate_2cOAMH.webp&quot; srcset=&quot;/_astro/img-32-gate_ZqA69z.webp 640w, /_astro/img-32-gate_Z2a2c7C.webp 750w, /_astro/img-32-gate_Z2gGRxK.webp 828w, /_astro/img-32-gate_Z1iqObf.webp 1080w, /_astro/img-32-gate_Z1UP9EM.webp 1280w, /_astro/img-32-gate_Z1j6JuW.webp 1668w, /_astro/img-32-gate_2cOAMH.webp 1920w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The silent parts are more silent than before, but still, the amplitude range or the dynamic range is quite high. You must change your volume levels to hear everything and void blowing your speakers/brain out.&lt;/p&gt;
&lt;h3 id=&quot;equalization&quot;&gt;Equalization&lt;/h3&gt;
&lt;p&gt;Before fixing that, let’s do a bit more housekeeping. Let’s do some equalization and frequency filtering!&lt;/p&gt;
&lt;p&gt;We’ll use these filters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#highpass&quot;&gt;highpass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#lowpass&quot;&gt;lowpass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#anequalizer&quot;&gt;anequalizer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;gate.wav&lt;/span&gt;&lt;span&gt;  \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;highpass=f=100:width_type=q:width=0.5 , lowpass=f=10000 , anequalizer=c0 f=250 w=100 g=2 t=1|c0 f=700 w=500 g=-5 t=1|c0 f=2000 w=1000 g=2 t=1&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gate_eq.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s hear it: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-5-gate.wav&quot;&gt;gate_eq.wav&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This command gradually attenuates frequencies below 100hz, as there are not much valuable content in there, but it can really lower the clarity of the speech.&lt;/p&gt;
&lt;p&gt;Then we do the same, but for frequencies above 10 kHz. This is mostly needed because we have a lot of high-frequency noise, so this is a workaround for those. Also, a male voice is generally deeper than a woman’s, so you might want to pay attention to how low you can put the bar.&lt;/p&gt;
&lt;p&gt;Then comes anequalizer, which has a crazy an exceptional way of setting its arguments:&lt;/p&gt;
&lt;p&gt;This: &lt;code&gt;anequalizer=c0 f=250 w=100 g=2 t=1|c0 f=700 w=500 g=-5 t=1|c0 f=2000 w=1000 g=2 t=1&lt;/code&gt; means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;at 250hz with a width of 100hz boost by 2 db, with Chebyshev type 1 filter on channel 0.&lt;/li&gt;
&lt;li&gt;at 700hz with a width of 500hz attenuate by 5 db, with Chebyshev type 1 filter on channel 0.&lt;/li&gt;
&lt;li&gt;at 2000hz with a width of 1000hz attenuate by 2 db, with Chebyshev type 1 filter on channel 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I agree. You might have used a friendlier equalizer in your life than this one:)&lt;/p&gt;
&lt;p&gt;Those values are based on experimentation and common recommendations for voice. Feel free to tune it for your own needs!&lt;/p&gt;
&lt;p&gt;Let’s compare the frequency plots before and after:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1463px) 1463px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1463&quot; height=&quot;386&quot; src=&quot;https://img.ly/_astro/img-33-a_eq_Z2gUDKL.webp&quot; srcset=&quot;/_astro/img-33-a_eq_Z1V1zyn.webp 640w, /_astro/img-33-a_eq_1bHq3R.webp 750w, /_astro/img-33-a_eq_Z15FmFs.webp 828w, /_astro/img-33-a_eq_Z1dDKHz.webp 1080w, /_astro/img-33-a_eq_rII8h.webp 1280w, /_astro/img-33-a_eq_Z2gUDKL.webp 1463w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Tip: To see the frequency plot in Audacity, open a file, select all, and choose Analyze → Plot spectrum!&lt;/p&gt;
&lt;h3 id=&quot;compression&quot;&gt;Compression&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#acompressor&quot;&gt;compressor&lt;/a&gt; filter applies &lt;a href=&quot;https://en.wikipedia.org/wiki/Dynamic_range_compression&quot;&gt;dynamic range compression&lt;/a&gt; on the incoming audio data. To simplify this, the compressor varies the attenuation based on the incoming signal level. Basically, when you watch a badly mastered movie, this is what you are doing. When it is way too loud in some action scene, you reach for the remote control or mouse to lower the volume, but in the next moment, you will not hear what your heroes are saying, so you increase it back again.&lt;/p&gt;
&lt;p&gt;Dynamic range compression roughly does the same. You may set it up in a way so that it would attenuate louder parts, therefore keeping the overall volume range relatively small.&lt;/p&gt;
&lt;p&gt;It often happens that performers on the stage use a high dynamic range. Many performers will shout at one moment and then whisper in the next to increase drama or keep the attention. If you want to avoid manually adjusting the volume in real-time (while blowing off your speakers and pulling your hair out), then a compressor will save you in these situations!&lt;/p&gt;
&lt;p&gt;This is why our example audio consists of different speaking strengths, so that we could see the dramatic effect of this filter.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;gate_eq.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;acompressor=level_in=6:threshold=0.025:ratio=20:makeup=6&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gate_eq_comp.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s hear it: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-1-gate-eq-comp.wav&quot;&gt;gate_eq_comp.wav&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And let’s compare the result of this with the original waveform!&lt;/p&gt;
&lt;p&gt;Original:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1920px) 1920px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1920&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/img-34-voice_recording_Z29Di0o.webp&quot; srcset=&quot;/_astro/img-34-voice_recording_Z20VCWo.webp 640w, /_astro/img-34-voice_recording_22l4K9.webp 750w, /_astro/img-34-voice_recording_Z1gxOkv.webp 828w, /_astro/img-34-voice_recording_27WsEH.webp 1080w, /_astro/img-34-voice_recording_1sEU8h.webp 1280w, /_astro/img-34-voice_recording_Z1KEqTA.webp 1668w, /_astro/img-34-voice_recording_Z29Di0o.webp 1920w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1920px) 1920px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1920&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/img-35-gate_eq_comp_Z2mmEkT.webp&quot; srcset=&quot;/_astro/img-35-gate_eq_comp_1tg7KR.webp 640w, /_astro/img-35-gate_eq_comp_2vyYBl.webp 750w, /_astro/img-35-gate_eq_comp_1xRkJE.webp 828w, /_astro/img-35-gate_eq_comp_tpybk.webp 1080w, /_astro/img-35-gate_eq_comp_1DreDv.webp 1280w, /_astro/img-35-gate_eq_comp_xv3Vx.webp 1668w, /_astro/img-35-gate_eq_comp_Z2mmEkT.webp 1920w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Quite dramatic, isn’t it?:)&lt;/p&gt;
&lt;p&gt;Let’s analyze this: &lt;code&gt;acompressor=level_in=6:threshold=0.025:ratio=20:makeup=6&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;First, &lt;code&gt;level_in=6&lt;/code&gt; sets the input gain. It is 1 by default, but since our example, audio is extremely silent at places, we boost up the whole thing before processing.&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;threshold=0.025&lt;/code&gt; defines that everything above 0.025 should be attenuated.&lt;/p&gt;
&lt;p&gt;Based on the image below, I’ve decided to cut at this point, as this is above most of the whispering, which cuts hard pops and “s”-es even in the “whisper zone”.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1916px) 1916px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1916&quot; height=&quot;455&quot; src=&quot;https://img.ly/_astro/img-36-eq_1D0n3V.webp&quot; srcset=&quot;/_astro/img-36-eq_25pidd.webp 640w, /_astro/img-36-eq_Z2b9iqi.webp 750w, /_astro/img-36-eq_1vTL83.webp 828w, /_astro/img-36-eq_WykcW.webp 1080w, /_astro/img-36-eq_UxPb5.webp 1280w, /_astro/img-36-eq_Z284TW8.webp 1668w, /_astro/img-36-eq_1D0n3V.webp 1916w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;ratio=20&lt;/code&gt; means 1:20 in attenuation ratio, which means that if the level rises 20 dB above the threshold, it will be only 1 dB above the line after the attenuation. Basically, this is a very strong compression ratio, it is almost a &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#alimiter&quot;&gt;limiter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This far, we boosted the signal, then turned down everything that was above our “whisper line” with a quite strong ratio, and now, everything is basically at the whisper level, even the parts that are shouting.&lt;/p&gt;
&lt;p&gt;Finally, with the &lt;code&gt;makeup=6&lt;/code&gt; we just bring back everything to the level where the “normal” parts were before.&lt;/p&gt;
&lt;p&gt;Let’s take a look back now, to understand why we used the gate and did the equalization before the compressor.&lt;/p&gt;
&lt;p&gt;Generally, you want to remove unneeded parts and frequencies before compression, as the compressor will likely increase those too! So by removing most of the noise in the gaps, we avoided &lt;code&gt;level_in=6&lt;/code&gt; to increase them too! And the same goes for the high- and lowpass filtering.&lt;/p&gt;
&lt;h3 id=&quot;changing-the-volume&quot;&gt;Changing the volume&lt;/h3&gt;
&lt;p&gt;Now, if we want to make the result a bit louder, we could increase the previous step’s &lt;code&gt;makeup&lt;/code&gt; argument, or leverage the volume &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#volume&quot;&gt;filter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While we are at it, let’s cut the first 4 seconds too with &lt;code&gt;-ss 4&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-ss &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; gate_eq_comp.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;volume=1.1&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gate_eq_volume_comp.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s hear it: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-2-gate_eq_volume_comp.wav&quot;&gt;gate_eq_volume_comp.wav&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;lets-make-audio-gate-again&quot;&gt;Let’s make audio gate again&lt;/h3&gt;
&lt;p&gt;Excuse me for that title:)&lt;/p&gt;
&lt;p&gt;So as I’ve described earlier, compression can amplify the noises, so you might want to run the result through a gate again:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;gate_eq_volume_comp.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;agate=threshold=0.1:attack=50:release=50:ratio=1.5:knee=4&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gate_eq_volume_comp_gate.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s hear it: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-3-gate_eq_volume_comp_gate.wav&quot;&gt;gate_eq_volume_comp_gate.wav&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this case, I’ve used a softer gate, with &lt;code&gt;ratio=1.5&lt;/code&gt;. Because of this, I could use shorter attack and release delays too, as the attenuation is not that strong, it isn’t causing hard dips in the audio.&lt;/p&gt;
&lt;h3 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h3&gt;
&lt;p&gt;Just a single command could have achieved all the steps above:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;shell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpeg&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-i &lt;/span&gt;&lt;span&gt;voice_recording.wav&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;-filter_complex &lt;/span&gt;&lt;span&gt;&quot;agate=threshold=0.01:attack=80:release=840:makeup=1:ratio=3:knee=8 , highpass=f=100:width_type=q:width=0.5 , lowpass=f=10000 , anequalizer=c0 f=250 w=100 g=2 t=1|c0 f=700 w=500 g=-5 t=1|c0 f=2000 w=1000 g=2 t=1 , acompressor=level_in=6:threshold=0.025:ratio=20:makeup=6 , volume=1.1 , agate=threshold=0.1:attack=50:release=50:ratio=1.5:knee=4&quot;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gate_eq_volume_comp_gate_together.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I just copy-pasted all the filters right after each other with a comma between them.&lt;/p&gt;
&lt;p&gt;Isn’t it beautiful? Yeah, it isn’t, but it is very practical:)&lt;/p&gt;
&lt;p&gt;For the last time, check out the difference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Original: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-4-voice_recording.wav&quot;&gt;voice_recording.wav&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Final: &lt;a href=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/ffmpeg-audio/audio-3-gate_eq_volume_comp_gate.wav&quot;&gt;gate_eq_volume_comp_gate.wav&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It has less noise, more clear voice, and a small volume range. Therefore it is easy on your ears!&lt;/p&gt;
&lt;h3 id=&quot;what-else-1&quot;&gt;What else?&lt;/h3&gt;
&lt;p&gt;You might want to check out a few more &lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#toc-Audio-Filters&quot;&gt;filters&lt;/a&gt; that I didn’t cover here.&lt;/p&gt;
&lt;p&gt;Here are just a few interesting ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#adeclick&quot;&gt;adeclick&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#adeclip&quot;&gt;adeclip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#aecho&quot;&gt;aecho&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#deesser&quot;&gt;deesser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html#alimiter&quot;&gt;alimiter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;
&lt;p&gt;For your convenience, let me list the most important documentations that might be important for you! Most of these were already linked many times in this article.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg.html&quot;&gt;FFmpeg main documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki&quot;&gt;FFmpeg WIKI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/CompilationGuide&quot;&gt;FFmpeg compilation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-filters.html&quot;&gt;FFmpeg filters documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ffmpeg.org/ffmpeg-formats.html&quot;&gt;FFmpeg formats documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.264&quot;&gt;H.264 Video Encoding Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trac.ffmpeg.org/wiki/Encode/H.265&quot;&gt;H.265 Video Encoding Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you got this far from top to bottom, then you are a true hero! I hope you enjoyed this, and I also hope that it inspired you to create something awesome with FFmpeg! Please consider &lt;a href=&quot;https://ffmpeg.org/donations.html&quot;&gt;donating&lt;/a&gt; to FFmpeg – they are fantastic.&lt;/p&gt;
&lt;p&gt;If you’re looking to take your creative projects to the next level, check out our products - &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;Creative Editor SDK&lt;/a&gt;, &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;Video Editor SDK&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;Photo Editor SDK&lt;/a&gt;. These versatile tools empower you to bring your vision to life, whether you’re editing images, crafting stunning videos, or unleashing your artistic talents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! Let us know what you think on&lt;/strong&gt; &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://img.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Csaba</dc:creator><media:content url="https://blog.img.ly/2022/11/FFmpeg_ultimate_guide.png" medium="image"/><category>FFmpeg</category><category>Video App</category><category>Audio</category><category>Tech</category><category>Tutorial</category></item><item><title>How to Apply Filters and Effects to an Image in Flutter</title><link>https://img.ly/blog/how-to-add-stickers-and-overlays-to-a-video-in-flutter-test/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-stickers-and-overlays-to-a-video-in-flutter-test/</guid><description>Learn how to manipulate images in Flutter with filters and effects.</description><pubDate>Tue, 15 Nov 2022 10:51:25 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Having previously discussed &lt;a href=&quot;https://img.ly/blog/how-to-resize-images-in-flutter/&quot;&gt;how Flutter deals with resizing&lt;/a&gt; and various overlays, in this tutorial, we will talk about a more popular type of image manipulation; applying filters and effects. At least since the ubiquity of social media platforms such as Instagram, Snapchat or more recently TikTok, applying various filters and effects has become an expected feature of any application dealing with image manipulation.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/flutter-apply-filters-to-video.mp4&quot; controls muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Applying Filters and Effects in Flutter demo application&lt;/p&gt;
&lt;p&gt;In this tutorial, we go through the process of applying filters and effects to images in Flutter. Like the example given in the video, we will examine the options for developing an image editor and the effects available in the Flutter framework. The &lt;a href=&quot;https://github.com/nataliakzm/Applying_Filters_and_Effects_to_Images_Flutter&quot;&gt;Git repository&lt;/a&gt; supporting this article can be cloned with the command provided below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; git@github.com:nataliakzm/Applying_Filters_and_Effects_to_Images_Flutter.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;flutter-imagefiltered-widget&quot;&gt;Flutter ImageFiltered Widget&lt;/h3&gt;
&lt;p&gt;One of the leading solutions proposed by the Flutter team on this issue was the &lt;code&gt;ImageFiltered&lt;/code&gt; &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ImageFiltered-class.html&quot;&gt;class&lt;/a&gt;. Overall, the widget deals with major image manipulations (from blurring to matrix transformation, rotation etc.), transforming and rearranging image pixels. To create an &lt;code&gt;ImageFilter&lt;/code&gt;, you need to follow a particular structure and apply the &lt;code&gt;ImageFilter&lt;/code&gt; class to its child elements:&lt;/p&gt;
&lt;p&gt;Moreover, in order to start working with &lt;code&gt;ImageFiltered&lt;/code&gt; within your Flutter application, first, don’t forget to import this library on the top of your .dart file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ImageFiltered&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  super&lt;/span&gt;&lt;span&gt;.key,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  required&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.imageFilter,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  super&lt;/span&gt;&lt;span&gt;.child,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  this&lt;/span&gt;&lt;span&gt;.enabled &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; assert&lt;/span&gt;&lt;span&gt;(imageFilter &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;dart:ui&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, &lt;code&gt;ImageFiltered&lt;/code&gt; offers several filter choices for image processing, depending on the specification of your app or visual asset.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;So, for example, with the help of &lt;code&gt;ImageFilter.blur&lt;/code&gt; or the so-called &lt;strong&gt;&lt;em&gt;Gaussian blur&lt;/em&gt;&lt;/strong&gt;, the pixels of the image can be blurred, which can, for instance, be used to create a graphic element that can serve as a background image.&lt;/li&gt;
&lt;li&gt;On the other hand, two other filters such as &lt;code&gt;ImageFilter.dilate&lt;/code&gt; and &lt;code&gt;ImageFilter.erode&lt;/code&gt; affect the pixels’ value by either enlarging them to the max significance within the specified range along the x and y axes or by shrinking pixels to their minimum values;&lt;/li&gt;
&lt;li&gt;You can also easily rotate, scale and change your asset in other ways by transforming its matrix with &lt;code&gt;ImageFilter.matrix&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Finally, &lt;code&gt;ImageFilter.compose&lt;/code&gt; is handy for merging two filters and combining their effects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, the purpose of each effect seems much clearer, but we still miss some crucial steps to implement these filters in your Flutter application. Let’s see some real examples:&lt;/p&gt;
&lt;h3 id=&quot;imagefilterblur-constructor&quot;&gt;ImageFilter.blur constructor&lt;/h3&gt;
&lt;p&gt;To apply a blurring effect on your image, you have to call an ****&lt;code&gt;ImageFiltered.blur()&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt; and adjust the values of &lt;code&gt;sigmaX&lt;/code&gt; and &lt;code&gt;sigmaY&lt;/code&gt; arguments for blurring the image. Note that the asset must be called as a child element of the filter to achieve the desired effect.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Container&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Center&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;				child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Column&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;						mainAxisAlignment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; MainAxisAlignment&lt;/span&gt;&lt;span&gt;.center,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;						children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // Blur an Image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ImageFiltered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;					imageFilter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ImageFilter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;blur&lt;/span&gt;&lt;span&gt;(sigmaY&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, sigmaX&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;					child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;assets/logo.png&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;imagefiltermatrix-constructor&quot;&gt;ImageFilter.matrix constructor&lt;/h3&gt;
&lt;p&gt;The situation is slightly different with &lt;code&gt;ImageFilter.matrix&lt;/code&gt; filter since the effect can be initialized by one of two methods – &lt;code&gt;Float64List.fromList&lt;/code&gt; or &lt;code&gt;Matrix4.rotationZ&lt;/code&gt; . Thus, for the first method, the user needs to directly provide the matrix as a &lt;code&gt;Float64List&lt;/code&gt; as it is done below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Matrix Transformation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ImageFiltered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		imageFilter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ImageFilter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;matrix&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Float64List&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromList&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;			1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;			0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;			0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;			0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ])),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;assets/logo.png&quot;&lt;/span&gt;&lt;span&gt;, scale&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 3.5&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;// Note that scale is optional and depends on the image size&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the &lt;code&gt;Matrix4&lt;/code&gt; class simply provides rotation in Z directions:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Matrix4 Rotation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ImageFiltered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		imageFilter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ImageFilter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;matrix&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Matrix4&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rotationZ&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;).storage),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;assets/logo.png&quot;&lt;/span&gt;&lt;span&gt;, scale&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 6&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;//Note that scale is optional and depends on the image size&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;colorfiltered-effects&quot;&gt;ColorFiltered Effects&lt;/h2&gt;
&lt;p&gt;Alternatively, Flutter has several options to integrate some filters without calling any extra library. One such example is the &lt;code&gt;ColorFilter&lt;/code&gt; function executed within the widget of the same name. &lt;code&gt;ColorFiltered&lt;/code&gt; processes two colors and outputs only one of them, which is eventually displayed at the top of the layer.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt; // Black &amp;#x26; White Effect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ColorFiltered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		colorFilter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; ColorFilter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Colors&lt;/span&gt;&lt;span&gt;.grey, &lt;/span&gt;&lt;span&gt;BlendMode&lt;/span&gt;&lt;span&gt;.saturation),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;assets/logo.png&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Users can also engage with several construction methods: from directly calling the sRGB gamma curve with &lt;code&gt;ColorFilter.linearToSrgbGamma()&lt;/code&gt; widget or its inverse prototype with &lt;code&gt;ColorFilter.srgbToLinearGamma()&lt;/code&gt; to transforming the color by a 5x5 matrix within &lt;code&gt;ColorFilter.matrix()&lt;/code&gt; function or simply applying the blend mode with a given color specified in &lt;code&gt;ColorFilter.mode()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;In conclusion, Flutter’s image processing capabilities are limited by a small variety of effects and often depend on the developer’s ability to handle complex concepts like matrix or sRGB curves. In this article, we reviewed some of the main image processing techniques proposed by the Flutter team and examined how to integrate them into photo editing apps. However, it is not always possible to allocate several days to calculate the correct matrix for transforming just one image when it comes to processing a large number of graphic assets. In such cases, you may want to think about using the &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; for your next Flutter app.  Just like in the interactive example below, PhotoEditor SDK has many different effects and filters that will suit any of your requests.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/flutter-apply-filters-to-video2.mp4&quot; controls muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Applying Filters via PhotoEditor SDK&lt;/p&gt;
&lt;p&gt;To learn more, check out our article on &lt;a href=&quot;https://img.ly/docs/pesdk/flutter/getting-started/integration/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditor SDK and how to set it up in your app&lt;/a&gt; and &lt;a href=&quot;https://img.ly/docs/pesdk/guides/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;official PE.SDK guides&lt;/a&gt;. You can also follow our guide on how to integrate &lt;a href=&quot;https://img.ly/blog/a-modern-video-editor-sdk-for-your-flutter-app/&quot;&gt;video editor for Flutter&lt;/a&gt; into your app. For any of your questions that might occur in the process, you can contact our &lt;a href=&quot;https://img.ly/support?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;Support&lt;/a&gt;, who will be glad to help you.&lt;/p&gt;</content:encoded><dc:creator>Natalia</dc:creator><media:content url="https://blog.img.ly/2022/11/apply-filters-and-effects-to-an-image-in-Flutter.png" medium="image"/><category>How-To</category><category>Video Editing</category><category>Web Development</category><category>Flutter</category><category>Tech</category><category>Tutorial</category></item><item><title>How to Draw on Images in React Native</title><link>https://img.ly/blog/how-to-draw-on-images-in-react-native/</link><guid isPermaLink="true">https://img.ly/blog/how-to-draw-on-images-in-react-native/</guid><description>Learn how to draw on images in your React Native app – a great tool for annotations or fun image editing!</description><pubDate>Thu, 20 Oct 2022 13:09:38 GMT</pubDate><content:encoded>&lt;p&gt;It has become a commonplace requirement for many applications to manipulate images in one way or another before users are ready to share them. Therefore, it is crucial to foresee end-user needs while working on an app involving any interaction with graphical content. Image annotation can be necessary in various cases, such as simple inspection apps, bug reporting software, or machine learning applications relying on manual user input.&lt;/p&gt;
&lt;p&gt;In this article, we are shedding light on how to draw on images in React Native with the help of a simple HTML5 &lt;canvas&gt; element. We can summarize the drawing process with this checklist:&lt;/canvas&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enable the characteristics of the &lt;canvas&gt; element by defining the height and width of your drawing area;&lt;/canvas&gt;&lt;/li&gt;
&lt;li&gt;Set the thickness and color of the brush instrument;&lt;/li&gt;
&lt;li&gt;Define at what moment your brush needs to be activated and when the drawing process will finish;&lt;/li&gt;
&lt;li&gt;Start drawing!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s first clear up the purpose of &lt;canvas&gt;.&lt;/canvas&gt;&lt;/p&gt;
&lt;h3 id=&quot;canvas-integration-for-images&quot;&gt;&lt;strong&gt;Canvas Integration for Images&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; is an HTML component commonly used for creating and manipulating graphics, animation, and other visualization features. This element provides JavaScript APIs enabling image overlays and handling user input to make drawing possible. Moreover, while we have several methods for image manipulation with &lt;canvas&gt; (e.g., &lt;em&gt;boxes, circles, adding text, and other images&lt;/em&gt;), the most crucial attributes are always width and height.&lt;/canvas&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;! -- HTML example of using canvas: --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;canvas id=&quot;myCanvas&quot; width=&quot;200&quot; height=&quot;100&quot;&gt;&amp;#x3C;/canvas&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might wonder why you cannot easily substitute &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; with some lines of HTML and CSS in your code. The answer lies on the surface: &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; creates a single flattened graphic rather than multiple components lying on top of each other (i.e., typically the output of HTML and CSS).&lt;/p&gt;
&lt;p&gt;Now, let’s see how to implement this powerful element in your React Native application:&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/img-ly-how-to-draw-on-images-in-rn-forked-hewo07?fontsize=14&amp;#x26;hidenavigation=1&amp;#x26;theme=dark&quot; title=&quot;IMG.LY_How_to_Draw_on_Images_in_RN (forked)&quot; allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot; sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;&gt;&lt;/iframe&gt;
&lt;h3 id=&quot;drawing-with-canvas&quot;&gt;&lt;strong&gt;Drawing with Canvas&lt;/strong&gt;&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React, { useEffect } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// CanvasContext here is used for drawing onto the canvas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useCanvas } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./CanvasContext&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; Canvas&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;canvasRef&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;prepareCanvas&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;startDrawing&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;finishDrawing&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    useCanvas&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    prepareCanvas&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, []);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;canvas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      onMouseDown&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{startDrawing}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      onMouseUp&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{finishDrawing}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      onMouseMove&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{draw}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{canvasRef}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We start by enabling the characteristics of our &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; container, which are meant to render the &lt;code&gt;CanvasContext&lt;/code&gt; component presented below.&lt;/p&gt;
&lt;p&gt;The size of our future canvas is determined with &lt;code&gt;prepareCanvas&lt;/code&gt;, which includes both the height (&lt;code&gt;canvas.height&lt;/code&gt;) and width (&lt;code&gt;canvas.width&lt;/code&gt;) of the HTML element. For the drawing process itself, it is necessary to assign not only the thickness and color of the brush instrument (&lt;code&gt;context&lt;/code&gt;) but also take into account its activation (&lt;code&gt;startDrawing&lt;/code&gt; with &lt;code&gt;onMouseDown&lt;/code&gt;) and how the user can finish a stroke (&lt;code&gt;finishDrawing&lt;/code&gt; with &lt;code&gt;onMouseUp&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React, { useContext, useRef, useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// enabling drawing on the blank canvas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; CanvasContext&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;createContext&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; CanvasProvider&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;children&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;isDrawing&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsDrawing&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; canvasRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; contextRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // defining width &amp;#x26; height of the canvas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; prepareCanvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; canvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvasRef.current;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    canvas.width &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; window.innerWidth &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    canvas.height &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; window.innerHeight &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    canvas.style.width &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;innerWidth&lt;/span&gt;&lt;span&gt;}px`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    canvas.style.height &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;innerHeight&lt;/span&gt;&lt;span&gt;}px`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // defining the thickness and colour of our brush&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvas.&lt;/span&gt;&lt;span&gt;getContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;2d&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    context.&lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    context.lineCap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;round&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    context.strokeStyle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;black&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    context.lineWidth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contextRef.current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; context;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; startDrawing&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;nativeEvent&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;offsetX&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;offsetY&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nativeEvent;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contextRef.current.&lt;/span&gt;&lt;span&gt;beginPath&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contextRef.current.&lt;/span&gt;&lt;span&gt;moveTo&lt;/span&gt;&lt;span&gt;(offsetX, offsetY);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    setIsDrawing&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; finishDrawing&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contextRef.current.&lt;/span&gt;&lt;span&gt;closePath&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    setIsDrawing&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; draw&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;nativeEvent&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;isDrawing) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;offsetX&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;offsetY&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nativeEvent;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contextRef.current.&lt;/span&gt;&lt;span&gt;lineTo&lt;/span&gt;&lt;span&gt;(offsetX, offsetY);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    contextRef.current.&lt;/span&gt;&lt;span&gt;stroke&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Once the canvas is cleared it return to the default colour&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; clearCanvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; canvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvasRef.current;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvas.&lt;/span&gt;&lt;span&gt;getContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;2d&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    context.fillStyle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;white&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    context.&lt;/span&gt;&lt;span&gt;fillRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, canvas.width, canvas.height);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;CanvasContext.Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        canvasRef,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        contextRef,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        prepareCanvas,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        startDrawing,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        finishDrawing,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        clearCanvas,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        draw,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      {children}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;CanvasContext.Provider&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; useCanvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; useContext&lt;/span&gt;&lt;span&gt;(CanvasContext);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;drawbacks-of-the-canvas-method&quot;&gt;Drawbacks of the Canvas Method&lt;/h3&gt;
&lt;p&gt;Despite the canvas component offering powerful features, it is also quite demanding to use in its original form. For example, it may be problematic to adjust the properties of the brush to ensure that you can draw with smooth movements. Thus, implementing any solution for this issue would further increase the coding time and reduce the compatibility between different frameworks. Also, separately defining canvas and brush properties overcomplicates the code and requires advanced sources for successful integration into your React Native app. Although some temporary solutions aim to facilitate this process (such as &lt;a href=&quot;https://github.com/iddan/react-native-canvas&quot;&gt;react-native-image-draw&lt;/a&gt; or &lt;a href=&quot;https://github.com/terrylinla/react-native-sketch-canvas&quot;&gt;react-native-sketch-draw&lt;/a&gt;), none provides a permanent solution with clear documentation and guidance.&lt;/p&gt;
&lt;p&gt;If you wonder about a magic door to avoid this coding nightmare, you can always choose a commercial, all-in-one solution like &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;. Implementing this SDK only takes a few lines of code, saves time and expenses in building an app, and you can expect great support from the developers. This way, you can skirt the hassle of coding an editor and instead focus on creating a great product.&lt;/p&gt;
&lt;h3 id=&quot;photoeditor-sdk-integration-for-drawing-on-your-images&quot;&gt;PhotoEditor SDK Integration for Drawing on Your Images&lt;/h3&gt;
&lt;p&gt;To get started integrating the PhotoEditor SDK in your React Native app, refer to &lt;a href=&quot;https://img.ly/docs/pesdk/web/guides/react-js/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this guide&lt;/a&gt; from &lt;a href=&quot;https://img.ly/docs/pesdk/guides/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;the official documentation&lt;/a&gt;. You can then ensure comfortable image interaction for your users with the optimized &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/brush/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;brush tool&lt;/a&gt;&lt;em&gt;,&lt;/em&gt; as in the example below.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/pe-sdk-draw-image-react.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This article aimed to examine image editing instruments suitable for React Native framework. The HTML &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; element could have big potential as it provides fundamental tools for creating graphics or animation. However, its application is time-consuming and requires comprehensive knowledge in the field. Therefore, if you want to guarantee a smooth and easy integration of image drawing tools for your app, consider using advanced software like PhotoEditor SDK.&lt;/p&gt;</content:encoded><dc:creator>Natalia</dc:creator><media:content url="https://blog.img.ly/2022/10/photoeditor-sdk-ract-draw-on-images.png" medium="image"/><category>How-To</category><category>React</category><category>Web Development</category><category>Web Application</category><category>Image Editing</category><category>HTML5</category><category>React Native</category><category>Tech</category><category>Tutorial</category></item><item><title>How To Build a Video Player in JavaScript</title><link>https://img.ly/blog/how-to-build-video-player-in-javascript/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-video-player-in-javascript/</guid><description>Create your own JavaScript video player using simple methods for neat results!</description><pubDate>Tue, 27 Sep 2022 17:02:49 GMT</pubDate><content:encoded>&lt;p&gt;Probably a decade ago, it was impossible to play video or audio inside your browser without any third-party services such as Flash or Silverlight. You needed to install a plugin and only play your media while using it, so as you can see, it was very uncomfortable, with low speed and high delays. Nowadays, we have JavaScript with the new version of HTML5. With these new technologies and tools, we can stream our video much quicker, easier, and without any latency. To do it, you will need only a simple &lt;video&gt; tag and give a link to your video stored on your computer. The simple attribute &lt;em&gt;controls&lt;/em&gt; will give you a default video player built into the browser. It’s elementary and doesn’t have many features, so if you want to stream a video on your website in a more professional way using your video player, you’ll need to use JavaScript. And we’ll teach you how to do it in this article!&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;By the end of this guide, you’ll have something similar to this, so if you’re excited, keep reading and follow this tutorial step-by-step!&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;qBYNxxa&quot; data-user=&quot;paulknulst&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/paulknulst/pen/qBYNxxa&quot;&gt;How to build a video player in Javascript&lt;/a&gt; by Paul Knulst (&lt;a href=&quot;https://codepen.io/paulknulst&quot;&gt;@paulknulst&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-project&quot;&gt;&lt;strong&gt;Setting Up the Project&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Assuming you are working with UNIX system (or have Git BASH on Windows) you can create all three files that are necessary to build a video player in JavaScript with this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;mkdir video-player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd video-player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;touch index.html script.js style.css&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add a simple video player to our application, we have to add the following code to our &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;!DOCTYPE html&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;html lang=&quot;en&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;head&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;meta charset=&quot;UTF-8&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;title&gt;How to build a video player in Javascript&amp;#x3C;/title&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;link rel=&quot;stylesheet&quot; href=&quot;style.css&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/head&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;body&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;div class=&quot;player&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;video class=&quot;video&quot; controls&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                &amp;#x3C;source&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    src=&quot;&amp;#x3C;https://ftp.f1nalboss.de/data/imgly/videoplayer/testvideo.mp4&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    type=&quot;video/mp4&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                &amp;#x3C;source&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    src=&quot;&amp;#x3C;https://ftp.f1nalboss.de/data/imgly/videoplayer/testvideo.mp4&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    type=&quot;video/webm&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                &amp;#x3C;p&gt;No HTML5 video supported&amp;#x3C;/p&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;/video&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/div&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;script src=&quot;script.js&quot;&gt;&amp;#x3C;/script&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/body&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/html&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within the above code, the &lt;code&gt;&amp;#x3C;video&gt;&lt;/code&gt; element uses a remote video from my FTP. You can either use my default video or add any video from your local computer by adjusting the &lt;code&gt;src&lt;/code&gt; attribute. HTML5 specification supports three different video formats, and the snippet used multiple &lt;code&gt;&amp;#x3C;source&gt;&lt;/code&gt; tags to make the videos available in MP4 and WebM. Furthermore, the &lt;code&gt;&amp;#x3C;p&gt;&lt;/code&gt; tag is used to display pre-defined content to user agents that do not support the &lt;code&gt;video&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;The HTML5 &lt;code&gt;&amp;#x3C;video&gt;&lt;/code&gt; tag accepts several native attributes. For example, the &lt;code&gt;controls&lt;/code&gt; attribute displays the standard player controls when added or set to true. You can find out more about &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-controls&quot;&gt;all video attributes here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before continuing, you should apply all styles that are needed within this tutorial by populating your &lt;code&gt;style.css&lt;/code&gt; with all styles &lt;a href=&quot;https://codepen.io/paulknulst/pen/qBYNxxa&quot;&gt;from this CodePen&lt;/a&gt;. Save and open your &lt;code&gt;index.html&lt;/code&gt; and load it within the browser to see the embedded video player as seen below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;This is what the embedded video player should look like in your index.html file.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 805px) 805px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;805&quot; height=&quot;544&quot; src=&quot;https://img.ly/_astro/build-video-player-with-javascript_ZBugbA.webp&quot; srcset=&quot;/_astro/build-video-player-with-javascript_Z2soYbu.webp 640w, /_astro/build-video-player-with-javascript_ZnTDYb.webp 750w, /_astro/build-video-player-with-javascript_ZBugbA.webp 805w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;customize-the-video-player-with-javascript&quot;&gt;&lt;strong&gt;Customize the Video Player With JavaScript&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To customize the video player, you first have to remove the &lt;code&gt;controls&lt;/code&gt; attribute that displays &lt;code&gt;Play&lt;/code&gt;, &lt;code&gt;Pause&lt;/code&gt;, &lt;code&gt;Volume&lt;/code&gt;, etc. because you will implement your own custom controls within this tutorial. Now, check your browser, you will recognize that the controls are gone, and you cannot play the video anymore.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 815px) 815px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;815&quot; height=&quot;540&quot; src=&quot;https://img.ly/_astro/build-video-player-with-javascript_2_Z1Exl3w.webp&quot; srcset=&quot;/_astro/build-video-player-with-javascript_2_27jRL3.webp 640w, /_astro/build-video-player-with-javascript_2_Z2tYMIW.webp 750w, /_astro/build-video-player-with-javascript_2_Z1Exl3w.webp 815w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;add-play-and-pause&quot;&gt;&lt;strong&gt;Add Play and Pause&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;To enable play and pause the video, you have to add a new button to the &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;div class=&quot;controls&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            class=&quot;controls__btn playPauseBtn&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            title=&quot;Toggle Play&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ►&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/button&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterward, open your &lt;code&gt;script.js&lt;/code&gt; and enable functionality by adding this code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const videoContainer = document.querySelector(&quot;.video-container&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const playPauseBtn = document.querySelector(&quot;.playPauseBtn&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function togglePlay() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if (videoContainer.paused || videoContainer.ended) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videoContainer.play();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videoContainer.pause();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function updatePlayBtn() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  playPauseBtn.innerHTML = videoContainer.paused ? &quot;►&quot; : &quot;❚❚&quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;playPauseBtn.addEventListener(&quot;click&quot;, togglePlay);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoContainer.addEventListener(&quot;click&quot;, togglePlay);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoContainer.addEventListener(&quot;play&quot;, updatePlayBtn);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoContainer.addEventListener(&quot;pause&quot;, updatePlayBtn);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within this javascript code, first, the &lt;code&gt;video-container&lt;/code&gt; element and the &lt;code&gt;playPauseBtn&lt;/code&gt; is selected (Line 1 and 2). Then two functions are defined: &lt;code&gt;togglePlay()&lt;/code&gt; and &lt;code&gt;updatePlayBtn()&lt;/code&gt;. &lt;code&gt;togglePlay()&lt;/code&gt; is used to stop and start the video based on its actual state. &lt;code&gt;updatePlayBtn&lt;/code&gt; is used to switch between the Icon which is shown within the video player.&lt;/p&gt;
&lt;p&gt;In the last part of the snippet, a click event listener is added to the &lt;code&gt;playPauseBtn&lt;/code&gt; that executes the &lt;code&gt;togglePlay()&lt;/code&gt; function. Next, three click event listeners are added to the &lt;code&gt;videoContainer&lt;/code&gt; that executes &lt;code&gt;togglePlay()&lt;/code&gt; on mouse click and also executes &lt;code&gt;updatePlayBtn&lt;/code&gt; based on the video’s state.&lt;/p&gt;
&lt;p&gt;Now you can reload your &lt;code&gt;index.html&lt;/code&gt; and should be able to play and pause the video by either clicking the video or the button:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;You can now pause and play your video.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 803px) 803px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;803&quot; height=&quot;526&quot; src=&quot;https://img.ly/_astro/javascript-video-player_c0OvI.webp&quot; srcset=&quot;/_astro/javascript-video-player_Z2mALHv.webp 640w, /_astro/javascript-video-player_2aoUuJ.webp 750w, /_astro/javascript-video-player_c0OvI.webp 803w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;add-progress-bar&quot;&gt;Add Progress Bar&lt;/h3&gt;
&lt;p&gt;Next, a progress bar will be implemented to show the current timestamp of the video when played. First, add a &lt;code&gt;div&lt;/code&gt; tag to the &lt;code&gt;index.html&lt;/code&gt; which will act as the progress bar:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;div class=&quot;controls&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;div class=&quot;progress&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    	&amp;#x3C;div class=&quot;progress__filled&quot;&gt;&amp;#x3C;/div&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/div&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // ..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/div&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open the &lt;code&gt;script.js&lt;/code&gt; and add the following snippet:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const progress = document.querySelector(&quot;.progress&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const progressBar = document.querySelector(&quot;.progress__filled&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function handleProgress() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const progressPercentage = (videoContainer.currentTime / videoContainer.duration) * 100;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  progressBar.style.flexBasis = `${progressPercentage}%`;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function jump(e) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const position = (e.offsetX / progress.offsetWidth) * videoContainer.duration;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoContainer.currentTime = position;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoContainer.addEventListener(&quot;timeupdate&quot;, handleProgress);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;progress.addEventListener(&quot;click&quot;, jump);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let mousedown = false;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;progress.addEventListener(&quot;mousedown&quot;, () =&gt; (mousedown = true));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;progress.addEventListener(&quot;mousemove&quot;, (e) =&gt; mousedown &amp;#x26;&amp;#x26; jump(e));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;progress.addEventListener(&quot;mouseup&quot;, () =&gt; (mousedown = false));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this snippet, the &lt;code&gt;progress&lt;/code&gt; container and the &lt;code&gt;progress__filled&lt;/code&gt; element will be selected, and two functions will be added: &lt;code&gt;handleProgress()&lt;/code&gt; and &lt;code&gt;jump(e)&lt;/code&gt;. &lt;code&gt;handleProgress()&lt;/code&gt; will be responsible for updating the progress bar. The &lt;code&gt;jump(e)&lt;/code&gt; function is used to enable clicking on the progress bar to jump to the position within the video.&lt;/p&gt;
&lt;p&gt;The last part contains all event listeners that are needed for the progress bar. The &lt;code&gt;handleProgress()&lt;/code&gt; will be called on every &lt;code&gt;timeupdate&lt;/code&gt; event. Also, clicking anywhere on the progress bar will call the &lt;code&gt;jump(e)&lt;/code&gt; method and the video will jump to the position. Additionally, &lt;code&gt;mousedown&lt;/code&gt;, &lt;code&gt;mousemove&lt;/code&gt;, and &lt;code&gt;mouseup&lt;/code&gt; will be used to enable &lt;em&gt;sliding&lt;/em&gt; through the video while holding the mouse button down on the progress bar.&lt;/p&gt;
&lt;h2 id=&quot;closing-notes&quot;&gt;&lt;strong&gt;Closing Notes&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Congratulations! If you followed the tutorial, you learned how to implement your own video player and add custom controls using JavaScript. Now, you can use &lt;a href=&quot;https://codepen.io/paulknulst/pen/qBYNxxa&quot;&gt;my CodePen&lt;/a&gt; and start implementing more controls like &lt;strong&gt;volume control&lt;/strong&gt;, &lt;strong&gt;keyboard shortcuts&lt;/strong&gt;, or &lt;strong&gt;skip controls&lt;/strong&gt; to build your own customized video player.&lt;/p&gt;
&lt;p&gt;If your app goes beyond merely displaying video, and you want to allow your users to also edit video or create video based templates in the browser, check out our &lt;a href=&quot;https://img.ly/use-cases/video-for-web&quot;&gt;Video Editor for Web&lt;/a&gt;!&lt;/p&gt;</content:encoded><dc:creator>Paul</dc:creator><media:content url="https://blog.img.ly/2022/09/video-player-javascript_tutorial.png" medium="image"/><category>How-To</category><category>Video Editor</category><category>Video Player</category><category>JavaScript</category><category>Web Development</category><category>Web Application</category><category>Tutorial</category></item><item><title>How To Resize Images in Flutter</title><link>https://img.ly/blog/how-to-resize-images-in-flutter/</link><guid isPermaLink="true">https://img.ly/blog/how-to-resize-images-in-flutter/</guid><description>Keep it light: resize your application images in Flutter. </description><pubDate>Tue, 27 Sep 2022 15:03:53 GMT</pubDate><content:encoded>&lt;p&gt;Static images are a core part of mobile applications. Usually, you store them in the directory in their original sizes, which requires you to adjust the sizes now and then, depending on where they are displayed. This article discusses how to resize images in Flutter and adjust their width, height, and size with efficient lines of code.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/flutter-resize-images.mp4&quot; controls muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;resizing-methods&quot;&gt;Resizing Methods&lt;/h3&gt;
&lt;p&gt;For image editing tools, Flutter offers an excellent and easy-in-use &lt;code&gt;Boxfit&lt;/code&gt; property that works within the &lt;code&gt;fit&lt;/code&gt; parameter, and you can easily integrate it into your application.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;( &lt;/span&gt;&lt;span&gt;&apos;images/pexels.jpg&apos;&lt;/span&gt;&lt;span&gt;, fit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BoxFit&lt;/span&gt;&lt;span&gt;.cover)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on your needs, you can choose between multiple attributes. For example, &lt;code&gt;.cover&lt;/code&gt; and &lt;code&gt;.fitHeight&lt;/code&gt; properties are similar when called, and both result in maximum frame coverage. Yet, although the image is widened proportionally, these methods can significantly affect the quality of crop image borders if they overfill. On the contrary, with &lt;code&gt;.fitWidth&lt;/code&gt; or &lt;code&gt;.scaledown&lt;/code&gt;, the asset is resized to the container’s width boundaries, which could be helpful when multiple images are needed to be displayed simultaneously.  If you are dealing with a static image of small size, you can also employ &lt;code&gt;.fill&lt;/code&gt; attribute that helps stretch your assets without cropping any critical information.&lt;/p&gt;
&lt;p&gt;Alternatively, by calling  .scale 0.5 the fit parameter will return you a graphical asset based on the scale you define. Note that any value less than 1 would reduce the image size.&lt;/p&gt;
&lt;h3 id=&quot;adjusting-image-size-with-flutter&quot;&gt;Adjusting Image Size with Flutter&lt;/h3&gt;
&lt;p&gt;Now, let’s look at how Flutter allows applying these image manipulation techniques. The crucial difference from the other frameworks is that in Flutter, images should be stored in a specific folder. In other words, once you upload graphic files, go to the &lt;code&gt;pubspec.yaml&lt;/code&gt; file and add the path to the directory around under &lt;code&gt;assets&lt;/code&gt;(&lt;strong&gt;Note:&lt;/strong&gt; by default the dependencies corresponding to ****&lt;code&gt;assets&lt;/code&gt;are usually placed around 45-46th lines in the code and need to be uncommented first by eliminating # and one space).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Don&apos;t forget to define the assets folder in your directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# To do so, find lines 45 - 46 in the **pubspec.yaml** file&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# and uncomment the assets section like this:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assets&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;images/&lt;/span&gt;&lt;span&gt; #Note that this should correspond to the name of your folder&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can specify the path to each image in the list:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assets&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;assets/images/your_image.jpg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;assets/images/your_image2.jpg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the image is uploaded to Flutter, it seeks to occupy as much size as possible. But let’s presume that we have a “frame” or container that differs in size from the visual asset. One way to see the differences in sizes of both canvases is to color one frame, like color: Colors.indigo. Then we provide Flutter with the size specification and render our image in a child node child: Image.asset(‘images/pexels.jpg’). Thus, now we can see the image is placed inside the frame, and we need only to assign one of the filling methods (for example, fit: BoxFit.cover) discussed above.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:flutter/cupertino.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:flutter/material.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  runApp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ImgApp&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ImgApp&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatelessWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; ImgApp&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; MaterialApp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      debugShowCheckedModeBanner&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      home&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; ResizePage&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; ResizePage&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatelessWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; ResizePage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; Scaffold&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        appBar&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AppBar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;How to Resize Images&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          backgroundColor&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Color&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0xffe55586&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Center&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            // Enabling the Image Frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Container&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Colors&lt;/span&gt;&lt;span&gt;.indigo, &lt;/span&gt;&lt;span&gt;// To see the difference between the image&apos;s original size and the frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                height&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 550&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 300&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                // Uploading the Image from Assets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  &apos;images/pexels.jpg&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  // Resizing the Image to the Frame Size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  fit&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; BoxFit&lt;/span&gt;&lt;span&gt;.cover,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                ))));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find the associated to this tutorial Git repository ― &lt;a href=&quot;https://github.com/nataliakzm/Resizing_Images_with_Flutter&quot;&gt;here&lt;/a&gt;. Otherwise, run the following command to clone the complete code to your system:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; clone&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;https://github.com/nataliakzm/Resizing_Images_with_Flutte&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This article covered Flutter’s approach to the image resizing problem and saw how to use it in practice. However, despite the diversity of suggested methods that could fulfill basic users’ needs, Flutter fails to provide a comfortable integration. Just imagine you are building an app similar to Instagram in its functionality and you need not only upload various images from different assets but also resize them differently. If you want to avoid spending your time writing and maintaining a ton of code, then you may consider using &lt;a href=&quot;https://img.ly/products/photo-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditor SDK&lt;/a&gt; in your next project.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/photoeditor-sdk-resize-image.mp4&quot; controls muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;PhotoEditor SDK is available for various frameworks: first, read &lt;a href=&quot;https://img.ly/docs/pesdk/flutter/getting-started/integration/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this article&lt;/a&gt; from &lt;a href=&quot;https://img.ly/docs/pesdk/guides/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;the official documentation&lt;/a&gt; to set up dependencies for your Flutter-based project. You can also follow our guide on how to integrate &lt;a href=&quot;https://img.ly/blog/a-modern-video-editor-sdk-for-your-flutter-app/&quot;&gt;video editor for Flutter&lt;/a&gt; into your app.&lt;/p&gt;
&lt;p&gt;In case you encounter any difficulties with the installation process, don’t hesitate to contact our &lt;a href=&quot;https://img.ly/support?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;support&lt;/a&gt; who will be happy to help you.  Then, as shown in the example below, you can efficiently resize, crop, rotate or flip your visual assets with the &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/transform/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;transform tool&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Natalia</dc:creator><media:content url="https://blog.img.ly/2022/09/resize-images-in-flutter-1.png" medium="image"/><category>How-To</category><category>Flutter</category><category>Photo Editing</category><category>App Development</category><category>Framework</category><category>Tutorial</category></item><item><title>How to Crop and Trim Videos in Flutter</title><link>https://img.ly/blog/how-to-crop-and-trim-videos-in-flutter/</link><guid isPermaLink="true">https://img.ly/blog/how-to-crop-and-trim-videos-in-flutter/</guid><description>Create your own video app in Flutter with the free-to-use and open source solution FFmpeg.</description><pubDate>Tue, 27 Sep 2022 08:45:16 GMT</pubDate><content:encoded>&lt;p&gt;If you are looking for a package that crops and trims videos in Flutter, you must have already come across the &lt;a href=&quot;https://pub.dev/packages/video_trimmer&quot;&gt;video_trimmer&lt;/a&gt; Flutter package. This package can trim videos but does not provide video cropping (at least not out-of-the-box). In fact, none of the packages on pub.dev, as of today, allow cropping a video in Flutter. If you have dug deeper, you might have come across FFmpeg — a powerful video editing command line tool, that is not the easiest to get started with. See the &lt;a href=&quot;https://img.ly/blog/ultimate-guide-to-ffmpeg/&quot;&gt;Ultimate Guide to FFmpeg&lt;/a&gt; for help.In this article we will use the FFmpeg library to crop and trim a video in a Flutter project.&lt;/p&gt;
&lt;p&gt;Here is a list of packages that we will be using; &lt;a href=&quot;https://pub.dev/packages/ffmpeg_kit_flutter&quot;&gt;ffmpeg_kit_flutter&lt;/a&gt; package for cropping and trimming videos, the &lt;a href=&quot;https://pub.dev/packages/path_provider&quot;&gt;path_provider&lt;/a&gt; package to get the path to the application or external directory where the video files will be stored, and the &lt;a href=&quot;https://pub.dev/packages/video_player&quot;&gt;video_player&lt;/a&gt; package to play the video preview.&lt;/p&gt;
&lt;p&gt;This article will not cover building any UI for cropping and trimming. But, it will discuss in brief how to crop the video using the video_trimmer library as well. If you want to package this workflow for production, check out our guide on &lt;a href=&quot;https://img.ly/blog/how-to-run-ffmpeg-inside-a-docker-container/&quot;&gt;running FFmpeg inside a Docker container&lt;/a&gt;. Also, you can try out the code used in this article from this &lt;a href=&quot;https://github.com/numerative/flutter_crop_and_trim_video&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;get-started&quot;&gt;Get Started&lt;/h2&gt;
&lt;p&gt;The end result of this tutorial will be a simple app that would look like the following screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Create a Flutter app to trim and crop videos easily.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 323px) 323px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;323&quot; height=&quot;700&quot; src=&quot;https://img.ly/_astro/resized_Z1EVmRl.webp&quot; srcset=&quot;/_astro/resized_Z1EVmRl.webp 323w&quot;&gt;&lt;/p&gt;
&lt;p&gt;When you tap the &lt;strong&gt;Save Video&lt;/strong&gt;, the preview will refresh with the cropped and trimmed video replacing the original video. Follow the instructions in this article, and you will be able to develop a similar Flutter app.&lt;/p&gt;
&lt;p&gt;First, let us start by adding dependencies to a new Flutter project.&lt;/p&gt;
&lt;h3 id=&quot;add-dependencies&quot;&gt;Add Dependencies&lt;/h3&gt;
&lt;p&gt;In your project’s &lt;code&gt;pubspec.yaml&lt;/code&gt; file add the following 3 dependencies.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ffmpeg_kit_flutter&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;^4.5.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  path_provider&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;^2.0.11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  video_player&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;^2.4.6&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, execute &lt;code&gt;flutter pub get&lt;/code&gt; command from the project folder root.&lt;/p&gt;
&lt;h3 id=&quot;set-minimum-sdk-version-and-platform-version&quot;&gt;Set Minimum SDK Version and Platform Version&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;ffmpeg_kit_flutter&lt;/code&gt; plugin runs on Android SDK API level 24+ and iOS SDK 12.1+. Therefore, modify the module level &lt;code&gt;build.gradle&lt;/code&gt; file to declare the &lt;code&gt;minSdkVersion&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;groovy&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;android {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  defaultConfig {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    minSdkVersion &lt;/span&gt;&lt;span&gt;24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, modify the &lt;code&gt;Podfile&lt;/code&gt; to declare the minimum global platform.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Uncomment this line to define a global platform for your project&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;platform &lt;/span&gt;&lt;span&gt;:ios&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;12.1&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;add-a-video-asset&quot;&gt;Add a Video Asset&lt;/h3&gt;
&lt;p&gt;For simplicity, we will be using a video asset instead of implementing a file picker. Choose a video file you would like to work with or &lt;a href=&quot;https://github.com/numerative/flutter_crop_and_trim_video/raw/main/assets/file1.mp4&quot;&gt;download&lt;/a&gt; this sample video file. Next, copy the video file to a new directory named &lt;code&gt;assets&lt;/code&gt; at the root of your project folder.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Copy your video of choice to your assets folder at the root of your project folder.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 318px) 318px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;318&quot; height=&quot;240&quot; src=&quot;https://img.ly/_astro/file1-mp4-placement_ZVFQb9.webp&quot; srcset=&quot;/_astro/file1-mp4-placement_ZVFQb9.webp 318w&quot;&gt;&lt;/p&gt;
&lt;p&gt;And then reference the video file from the &lt;code&gt;pubspec.yaml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;flutter&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  assets&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    - &lt;/span&gt;&lt;span&gt;assets/file1.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Execute &lt;code&gt;flutter pub get&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;Having added the required dependencies, let us move on to coding.&lt;/p&gt;
&lt;h3 id=&quot;implement-video-crop-and-trim&quot;&gt;Implement Video Crop and Trim&lt;/h3&gt;
&lt;p&gt;Replace the code in your &lt;code&gt;main.dart&lt;/code&gt; file with the following code in the code block. This will be our starter code.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:flutter/material.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  runApp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; MyApp&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyApp&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatelessWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; MyApp&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // This widget is the root of your application.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; MaterialApp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;Crop and Trim Demo&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      home&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; MyHomePage&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyHomePage&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; StatefulWidget&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; MyHomePage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; key}) &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; super&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  State&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyHomePage&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;createState&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; _MyHomePageState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _MyHomePageState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyHomePage&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Widget&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; Scaffold&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      appBar&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AppBar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Flutter Crop and Trim&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Column&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;initialize-the-input-player&quot;&gt;Initialize the Input Player&lt;/h3&gt;
&lt;p&gt;Add the following code to the &lt;code&gt;_MyHomePageState&lt;/code&gt; class.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;dart:io&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:flutter/services.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:path_provider/path_provider.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:video_player/video_player.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _MyHomePageState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyHomePage&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  late&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt; inputPath;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  VideoPlayerController&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; controller;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  String&lt;/span&gt;&lt;span&gt; outputPath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void&lt;/span&gt;&lt;span&gt; initState&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;initState&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    copyVideoToApplicationDirectory&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((path) &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      inputPath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; path;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      controller &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VideoPlayerController&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;(inputPath));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;initialize&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setState&lt;/span&gt;&lt;span&gt;(() {});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      outputPath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getOutputPath&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ///Copy input file to ApplicationStorage Directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ///returns path to copied video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Future&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;copyVideoToApplicationDirectory&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; filename &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;file1.mp4&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    var&lt;/span&gt;&lt;span&gt; bytes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; rootBundle.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;assets/file1.mp4&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    String&lt;/span&gt;&lt;span&gt; dir &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; getApplicationDocumentsDirectory&lt;/span&gt;&lt;span&gt;()).path;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    writeToFile&lt;/span&gt;&lt;span&gt;(bytes, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;dir&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;dir&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ///Write to Path.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Future&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;writeToFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ByteData&lt;/span&gt;&lt;span&gt; data, &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; path) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; buffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.buffer;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; File&lt;/span&gt;&lt;span&gt;(path).&lt;/span&gt;&lt;span&gt;writeAsBytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        buffer.&lt;/span&gt;&lt;span&gt;asUint8List&lt;/span&gt;&lt;span&gt;(data.offsetInBytes, data.lengthInBytes));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;copyVideoToApplicationDirectory&lt;/code&gt; method is copying the video from Assets to the Application directory on the phone’s file system. The resulting path is then stored in the &lt;code&gt;inputPath&lt;/code&gt; variable which is then supplied to the FFmpeg command. For large‑scale workloads, you can move this FFmpeg command to &lt;a href=&quot;https://img.ly/blog/how-to-run-ffmpeg-on-aws-spot-instances-for-scalable-low-cost-video-processing/&quot;&gt;AWS Spot Instances&lt;/a&gt;; our AWS guide shows how to configure a cloud environment for scalable video processing.&lt;/p&gt;
&lt;p&gt;Next, add the &lt;code&gt;VideoPlayer&lt;/code&gt; widget to the &lt;code&gt;Column&lt;/code&gt; widget.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Column&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (controller &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ?&lt;/span&gt;&lt;span&gt; AspectRatio&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        aspectRatio&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.value.aspectRatio,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; VideoPlayer&lt;/span&gt;&lt;span&gt;(controller&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        :&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; SizedBox&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the app and the video should start playing.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;You can now play your video.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 323px) 323px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;323&quot; height=&quot;700&quot; src=&quot;https://img.ly/_astro/resized_screen_2sxrDY.webp&quot; srcset=&quot;/_astro/resized_screen_2sxrDY.webp 323w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the next step, we will add the FFmpeg command, but before that add the following 2 more methods to the &lt;code&gt;_MyHomePageState&lt;/code&gt; class.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:ffmpeg_kit_flutter/ffmpeg_kit.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:ffmpeg_kit_flutter/log.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;package:ffmpeg_kit_flutter/return_code.dart&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; _MyHomePageState&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; State&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyHomePage&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  /// Output path with a file name where the result will be stored.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Future&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;getOutputPath&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; appDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Platform&lt;/span&gt;&lt;span&gt;.isAndroid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ?&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getExternalStorageDirectory&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        :&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; getApplicationDocumentsDirectory&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; externalPath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;appDirectory&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/out_file.mp4&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; externalPath;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ///Executes the FFMPEG &lt;/span&gt;&lt;span&gt;[command]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ///Note: Green bar on the right is a Flutter issue. &amp;#x3C;https://github.com/flutter/engine/pull/24888&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ///Should get fixed in a 3.1.0+ stable release &amp;#x3C;https://github.com/flutter/engine/pull/24888#issuecomment-1212374010&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Future&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ffmpegExecute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; command) &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; FFmpegKit&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(command);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    final&lt;/span&gt;&lt;span&gt; returnCode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;getReturnCode&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ReturnCode&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isSuccess&lt;/span&gt;&lt;span&gt;(returnCode)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Success&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      //Replace the preview video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pause&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      controller &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VideoPlayerController&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;(outputPath));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;initialize&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; controller&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      setState&lt;/span&gt;&lt;span&gt;(() {});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ReturnCode&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isCancel&lt;/span&gt;&lt;span&gt;(returnCode)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Cancel&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      final&lt;/span&gt;&lt;span&gt; failStackTrace &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;getFailStackTrace&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      print&lt;/span&gt;&lt;span&gt;(failStackTrace);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Log&lt;/span&gt;&lt;span&gt;&gt; logs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;getLogs&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; element &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; logs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        print&lt;/span&gt;&lt;span&gt;(element.&lt;/span&gt;&lt;span&gt;getMessage&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;getOutputPath&lt;/code&gt; method provides the path where the resulting video will be saved. The &lt;code&gt;outputPath&lt;/code&gt; will be passed to the FFmpeg command whereas the &lt;code&gt;ffmpegExecute&lt;/code&gt; method is where the FFmpeg magic takes place.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ffmpegExecute&lt;/code&gt; method expects a valid FFmpeg &lt;code&gt;String&lt;/code&gt; command. The &lt;code&gt;String&lt;/code&gt; command is then passed to the &lt;code&gt;FFmpegKit.execute&lt;/code&gt; method which returns an instance of &lt;code&gt;FFMpegSession&lt;/code&gt; . It will tell us whether our command was executed successfully or not. If it was not executed successfully, we can extract error logs from it.&lt;/p&gt;
&lt;h3 id=&quot;ffmpeg-execute-command&quot;&gt;FFmpeg Execute Command&lt;/h3&gt;
&lt;p&gt;Add a &lt;code&gt;TextButton&lt;/code&gt; from where the FFmpeg command shall be sent.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TextButton&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  child&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Save Video&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onPressed&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //TODO: Call FFMPEG Execute&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, call the following &lt;code&gt;ffmpegExecute&lt;/code&gt; method from the &lt;code&gt;onPressed&lt;/code&gt; property.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;dart&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ffmpegExecute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;-ss 0:00:15 -to 0:00:45 -y -i &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;inputPath&lt;/span&gt;&lt;span&gt; -filter:v &quot;crop=320:150&quot; -c:a copy &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;outputPath&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the app and tap the &lt;strong&gt;Save Video&lt;/strong&gt; button. You will notice that the player is now playing the new cropped and trimmed video.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;You can now play your cropped and trimmed video!&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 323px) 323px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;323&quot; height=&quot;700&quot; src=&quot;https://img.ly/_astro/flutter_video_app_crop_trim_screen_ZP5gb4.webp&quot; srcset=&quot;/_astro/flutter_video_app_crop_trim_screen_ZP5gb4.webp 323w&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you were able to successfully follow the instructions up till here, it is time to dig a little deeper into the FFmpeg command that we just ran earlier.&lt;/p&gt;
&lt;h3 id=&quot;understanding-the-ffmpeg-command&quot;&gt;Understanding the FFmpeg Command&lt;/h3&gt;
&lt;p&gt;Because command FFmpeg is a command line tool, it expects string-only commands. For this reason, we do not have any dart Classes, Methods, or Parameters to work with that we usually get when working with a dart plugin. Let us dissect the above command line command that is helping us crop and trim a video.&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Command&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;-ss 0:00:15&lt;/td&gt;&lt;td&gt;Seeks to position on the input video. The trim starts from this position.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-to 0:00:45&lt;/td&gt;&lt;td&gt;Stops reading at the position in the input video. The trim stops at this position. The total length of the video is 0:02:05.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-y&lt;/td&gt;&lt;td&gt;Overwrite output files. Helpful when the Save Video button is tapped more than once.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-i $inputPath&lt;/td&gt;&lt;td&gt;Input file location. This is the file on which the crop and trim are applied.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-filter:v&lt;/td&gt;&lt;td&gt;Apply a filter to the video stream.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;”crop=320:150”&lt;/td&gt;&lt;td&gt;Apply a crop filter from the center of the video that is 320 pixels wide and 150 pixels tall. The original dimensions of the video were 320 x 240.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;-c:a copy $outputPath&lt;/td&gt;&lt;td&gt;Specifies the codec with which the output file must be encoded. Here, copy is a special value to indicate the stream is not to be re-encoded. The a after the colon is a stream specifier for the audio stream.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;That will be either the application directory or the external directory of the application, depending on whether the app is running on iOS or Android.&lt;/p&gt;
&lt;p&gt;You can check out the app’s code and play with it by downloading it from this &lt;a href=&quot;https://github.com/numerative/flutter_crop_and_trim_video&quot;&gt;GitHub Repository&lt;/a&gt;. This project allows you to tweak the duration of the trim and dimensions of the crop, so it has more code, but at the heart of it, it still uses the above FFmpeg command.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Download this app code from the GitHub Repository.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 378px) 378px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;378&quot; height=&quot;700&quot; src=&quot;https://img.ly/_astro/flutter_video_app_crop_trim_info_Z2pzU1G.webp&quot; srcset=&quot;/_astro/flutter_video_app_crop_trim_info_Z2pzU1G.webp 378w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;cropping-using-the-video_trimmer-plugin&quot;&gt;Cropping Using the video_trimmer Plugin&lt;/h2&gt;
&lt;p&gt;As a bonus, here is a tip on how to use the video_trimmer plugin for cropping videos.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://pub.dev/packages/video_trimmer&quot;&gt;video_trimmer&lt;/a&gt; plugin also uses FFmpeg at its core and allows us to pass on an FFmpeg command while saving the video. For this reason, it is an easy task for us to apply the crop to a video using the video_trimmer plugin.&lt;/p&gt;
&lt;p&gt;To do this, pass the following command to the &lt;code&gt;saveTrimmedVideo&lt;/code&gt; method’s &lt;code&gt;ffmpegCommand&lt;/code&gt; parameter as shown in the code block below. The video_trimmer package expects the &lt;code&gt;customVideoFormat&lt;/code&gt; parameter argument when the &lt;code&gt;ffmpegCommand&lt;/code&gt; parameter is used.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;_trimmer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;saveTrimmedVideo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        startValue:&lt;/span&gt;&lt;span&gt; _startValue&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        endValue:&lt;/span&gt;&lt;span&gt; _endValue&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ffmpegCommand:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &apos;-filter:v &quot;crop=320:150&quot;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        customVideoFormat:&lt;/span&gt;&lt;span&gt; &apos;.mp4&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((value) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  setState&lt;/span&gt;&lt;span&gt;(() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    _value&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;limitations-of-this-approach&quot;&gt;Limitations of this Approach&lt;/h2&gt;
&lt;p&gt;The crop that we are applying is from the center of the frame. The present command would also need starting coordinates to crop a non-center frame.&lt;/p&gt;
&lt;p&gt;The project will need a refined UI for cropping and trimming videos to offer a complete app experience to users. The current approach is miles away from creating that experience.&lt;/p&gt;
&lt;p&gt;But perhaps the most significant limitation is FFmpeg’s &lt;strong&gt;licensing&lt;/strong&gt;. FFmpeg is available with both LGPL and GPL licenses, so you must ensure your project is compatible with those licenses. For many commercial projects, this is a non-starter. Alternatively, you can &lt;a href=&quot;https://img.ly/blog/ffmpeg-on-google-cloud-platform-guide/&quot;&gt;run FFmpeg on Google Cloud Platform&lt;/a&gt;; our GCP tutorial walks you through setup.&lt;/p&gt;
&lt;h2 id=&quot;commercial-alternative&quot;&gt;Commercial Alternative&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/docs/vesdk/flutter/getting-started/integration/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK (VE.SDK)&lt;/a&gt; from &lt;a href=&quot;https://img.ly&quot;&gt;IMG.LY&lt;/a&gt; provides powerful video editing features, including cropping and trimming videos in a Flutter project. You will receive staples of video editing, including straightening videos, filters, brightness, color adjustments, and more. Follow our guide to learn how to integrate IMG.LY’s &lt;a href=&quot;https://img.ly/blog/a-modern-video-editor-sdk-for-your-flutter-app/&quot;&gt;video editor for Flutter&lt;/a&gt; into your app.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While still a complex topic, video manipulation is more attainable to implement on Flutter than on native Android. Nevertheless, FFmpeg is the only open-source, free-to-use option to edit videos on Flutter right now. To automate cropping and trimming across many files or build a transcoding server, see our article on &lt;a href=&quot;https://img.ly/blog/building-a-production-ready-batch-video-processing-server-with-ffmpeg/&quot;&gt;building a production‑ready batch video processing server.&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Michael H.</dc:creator><media:content url="https://blog.img.ly/2022/09/trim_videos_with_flutter_tutorial.png" medium="image"/><category>How-To</category><category>Flutter</category><category>Video Editor</category><category>App Development</category><category>FFmpeg</category><category>Tutorial</category></item><item><title>How To Crop and Trim Videos In Kotlin for Android</title><link>https://img.ly/blog/how-to-crop-and-trim-videos-in-kotlin-for-android/</link><guid isPermaLink="true">https://img.ly/blog/how-to-crop-and-trim-videos-in-kotlin-for-android/</guid><description>In this beginner-friendly tutorial you will learn how to crop and trim videos in Android with FFmpeg.</description><pubDate>Fri, 02 Sep 2022 10:18:21 GMT</pubDate><content:encoded>&lt;p&gt;Cropping and trimming videos is a notoriously difficult task to achieve on Android. One way to implement this functionality is by using FFmpeg a free open-source suite of tools that can perform a wide range of tasks, from video converting to editing. Normally FFmpeg is used from the command line, to use it correctly in Android you have to understand its underlying APIs and how to use them.&lt;/p&gt;
&lt;p&gt;In this tutorial, you will learn how to crop and trim videos in Android by using FFmpeg. Even if you are a beginner you should be able to follow the steps to achieve the desired results.&lt;/p&gt;
&lt;p&gt;I try to summarise the most important basics that you need to know to manipulate videos with FFmpeg. After reading this article you should be able to use it in your own applications. Furthermore, I have developed a sample application and library that can be used to trim and crop videos with an Android device.&lt;/p&gt;
&lt;h2 id=&quot;what-is-ffmpeg&quot;&gt;What is FFmpeg&lt;/h2&gt;
&lt;p&gt;FFmpeg is a great multimedia framework that is able to &lt;strong&gt;mux&lt;/strong&gt;, &lt;strong&gt;demux&lt;/strong&gt;, &lt;strong&gt;decode&lt;/strong&gt;, &lt;strong&gt;encode&lt;/strong&gt;, &lt;strong&gt;transcode&lt;/strong&gt;, &lt;strong&gt;filter&lt;/strong&gt;, &lt;strong&gt;stream&lt;/strong&gt;, &lt;strong&gt;and&lt;/strong&gt; &lt;strong&gt;play&lt;/strong&gt; most media content that exists. FFmpeg supports different tools that can be used to develop an application to manipulate any kind of media to the desired output. You do not have any limitations on what to do with multimedia when using FFmpeg.&lt;/p&gt;
&lt;p&gt;Unfortunately, to use FFmpeg in an Android App using Kotlin you have to compile and build the libraries to use them as a dependency in your project. This process is not straightforward because you have to manually compile a C/C++ library with help of the Android NDK.&lt;/p&gt;
&lt;p&gt;Luckily, many people already have done this and we are able to use their compiled library in our project. However, if you want to compile FFmpeg from scratch you can do this. All necessary information should be available within the &lt;a href=&quot;https://developer.android.com/studio/projects/add-native-code&quot;&gt;android developer documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To show you how we can use FFmpeg in our app I will use a compiled FFmpeg library that can be found &lt;a href=&quot;https://github.com/WritingMinds/ffmpeg-android-java&quot;&gt;here&lt;/a&gt;. To find other FFmpeg libraries you can have a look at the &lt;a href=&quot;https://trac.ffmpeg.org/wiki/CompilationGuide/Android&quot;&gt;official FFmpeg wiki&lt;/a&gt; where several pre-packaged sources are listed.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-project&quot;&gt;Setting up the project&lt;/h2&gt;
&lt;p&gt;In order to use the FFmpeg library of WritingMinds (or any other library), we have to follow some simple steps.&lt;/p&gt;
&lt;h3 id=&quot;1-add-dependency-for-the-ffmpeg-library-to-your-app-level-buildgradle&quot;&gt;1. Add dependency for the FFmpeg library to your app-level build.gradle&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    implementation fileTree(dir: &apos;libs&apos;, include: [&apos;*.jar&apos;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    implementation &apos;com.writingminds:FFmpegAndroid:0.3.2&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-check-if-your-device-supports-the-current-implementation-of-ffmpeg-or-not&quot;&gt;2. Check if your device supports the current implementation of FFmpeg or not&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; initialize&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; ffmpeg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FFmpeg.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;(ctx.applicationContext)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            ffmpeg.&lt;/span&gt;&lt;span&gt;loadBinary&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;LoadBinaryResponseHandler&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onFinish&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onFinish&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onSuccess&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onSuccess&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onFailure&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onFailure&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onStart&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onStart&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;FFmpegNotSupportedException&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;FFmpeg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Your device does not support FFmpeg&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-initialize-the-ffmpeg-module-leave-command-blank&quot;&gt;3. Initialize the FFmpeg module (leave command blank)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; ffmpeg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FFmpeg.&lt;/span&gt;&lt;span&gt;getInstance&lt;/span&gt;&lt;span&gt;(ctx)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       ffmpeg.&lt;/span&gt;&lt;span&gt;loadBinary&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;FFmpegLoadBinaryResponseHandler&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onFinish&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                Log.&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;FFmpeg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;onFinish&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onSuccess&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                Log.&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;FFmpeg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;onSuccess&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; //TODO: the command will added here later&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ffmpeg.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(command, &lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;ExecuteBinaryResponseHandler&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onSuccess&lt;/span&gt;&lt;span&gt;(message: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onSuccess&lt;/span&gt;&lt;span&gt;(message)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Log.&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;onSuccess: &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;!!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onProgress&lt;/span&gt;&lt;span&gt;(message: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onProgress&lt;/span&gt;&lt;span&gt;(message)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Log.&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;onProgress: &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;!!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onFailure&lt;/span&gt;&lt;span&gt;(message: &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onFailure&lt;/span&gt;&lt;span&gt;(message)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;onFailure: &quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;!!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onStart&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onStart&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Log.&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;onStart&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onFinish&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onFinish&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Log.&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;onFinish&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;FFmpegCommandAlreadyRunningException&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;FFmpeg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;FFmpeg runs already&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onFailure&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;FFmpeg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;onFailure&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onStart&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-implement-the-commands-you-want-to-use-in-your-app&quot;&gt;4. Implement the commands you want to use in your app&lt;/h3&gt;
&lt;p&gt;All commands that are supported by FFmpeg can be included in your app with help of an array. This is done by passing every command line argument as a single element within the array. The array will then be translated into an FFmpeg command using the execute method from FFmpeg:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ffmpeg.execute(command, object : ExecuteBinaryResponseHandler() { ... }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As we want to implement Trim and Crop I will show how this can be done using the &lt;code&gt;arrayOf&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trim:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arrayOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-y&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-i&quot;&lt;/span&gt;&lt;span&gt;, input, &lt;/span&gt;&lt;span&gt;&quot;-ss&quot;&lt;/span&gt;&lt;span&gt;, startPos, &lt;/span&gt;&lt;span&gt;&quot;-to&quot;&lt;/span&gt;&lt;span&gt;, endPos, &lt;/span&gt;&lt;span&gt;&quot;-c&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;copy&quot;&lt;/span&gt;&lt;span&gt;, output)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“-y”:&lt;/strong&gt; overwrites output files without asking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-i”:&lt;/strong&gt; specifies an input file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;input:&lt;/strong&gt; the path of the source video to trim&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-ss”:&lt;/strong&gt; specifies that the next value will be the starting point of the resulting video&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;startPos:&lt;/strong&gt; the starting position in “%d:%02d:%02d” format&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-to”:&lt;/strong&gt; specifies that the next value will be the end position of the resulting video&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;endPos:&lt;/strong&gt; the end position in “%d:%02d:%02d” format&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-c” AND “copy”&lt;/strong&gt;: defines that the stream will not be encoded. The resulting video will only be saved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;output:&lt;/strong&gt; the path of the resulting video&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To better understand every argument you can have a look at &lt;a href=&quot;https://www.ffmpeg.org/ffmpeg.html#Main-options&quot;&gt;the official FFmpeg documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, to provide arguments for &lt;code&gt;startPos&lt;/code&gt; and &lt;code&gt;endPos&lt;/code&gt; you normally would have to use a utility function that converts the timestamp of the start and end position into the desired “%d:%02d:%02d” format (a string):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fun&lt;/span&gt;&lt;span&gt; convertTimestampToString&lt;/span&gt;&lt;span&gt;(timeInMs: &lt;/span&gt;&lt;span&gt;Float&lt;/span&gt;&lt;span&gt;): &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; totalSeconds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (timeInMs &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toInt&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; seconds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalSeconds &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; minutes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalSeconds &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt; %&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; hours &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalSeconds &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 3600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; formatter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Formatter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (hours &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            formatter.&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;%d:%02d:%02d&quot;&lt;/span&gt;&lt;span&gt;, hours, minutes, seconds).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            formatter.&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;%02d:%02d&quot;&lt;/span&gt;&lt;span&gt;, minutes, seconds).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Crop:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arrayOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-i&quot;&lt;/span&gt;&lt;span&gt;, input, &lt;/span&gt;&lt;span&gt;&quot;-filter:v&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;crop=&lt;/span&gt;&lt;span&gt;$w&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;$h&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;$x&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;$y&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-threads&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;5&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-preset&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;ultrafast&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-strict&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-2&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-c:a&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;copy&quot;&lt;/span&gt;&lt;span&gt;, output)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“-i”:&lt;/strong&gt; specifies an input file&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;input:&lt;/strong&gt; the path of the source video to trim&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-filter:v”:&lt;/strong&gt; defines that a filtergraph is used&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“crop=$w:$h:$x:$y”:&lt;/strong&gt; use crop functionality to crop a part from the video start at point x:y and having a width(w) and height(h).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-threads”:&lt;/strong&gt; specifies that the next value will set the thread count&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“5”:&lt;/strong&gt; the number of threads to use&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-preset”:&lt;/strong&gt; specifies that the next value will set the encoding preset&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“ultrafast”:&lt;/strong&gt; the encoding preset to use&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-strict”:&lt;/strong&gt; specifies how strictly the standards should be followed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-2”:&lt;/strong&gt; the strict value&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“-c:a” AND “copy”&lt;/strong&gt; defines that the stream will not be encoded and ALL audio streams will also be used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;output:&lt;/strong&gt; the path of the resulting video&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, check &lt;a href=&quot;https://www.ffmpeg.org/ffmpeg.html#Main-options&quot;&gt;the official FFmpeg documentation&lt;/a&gt; to better understand every argument.&lt;/p&gt;
&lt;h3 id=&quot;5-implement-the-ui&quot;&gt;5. Implement the UI&lt;/h3&gt;
&lt;p&gt;To create a fancy UI you should create a custom view that shows some of the frames available in the video (&lt;code&gt;VideoPreviewView&lt;/code&gt;). Also, it should contain a &lt;code&gt;SeekBar&lt;/code&gt; for &lt;strong&gt;cropping&lt;/strong&gt; and a &lt;code&gt;RangeSeekBar&lt;/code&gt; for &lt;strong&gt;trimming&lt;/strong&gt;. The &lt;code&gt;SeekBar&lt;/code&gt; will be used to switch to a certain timestamp within the video to see what is actually trimmed or cropped. The RangeSeekBar will only be used within the trimming UI to define the start and end position of the resulting video.&lt;/p&gt;
&lt;p&gt;While &lt;code&gt;SeekBar&lt;/code&gt; is &lt;a href=&quot;https://developer.android.com/reference/android/widget/SeekBar&quot;&gt;an Android Widget&lt;/a&gt; the &lt;code&gt;RangeSeekBar&lt;/code&gt; is more complicated but there exist several implementations that can be used: By &lt;a href=&quot;https://github.com/tizisdeepan/VideoEditor/blob/master/video-editor/src/main/java/com/video/trimmer/view/RangeSeekBarView.kt&quot;&gt;Tiszideepan&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can implement the &lt;code&gt;VideoPreviewView&lt;/code&gt; with an easy approach. Divide the video into a specific number of frames that are based on the view’s width and display every frame sequentially within a single view to have a preview. To do this you need the width of the view and the duration of the video that can be found using the &lt;a href=&quot;https://developer.android.com/reference/android/media/MediaMetadataRetriever&quot;&gt;MediaMetadataRetriever&lt;/a&gt;. With the &lt;code&gt;MediaMetadataRetriever&lt;/code&gt;, you can use &lt;code&gt;getFrameAtTime()&lt;/code&gt; to fetch a single frame at a specific timestamp. If you now want to display a complete video preview you need to display &lt;code&gt;viewWidth / frameWidth&lt;/code&gt; frames. Unfortunately, depending on the video length and video width, it could happen that only a few frames will be present within the &lt;code&gt;VideoPreviewView&lt;/code&gt;. To fix this problem you have to maintain a threshold to ensure that a specific number of frames are displayed. This means that you have to crop the frames to a certain width until calculated frames equal the threshold.&lt;/p&gt;
&lt;p&gt;The following code snippet will show how you can achieve this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; createPreview&lt;/span&gt;&lt;span&gt;(viewWidth: &lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        BackgroundExecutor.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;BackgroundExecutor&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0L&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; execute&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; threshold &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; thumbnails &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; LongSparseArray&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Bitmap&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; mediaMetadataRetriever &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MediaMetadataRetriever&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    mediaMetadataRetriever.&lt;/span&gt;&lt;span&gt;setDataSource&lt;/span&gt;&lt;span&gt;(context, videoUri)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; videoLengthInMs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (Integer.&lt;/span&gt;&lt;span&gt;parseInt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        mediaMetadataRetriever.&lt;/span&gt;&lt;span&gt;extractMetadata&lt;/span&gt;&lt;span&gt;(MediaMetadataRetriever.METADATA_KEY_DURATION)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    ) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toLong&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; frameHeight &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; viewHeight&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; initialBitmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mediaMetadataRetriever.&lt;/span&gt;&lt;span&gt;getFrameAtTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        MediaMetadataRetriever.OPTION_CLOSEST_SYNC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; frameWidth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        ((initialBitmap.width.&lt;/span&gt;&lt;span&gt;toFloat&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; initialBitmap.height.&lt;/span&gt;&lt;span&gt;toFloat&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; frameHeight.&lt;/span&gt;&lt;span&gt;toFloat&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;toInt&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    var&lt;/span&gt;&lt;span&gt; numThumbs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ceil&lt;/span&gt;&lt;span&gt;((viewWidth.&lt;/span&gt;&lt;span&gt;toFloat&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; frameWidth)).&lt;/span&gt;&lt;span&gt;toInt&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    if&lt;/span&gt;&lt;span&gt; (numThumbs &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; threshold) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        numThumbs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; threshold&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; cropWidth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; viewWidth &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; threshold&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    val&lt;/span&gt;&lt;span&gt; interval &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videoLengthInMs &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; numThumbs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    for&lt;/span&gt;&lt;span&gt; (i &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; until numThumbs) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        var&lt;/span&gt;&lt;span&gt; bitmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mediaMetadataRetriever.&lt;/span&gt;&lt;span&gt;getFrameAtTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            i &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; interval,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            MediaMetadataRetriever.OPTION_CLOSEST_SYNC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        bitmap?.&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                bitmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Bitmap.&lt;/span&gt;&lt;span&gt;createScaledBitmap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                    bitmap,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                    frameWidth,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                    frameHeight,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                    false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                bitmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Bitmap.&lt;/span&gt;&lt;span&gt;createBitmap&lt;/span&gt;&lt;span&gt;(bitmap, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, cropWidth, bitmap.height)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;error while create bitmap: &lt;/span&gt;&lt;span&gt;$e&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            thumbnails.&lt;/span&gt;&lt;span&gt;put&lt;/span&gt;&lt;span&gt;(i.&lt;/span&gt;&lt;span&gt;toLong&lt;/span&gt;&lt;span&gt;(), bitmap)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    mediaMetadataRetriever.&lt;/span&gt;&lt;span&gt;release&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    returnBitmaps&lt;/span&gt;&lt;span&gt;(thumbnails)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;Throwable&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    Thread.&lt;/span&gt;&lt;span&gt;getDefaultUncaughtExceptionHandler&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                        .&lt;/span&gt;&lt;span&gt;uncaughtException&lt;/span&gt;&lt;span&gt;(Thread.&lt;/span&gt;&lt;span&gt;currentThread&lt;/span&gt;&lt;span&gt;(), e)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additionally, for cropping a video you need to display a &lt;em&gt;crop rectangle&lt;/em&gt; within the UI to let users decide which part of the video they want to crop. Also, you have to give users the ability to reposition the crop area. To do this, you can use &lt;a href=&quot;https://github.com/ArthurHub/Android-Image-Cropper&quot;&gt;the ImageCropper library from ArthurHub&lt;/a&gt; which is initially designed to crop an image but can also be used for videos as you only need the values from the rectangle. Add the &lt;code&gt;ImageCropper&lt;/code&gt; to your project:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    implementation &apos;com.theartofdev.edmodo:android-image-cropper:2.8.0&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can extend a &lt;code&gt;CropperActivity&lt;/code&gt; which loads the current frame of the video (that you can extract with the &lt;code&gt;VideoPreviewView&lt;/code&gt;) by inserting the &lt;code&gt;CropImageView&lt;/code&gt; layout to get the cropping bounds. Once, you select an area in the UI, the rectangle values can be extracted and used to calculate the needed values for video cropping with this function:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; rect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropFrame.cropRect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; abs&lt;/span&gt;&lt;span&gt;(rect.left &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rect.right)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; h &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; abs&lt;/span&gt;&lt;span&gt;(rect.top &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rect.bottom)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rect.left&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; y &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rect.top&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These values will then be used as input variables for the FFmpeg crop command.&lt;/p&gt;
&lt;p&gt;To help you use all this information I created a project that uses all explained snippets to create a sample app that can crop and trim videos. The source code can be found &lt;a href=&quot;https://github.com/paulknulst/VideoTrimmer&quot;&gt;on my personal GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;fixing-error13-permission-denied&quot;&gt;Fixing error=13, Permission denied&lt;/h2&gt;
&lt;p&gt;Unfortunately, if you implement the described functionality and use the compiled FFmpeg library you will run into this problem if you compile your app for SDK 29 and onwards.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.knulst.de/content/images/2022/08/image-4.png&quot; alt=&quot;Permission denied error due to wrong target SDK&quot;&gt;&lt;/p&gt;
&lt;p&gt;Permission denied error due to wrong target SDK The problem is caused because from Android Q onwards it is not allowed to execute binaries in your app’s private data directory. The error is described in &lt;a href=&quot;https://issuetracker.google.com/issues/128554619?pli=1&quot;&gt;the official issue tracker from google&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One solution will be that you compile your app for SDK 28 but this could lead to problems if you try to put your app into the Android Play Store.&lt;/p&gt;
&lt;p&gt;Another solution will be that you compile your app for SDK 29 and stop putting binaries in any not supported directory. Unfortunately, the code that moves the binaries is within the 3rd party lib from WritingMinds and has to be removed by you (with a Pull Request) or by the maintainer of the library. A future-proof solution will be that you stop using external binaries and start compiling dependencies as an NDK project. This will be a lot of work but you can find help within a famous repository that compiles CPP to Java and also has &lt;a href=&quot;https://github.com/bytedeco/javacpp-presets/tree/master/ffmpeg&quot;&gt;FFmpeg&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;add-more-commands&quot;&gt;Add more commands&lt;/h2&gt;
&lt;p&gt;After you implemented crop and trim you maybe want to add more cool features to your Android app. To do this with FFmpeg and the previously described code you can do this by simply creating a new function within the &lt;code&gt;VideoCommands()&lt;/code&gt; and translate the FFmpeg command into a &lt;code&gt;VideoCommands()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The next sections will describe some fancy functions that would improve the usability of your app. Just add a new function with this command and fill the callbacks with a toast or anything else. Then call the function from anywhere within your app (because you do not need UI for this).&lt;/p&gt;
&lt;h3 id=&quot;removing-audio-from-videos&quot;&gt;Removing audio from videos&lt;/h3&gt;
&lt;p&gt;You might run into a scenario where you’d only like to keep the visuals of a video and remove the audio track, for instance for voicing over certain footage or removing background noise:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arrayOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-i&quot;&lt;/span&gt;&lt;span&gt;, input, &lt;/span&gt;&lt;span&gt;&quot;-an&quot;&lt;/span&gt;&lt;span&gt;, output)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;compress-a-video&quot;&gt;Compress a video&lt;/h3&gt;
&lt;p&gt;Make big videos smaller to save your valuable disk resources.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arrayOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-i&quot;&lt;/span&gt;&lt;span&gt;, input, &lt;/span&gt;&lt;span&gt;&quot;-vf&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;scale=&lt;/span&gt;&lt;span&gt;$w&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;$h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-c:v&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;libx264&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-preset&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;veryslow&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-crf&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;24&quot;&lt;/span&gt;&lt;span&gt;, output)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$w and $h&lt;/strong&gt; are the new sizes (should be smaller). If one is -1 it will keep the ratio and is automatically adjusted in relation to the other one.&lt;/p&gt;
&lt;h3 id=&quot;change-playback-speed&quot;&gt;Change playback speed&lt;/h3&gt;
&lt;p&gt;Increase the playback speed to make long videos faster or decrease it for showing something really awesome very slowly.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt; val&lt;/span&gt;&lt;span&gt; command &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; arrayOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;-i&quot;&lt;/span&gt;&lt;span&gt;, input, &lt;/span&gt;&lt;span&gt;&quot;-vf&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;setpts=&lt;/span&gt;&lt;span&gt;$scale&lt;/span&gt;&lt;span&gt;*PTS&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, output)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;$scale&lt;/strong&gt; should be &gt; 1 for slowing down a video or &amp;#x3C; 1 for fastening a video.&lt;/p&gt;
&lt;h3 id=&quot;more&quot;&gt;More&lt;/h3&gt;
&lt;p&gt;To find more cool commands search &lt;a href=&quot;https://www.ffmpeg.org/ffmpeg.html&quot;&gt;the official argument documentation&lt;/a&gt; and implement them by putting every argument into a value of the array function as described earlier.&lt;/p&gt;
&lt;p&gt;For production-grade application you may be better served by using our &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditorSDK&lt;/a&gt; which in addition to professional trim and crop features allows your users to add filters, text and audio overlays. Recently, we released time-based sprites as well allowing users to time the appearance of text and stickers in videos.&lt;/p&gt;
&lt;h3 id=&quot;closing-notes&quot;&gt;Closing Notes&lt;/h3&gt;
&lt;p&gt;In this article, you learned that you can use FFmpeg to crop and trim videos on Android. Unfortunately, it does not work out of the box and you have to compile the library using complex techniques. Luckily, there exist some useful libraries that do the heavy lifting for you.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/paulknulst/VideoTrimmer&quot;&gt;Using my app as a baseline&lt;/a&gt; you are able to crop and trim videos. Additionally, you can implement more commands easily if they do not need a GUI because creating a fancy-looking GUI was a complicated part of developing a cropping and trimming app.&lt;/p&gt;
&lt;p&gt;Unfortunately, there are problems with the library that was used because Android Q introduces a security fix that forbids apps to execute binaries within their data folder. But, this is only a problem if you want to create a “Google Play Store” ready app because you can avoid this problem if compile your app for SDK 28 (and lower). Keep in mind that if you want to create a private app that you distribute on your website instead of using the Play Store you can do this. But, if you want to upload your app to the Google Play Store you have to search for &lt;a href=&quot;https://trac.ffmpeg.org/wiki/CompilationGuide/Android&quot;&gt;another FFmpeg library within the documentation&lt;/a&gt; or build one from scratch so you can compile your app for the current needed SDK.&lt;/p&gt;
&lt;p&gt;Looking to integrate video capabilities into your app? Check out our &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;Video Editor SDK&lt;/a&gt;, &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I hope you enjoyed reading this article and are able to create an Android app that can crop and trim videos. If you have any questions, need help, or want to give feedback @ us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;. We are happy to help.&lt;/p&gt;</content:encoded><dc:creator>Paul</dc:creator><media:content url="https://blog.img.ly/2022/09/Android-trimmimg-with-Kotlin.png" medium="image"/><category>How-To</category><category>Tech</category><category>Android</category><category>Android App Development</category><category>Tutorial</category></item><item><title>Build a Simple Real-Time Video Editor with Metal for iOS</title><link>https://img.ly/blog/build-a-simple-real-time-video-editor-with-metal-for-ios/</link><guid isPermaLink="true">https://img.ly/blog/build-a-simple-real-time-video-editor-with-metal-for-ios/</guid><description>Learn to extract frames from live camera streams, regular movie files and streamed movie files and display them on a MetalKit View. </description><pubDate>Tue, 30 Aug 2022 20:06:49 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial you’ll see how to extract frames from live camera streams, regular movie files and streamed movie files and display them on a MetalKit View. Using Metal allows you to have a great deal of control over how the pixels are rendered and ensures that the GPU is used for rendering, which keeps from slowing down the CPU.&lt;/p&gt;
&lt;p&gt;The tutorial code will always convert the video frames into &lt;code&gt;CIImage&lt;/code&gt; objects before display. This is to help you adapt the code to your own applications. Applying filters, resizing and other effects are fast and easy when working with &lt;code&gt;CIImage&lt;/code&gt;. Additionally, as long as you set your &lt;code&gt;CIContext&lt;/code&gt; to the GPU, you can be sure that your code uses the GPU and the CPU efficiently.&lt;/p&gt;
&lt;p&gt;When working with Video, Apple provides a number of frameworks that operate at different levels. For the higher level frameworks such as AVKit or CoreImage, the system determines when to use the GPU and when to use the CPU for rendering and computation. The higher-level frameworks also offer a tradeoff between ease of use and granularity of control. If you want to have direct access to tell the GPU how to render every pixel, then you will want to use Metal. Remember, though, that you are now responsible for keeping video and audio in sync, encoding and decoding data and determining a reasonable UI for your user. For many use cases, using a higher level framework is probably a better choice. Apple’s engineers have worked to ensure the graphics frameworks use the GPU and CPU in reasonable ways. However, when you need to use Metal, you you need it. So, let’s get started.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-mtkview&quot;&gt;Setting Up a MTKView&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;MTKView&lt;/code&gt; is a subclass of a &lt;code&gt;UIView&lt;/code&gt; so it has a frame and bounds and other properties. Its drawing and rendering is backed directly by drawables and textures rendered on the GPU, so drawing to it can be quite fast. In addition to displaying video, you can render 3D model objects and graphics shaders, so for a graphics rich application it can be a powerful tool.&lt;/p&gt;
&lt;p&gt;Before you can send data to the view it needs to be configured. MetalKit Views are still heavily influenced by UIKit, so if you are working in a SwiftUI project, you will need to wrap them in ViewRepresentable code.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//MetalKit Variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@IBOutlet&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; displayView: MTKView&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; metalDevice : MTLDevice&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; metalCommandQueue : MTLCommandQueue&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//CoreImage Variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; ciContext : CIContext&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; filteredImage: CIImage&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; cleanImage: CIImage&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//get a reference to the GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metalDevice &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MTLCreateSystemDefaultDevice&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//link the GPU to our MTKView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.device &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; metalDevice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//link our command queue variable to the GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metalCommandQueue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; metalDevice.&lt;/span&gt;&lt;span&gt;makeCommandQueue&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//associate our CIContext with the metal stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ciContext &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mtlCommandQueue&lt;/span&gt;&lt;span&gt;: metalCommandQueue)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need to get a reference to the GPU of the iOS device. At least for now, iOS devices only have one GPU. Then you will need to link the &lt;code&gt;displayView&lt;/code&gt; and the &lt;code&gt;metalCommandQueue&lt;/code&gt; to the &lt;code&gt;metalDevice&lt;/code&gt;. Finally we will link the &lt;code&gt;ciContext&lt;/code&gt; to the GPU so that all of our image manipulation code will run in the same place.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//tell our MTKView that we want to call .draw() to make updates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.isPaused &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.enableSetNeedsDisplay &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//let it&apos;s drawable texture be writen to dynamically&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.framebufferOnly &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set our code to be our MTKView&apos;s delegate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.delegate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we set the &lt;code&gt;MTKView&lt;/code&gt; to be in a &lt;code&gt;isPaused&lt;/code&gt; state and do not enable &lt;code&gt;setNeedsDisplay&lt;/code&gt;. This will ensure that it will only redraw when we explicitly tell it to redraw using a &lt;code&gt;.draw()&lt;/code&gt; method in our delegate. By setting &lt;code&gt;framebufferOnly&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; you are telling the view that you will be writing to it multiple times and may also read from it.&lt;/p&gt;
&lt;h2 id=&quot;drawing-in-an-mtkview&quot;&gt;Drawing in an MTKView&lt;/h2&gt;
&lt;p&gt;Now the MTKView is configured. The next step is to update the delegate methods. The &lt;code&gt;MTKViewDelegate&lt;/code&gt; has two methods. If your code needs to respond to the view dimensions changing (to support device rotation, or if you want the user to be able to resize the window) make your adjustments in &lt;code&gt;func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)&lt;/code&gt;. For this example we are only interested in the delegate method: &lt;code&gt;func draw(in view: MTKView)&lt;/code&gt;. The code below draws a &lt;code&gt;CIImage&lt;/code&gt; to the &lt;code&gt;MTKView&lt;/code&gt;. When showing video, we can extract each frame of the video as a &lt;code&gt;CIImage&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a buffer to hold this round of draw commands&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; commandBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; metalCommandQueue.&lt;/span&gt;&lt;span&gt;makeCommandBuffer&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//grab the filtered or a clean image to display&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; ciImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filteredImage &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; cleanImage &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//get a drawable if the GPU is not busy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; currentDrawable &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; view.currentDrawable &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//make sure frame is centered on screen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; heightOfImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ciImage.extent.height&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; heightOfDrawable &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; view.drawableSize.height&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; yOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (heightOfDrawable &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; heightOfImage)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//render into the metal texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.ciContext.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(ciImage,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             to&lt;/span&gt;&lt;span&gt;: currentDrawable.texture,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  commandBuffer&lt;/span&gt;&lt;span&gt;: commandBuffer,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;         bounds&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGPoint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;yOffset),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          size&lt;/span&gt;&lt;span&gt;: view.drawableSize),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     colorSpace&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGColorSpaceCreateDeviceRGB&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//present the drawable and buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;commandBuffer.&lt;/span&gt;&lt;span&gt;present&lt;/span&gt;&lt;span&gt;(currentDrawable)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//send the commands to the GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;commandBuffer.&lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each pass of the draw method, we will create a new &lt;code&gt;MTLCommandBuffer&lt;/code&gt; then we will get the &lt;code&gt;MTKView&lt;/code&gt;’s &lt;code&gt;.currentDrawable&lt;/code&gt; which contains a &lt;code&gt;texture&lt;/code&gt; we can send pixel data to. The texture will have a size and a color space. Though we are using Metal to render images to the screen, Metal can be used to send any valid commands to the GPU for things like computations. Once the buffer has been filled with commands it can be assigned to the drawable and committed. When the buffer is committed, the GPU will execute all of the commands. If another &lt;code&gt;.draw()&lt;/code&gt; gets called while the buffer is being executed, the GPU will not interrupt the current buffer in order to start the new one.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;bounds&lt;/code&gt; of the render allow you to move where the &lt;code&gt;CIImage&lt;/code&gt; gets displayed in the &lt;code&gt;MTKView&lt;/code&gt; and resize the &lt;code&gt;CIImage&lt;/code&gt; within the view. This can be valuable when compositing multiple images or video streams onto the same &lt;code&gt;MTKView&lt;/code&gt;. Remember that unlike a &lt;code&gt;UIView&lt;/code&gt; the origin point is the bottom left of the texture and &lt;code&gt;CIImage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since iOS 11 Apple provides a new lighter weight API for sending renders to the buffer. Use whichever form makes sense to you.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//render into the metal texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; destination &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIRenderDestination&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mtlTexture&lt;/span&gt;&lt;span&gt;: currentDrawable.texture,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                   commandBuffer&lt;/span&gt;&lt;span&gt;: commandBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;do&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.ciContext.&lt;/span&gt;&lt;span&gt;startTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;toRender&lt;/span&gt;&lt;span&gt;: ciImage, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: destination)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; print&lt;/span&gt;&lt;span&gt;(error)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CIRenderDestination&lt;/code&gt; has some other optional parameters such as &lt;code&gt;height&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt; but will always display with an origin of 0,0. So, unlike the earlier call, if you need to rotate or change the dimensions of the image, you will need to do it to the &lt;code&gt;CIImage&lt;/code&gt; earlier in the code. However, if your app always displays the video at full size in the &lt;code&gt;MTKView&lt;/code&gt; this may be easier. Also the &lt;code&gt;startTask&lt;/code&gt; call will return immediately, instead of waiting for other tasks on the CPU to complete.&lt;/p&gt;
&lt;p&gt;It is important to remember that you can have any number of renders in the &lt;code&gt;commandBuffer&lt;/code&gt; before the calls to &lt;code&gt;.present&lt;/code&gt; and &lt;code&gt;.commit&lt;/code&gt;. This means that the same &lt;code&gt;MTKView&lt;/code&gt; can display video from multiple streams as well as static images or animations.&lt;/p&gt;
&lt;h2 id=&quot;working-with-local-files&quot;&gt;Working with Local Files&lt;/h2&gt;
&lt;p&gt;In order to extract individual frames from a local file, we can use an &lt;code&gt;AVAssetReader&lt;/code&gt; to get pixel data to render in the &lt;code&gt;MTKView&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When using higher-level frameworks, a quick way to display a video file for playback is to load it into an &lt;code&gt;AVPlayer&lt;/code&gt;. However, an alternative is to use an &lt;code&gt;AVAssetReader&lt;/code&gt; to read the tracks and the individual frames from the video tracks.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; asset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAsset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;grocery-train&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; reader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try!&lt;/span&gt;&lt;span&gt; AVAssetReader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: asset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; track &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; asset.&lt;/span&gt;&lt;span&gt;tracks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withMediaType&lt;/span&gt;&lt;span&gt;: .video).&lt;/span&gt;&lt;span&gt;last&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; outputSettings: [&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Any&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        kCVPixelBufferPixelFormatTypeKey &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; kCVPixelFormatType_32ARGB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; trackOutput &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetReaderTrackOutput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;track&lt;/span&gt;&lt;span&gt;: track, &lt;/span&gt;&lt;span&gt;outputSettings&lt;/span&gt;&lt;span&gt;: outputSettings)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reader.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(trackOutput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reader.&lt;/span&gt;&lt;span&gt;startReading&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is important that the &lt;code&gt;outputSettings&lt;/code&gt; of the reader match the image format of the video track. If there is a mismatch the color may be off or the reader may be unable to extract a usable buffer. If your code is generating empty or black buffers, the &lt;code&gt;outputSettings&lt;/code&gt; are the first place to troubleshoot. Once the reader starts reading then it can extract pixel buffers. You can use a &lt;code&gt;while&lt;/code&gt; loop to get all of the buffers from the track and send them to the &lt;code&gt;MTKView&lt;/code&gt; for display.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; sampleBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; trackOutput.&lt;/span&gt;&lt;span&gt;copyNextSampleBuffer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; sampleBuffer &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; cvBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span&gt;(sampleBuffer&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(track.preferredTransform)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(sampleBuffer&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputPresentationTimeStamp)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a CIImage out of the CVImageBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  cleanImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cvImageBuffer&lt;/span&gt;&lt;span&gt;: cvBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  displayView.&lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  sampleBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; trackOutput.&lt;/span&gt;&lt;span&gt;copyNextSampleBuffer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, use &lt;code&gt;CMSampleBufferGetImageBuffer&lt;/code&gt; to ensure that we have a valid image. Then assign the image to a &lt;code&gt;CoreImage&lt;/code&gt; and execute the &lt;code&gt;.draw&lt;/code&gt; method of the &lt;code&gt;MTKView&lt;/code&gt;. Afterwards, get the next sample buffer. This will continue until the end of the track, when &lt;code&gt;.copyNextSampleBuffer()&lt;/code&gt; will return &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the code above, there are two &lt;code&gt;print&lt;/code&gt; statements to note some valuable information that your app may want to store for later use. Remember, when working with buffers and the GPU directly, much of the higher-level metadata about the video is lost, so you’ll need to keep track of it in your code if you want to use it later. An image may be rotated because of the way it was originally generated in the video. The &lt;code&gt;preferredTransform&lt;/code&gt; will provide data that your app can use to transform the image to the “proper” orientation before display. Additionally the &lt;code&gt;outputPresentationTimeStamp&lt;/code&gt; tells when the image represented by the buffer appeared in the original video. This can be helpful when you are trying to sync individual frames back to audio tracks or if your app only wants to modify specific frames and then reinsert them into the original clip.&lt;/p&gt;
&lt;h2 id=&quot;working-with-streams&quot;&gt;Working with Streams&lt;/h2&gt;
&lt;p&gt;In addition to local files, an app may want to use a video stream as the input. The process is largely the same, as there is a method to extract a &lt;code&gt;CVPixelBuffer&lt;/code&gt; from an &lt;code&gt;AVPlayer&lt;/code&gt; that is streaming. You can find the complete example code for pushing pixel buffers from a stream to an &lt;code&gt;MTKView&lt;/code&gt; &lt;a href=&quot;https://img.ly/blog/how-to-add-a-filter-to-a-video-stream-in-ios/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;in this blog post about streaming&lt;/a&gt;. However, the important part of the code perhaps looks familiar by now:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; currentTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;itemTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forHostTime&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CACurrentMediaTime&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;hasNewPixelBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forItemTime&lt;/span&gt;&lt;span&gt;: currentTime) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; buffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;copyPixelBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forItemTime&lt;/span&gt;&lt;span&gt;: currentTime, &lt;/span&gt;&lt;span&gt;itemTimeForDisplay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     let&lt;/span&gt;&lt;span&gt; frameImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cvImageBuffer&lt;/span&gt;&lt;span&gt;: buffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     self&lt;/span&gt;&lt;span&gt;.currentFrame &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; frameImage &lt;/span&gt;&lt;span&gt;//a CIImage var&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     self&lt;/span&gt;&lt;span&gt;.videoView.&lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//our MTKView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above uses a &lt;code&gt;CADisplayLink&lt;/code&gt; to query the stream on a regular basis for new video frames. It then extracts a buffer and sends it over the &lt;code&gt;MTKView&lt;/code&gt; for display.&lt;/p&gt;
&lt;h2 id=&quot;working-with-the-cameras&quot;&gt;Working with the Cameras&lt;/h2&gt;
&lt;p&gt;Much like a video stream with an &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; to output pixel buffer data, a standard object to use with the camera is an &lt;code&gt;AVCaptureVideoDataOutput&lt;/code&gt; object. First, you need to set up a standard capture session for either the front or back camera. Then attach a video data output object to the session.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoOutput &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVCaptureVideoDataOutput&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoQueue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DispatchQueue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;captureQueue&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;qos&lt;/span&gt;&lt;span&gt;: .userInteractive)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoOutput.&lt;/span&gt;&lt;span&gt;setSampleBufferDelegate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;queue&lt;/span&gt;&lt;span&gt;: videoQueue)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; captureSession.&lt;/span&gt;&lt;span&gt;canAddOutput&lt;/span&gt;&lt;span&gt;(videoOutput) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  captureSession.&lt;/span&gt;&lt;span&gt;addOutput&lt;/span&gt;&lt;span&gt;(videoOutput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  fatalError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;configuration failed&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever the camera has collected enough data to generate a pixel buffer it will send that to its delegate. The delegate can then convert that data to a &lt;code&gt;CIImage&lt;/code&gt; and send it to the &lt;code&gt;MTKView&lt;/code&gt; to get rendered. The method in a &lt;code&gt;AVCaptureVideoDataOutputSampleBufferDelegate&lt;/code&gt; would look like this.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; captureOutput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; output: AVCaptureOutput, &lt;/span&gt;&lt;span&gt;didOutput&lt;/span&gt;&lt;span&gt; sampleBuffer: CMSampleBuffer, &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; connection: AVCaptureConnection) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a CVImageBuffer from the camera&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; cvBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span&gt;(sampleBuffer) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a CIImage out of the CVImageBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  cleanImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cvImageBuffer&lt;/span&gt;&lt;span&gt;: cvBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  displayView.&lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw how to extract pixel buffers from the camera, local file and remote streams and convert them to &lt;code&gt;CIImage&lt;/code&gt; Then you saw how to render a &lt;code&gt;CIImage&lt;/code&gt; to fill all or part of an &lt;code&gt;MTKView&lt;/code&gt;. If your application needs to resize or apply filters to the images, you can do that before calling the &lt;code&gt;.draw&lt;/code&gt; method of the &lt;code&gt;MTKView&lt;/code&gt;. If the only reason you are considering using &lt;code&gt;MKTViews&lt;/code&gt; is because of a Metal or OpenGL kernel you want to use, consider &lt;a href=&quot;https://img.ly/blog/how-to-create-image-filters-in-ios/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;our tutorial on how to wrap kernels into Core Image filters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If your application needs to gather streams of video, filter and then render them to the screen with precision, then drawing to an &lt;code&gt;MTKView&lt;/code&gt; may be sufficient. However, if you want to let your users dictate how and where to filter the streams, then you may want to consider an SDK like &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt; or &lt;a href=&quot;https://img.ly/products/creative-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;CreativeEditor SDK&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/08/real-time-video-editor-with-Metal-for-iOS.png" medium="image"/><category>How-To</category><category>Tech</category><category>iOS</category><category>iOS Metal</category><category>Tutorial</category></item><item><title>How to Stream Videos Using Javascript and HTML5</title><link>https://img.ly/blog/how-to-stream-videos-using-javascript-and-html5/</link><guid isPermaLink="true">https://img.ly/blog/how-to-stream-videos-using-javascript-and-html5/</guid><description>Learn how to stream videos right in your browser using only JavaScript and HTML5 APIs.</description><pubDate>Mon, 29 Aug 2022 20:36:00 GMT</pubDate><content:encoded>&lt;p&gt;If you would tell someone from the early 2000s what you’re about to do, they would be amazed and would give everything to understand how you did it. Because back then HTML was much simpler and didn’t even support video playback. So to stream your video inside of your browser you were forced to use some third-party services like Flash or Silverlight. Now we have HTML5 and new versions of JavaScript, and by combining these two new technologies you’ll end up with results that rival native applications. Streaming video inside of your web application nowadays is a breeze.&lt;/p&gt;
&lt;h2 id=&quot;the-video-tag&quot;&gt;The &lt;code&gt;&amp;#x3C;video&gt;&lt;/code&gt; tag&lt;/h2&gt;
&lt;p&gt;You can easily start streaming your own video by using only one tag in HTML5. I’m talking about the &lt;code&gt;&amp;#x3C;video&gt;&lt;/code&gt; tag. It embeds a fully-fledged media player in the document and similar to the &lt;code&gt;img&lt;/code&gt; tag accepts a path to the media we want to play inside the &lt;code&gt;src&lt;/code&gt; attribute as well as accepting parameters for &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In addition, there are video-specific properties such as whether the video should loop or autoplay and whether to display the default browser controls.&lt;/p&gt;
&lt;p&gt;To handle the case of browsers not supporting the &lt;code&gt;video&lt;/code&gt; tag you can provide fallback content inside the &lt;code&gt;&amp;#x3C;video&gt;&amp;#x3C;/video&gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/fast-rgb-03tbl5?fontsize=14&amp;#x26;hidenavigation=1&amp;#x26;theme=dark&quot; title=&quot;fast-rgb-03tbl5&quot; allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot; sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;combining-with-javascript&quot;&gt;Combining with JavaScript&lt;/h2&gt;
&lt;p&gt;You can also combine this simple video streaming in HTML with JavaScript and make it more professional and be able to control your streaming manually. When you get an element from the DOM of a &lt;code&gt;video&lt;/code&gt; tag, the object that you get exposes various methods and attributes which can be used for manipulating media content. Firstly, we need to access the DOM and declare an object which will be used for our manipulations. Now we can use &lt;code&gt;myVideo&lt;/code&gt; to programmatically pause, change the playback rate and current time of the video.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myVideo&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#video1&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//pause the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;myVideo.&lt;/span&gt;&lt;span&gt;pause&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Change the speed of playback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;myVideo.playbackRate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 3.0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Change current time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;myVideo.currentTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, most videos that we see on the web today such as YouTube, Twitch, or any other social media display much more complex behaviors than just changing playback speed or pausing the video. On YouTube, you can change the quality of your video, add subtitles, or even add features like autoplay if you have several other videos queued up and don’t want to switch it manually.&lt;/p&gt;
&lt;p&gt;All those services actually still use a video tag. But instead of streaming a video file through the “src” attribute inside of your tag, they use powerful tools such as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API&quot;&gt;Media Source Extensions&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP&quot;&gt;other adaptive streaming API&lt;/a&gt; that will help stream your video more efficiently.&lt;/p&gt;
&lt;h2 id=&quot;what-are-media-source-extensions-mse&quot;&gt;What are Media Source Extensions (MSE)&lt;/h2&gt;
&lt;p&gt;Media Source Extensions is a W3C specification that allows JavaScript to send streams to media codecs within Web browsers that support HTML5 video and audio that most browsers implement today. In other words, it allows JavaScript to generate streams and facilitate a variety of use cases like adaptive streaming and time-shifting live streams. It’s quite an advanced solution if you want to make your video playback more customizable and professional. MSE gives us finer grained control over how much and how often content is fetched, and some control over memory usage details, such as when buffers are evicted.&lt;/p&gt;
&lt;p&gt;Wrap your resource via the Media Source Extensions API instead of dealing with the URL directly (We’ll talk more closely about it a little bit later).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;709&quot; src=&quot;https://img.ly/_astro/MediaSource_fyAaQ.webp&quot; srcset=&quot;/_astro/MediaSource_Z23x9ON.webp 640w, /_astro/MediaSource_1myAEA.webp 750w, /_astro/MediaSource_pO5aO.webp 828w, /_astro/MediaSource_1MlG3T.webp 1080w, /_astro/MediaSource_1cjHTR.webp 1280w, /_astro/MediaSource_GJoDh.webp 1668w, /_astro/MediaSource_fyAaQ.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;this-specification-was-designed-with-the-following-goals&quot;&gt;This specification was designed with the following goals:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Let JavaScript create a media stream regardless of how the media is fetched.&lt;/li&gt;
&lt;li&gt;Leverage the browser cache as much as possible.&lt;/li&gt;
&lt;li&gt;Provide requirements for byte stream format specifications.&lt;/li&gt;
&lt;li&gt;Minimize the need for media parsing in JavaScript.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As I mentioned, we are still using HTML5 video tags, and even more surprising is that we’ll still use the “src” attribute. Only this time, instead of adding a link to the video, add a link to the MediaSource object.&lt;/p&gt;
&lt;p&gt;If you’re a little bit confused, then don’t worry, I’ll explain everything. To enable this type of use case, the W3C has defined a static method &lt;code&gt;URL.createObjectURL&lt;/code&gt;. You can use this API to create a URL that directly points to a JavaScript object created on the client, rather than actually pointing to an online resource.&lt;/p&gt;
&lt;p&gt;You can see how it works in this example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoTag&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;my-video&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// creating the MediaSource, just with the &quot;new&quot; keyword, and the URL for it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myMediaSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; MediaSource&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(myMediaSource);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// attaching the MediaSource to the video tag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoTag.src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; url;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that you have a MediaSource, what should you do? That’s not the only MSE specification. It also defines another concept, Source Buffers.&lt;/p&gt;
&lt;h2 id=&quot;source-buffers&quot;&gt;*&lt;strong&gt;*Source Buffers**&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The SourceBuffer interface &lt;strong&gt;represents a chunk of media to be passed into an HTMLMediaElement and played via a MediaSource object.&lt;/strong&gt; To put the video directly into the MediaSource for playback, you’ll need Source Buffers. MediaSource contains one or more instances of it. Each is associated with a content type, such as &lt;code&gt;Audio&lt;/code&gt;, &lt;code&gt;Video&lt;/code&gt;, or both of them at the same time. Source buffers are all associated with a single MediaSource and are used in JavaScript to add video data directly to HTML5 video tags. Separating video and audio allows managing them separately on the server. Doing so has several advantages such as control over your traffic and increasing performance. This is how it works:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// -- Create a MediaSource and attach it to the video (We already learned about that) --&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoTag&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;my-video&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myMediaSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; MediaSource&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(myMediaSource);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoTag.src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; url;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 1. add source buffers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; audioSourceBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; myMediaSource.&lt;/span&gt;&lt;span&gt;addSourceBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;audio/mp4; codecs=&quot;mp4a.40.2&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoSourceBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; myMediaSource.&lt;/span&gt;&lt;span&gt;addSourceBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;video/mp4; codecs=&quot;avc1.64001e&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// 2. download and add our audio/video to the SourceBuffers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// for the audio SourceBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&amp;#x3C;http://server.com/audio.mp4&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // The data has to be a JavaScript ArrayBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;audioData&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    audioSourceBuffer.&lt;/span&gt;&lt;span&gt;appendBuffer&lt;/span&gt;&lt;span&gt;(audioData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// the same for the video SourceBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&amp;#x3C;http://server.com/video.mp4&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // The data has to be a JavaScript ArrayBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;videoData&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videoSourceBuffer.&lt;/span&gt;&lt;span&gt;appendBuffer&lt;/span&gt;&lt;span&gt;(videoData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;adaptive-streaming&quot;&gt;*&lt;strong&gt;*Adaptive Streaming**&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Many video players have an “auto quality” feature that automatically selects quality based on network speed. If you have a slow internet connection then you’ll end up watching a video in the lowest resolution (probably 360p or even lower), on the other hand, if the connection is good then the video will be in the highest resolution (probably 1080p or even higher). It depends on how many options the specific video has. In addition, your hardware capability is also taken into account.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1920px) 1920px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1920&quot; height=&quot;929&quot; src=&quot;https://img.ly/_astro/StreamingVideo_Z283hMX.webp&quot; srcset=&quot;/_astro/StreamingVideo_ZR8DDM.webp 640w, /_astro/StreamingVideo_1MDLQg.webp 750w, /_astro/StreamingVideo_2m6JDQ.webp 828w, /_astro/StreamingVideo_20Qkbx.webp 1080w, /_astro/StreamingVideo_Z1U1FVk.webp 1280w, /_astro/StreamingVideo_Z1iNCGF.webp 1668w, /_astro/StreamingVideo_Z283hMX.webp 1920w&quot;&gt;&lt;/p&gt;
&lt;p&gt;On the server side, we create different segments for every quality we want to support. For example, we would put the following files on our server:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;./video/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ├── ./360p/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  |     ├── video_0.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  |     ├── video_1.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  |     └── video_2.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	├── ./720p/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  |     ├── video_0.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  |     ├── video_1.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  |     └── video_2.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  └── ./1080p/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ├── video_0.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ├── video_1.mp4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        └── video_2.mp4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The web player then automatically selects the correct video file to load when the network or CPU state changes. This is entirely done in JavaScript. For video segments, it could, for example, look as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * We first derive the desired quality from the bandwidth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * Then pass it to the pushVideoSegment()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * which fetches each segment with the desired quality in turn and returns a promise&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;**/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; pushVideoSegment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;nb&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;wantedQuality&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;&amp;#x3C;http://my-server/video/&gt;&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; wantedQuality &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;/segment&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; nb &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;.mp4&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; videoSourceBuffer.&lt;/span&gt;&lt;span&gt;appendBuffer&lt;/span&gt;&lt;span&gt;(arrayBuffer);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * Translate an estimated bandwidth to the right audio&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * quality as defined on server-side.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * &lt;/span&gt;&lt;span&gt;@param&lt;/span&gt;&lt;span&gt; {number}&lt;/span&gt;&lt;span&gt; bandwidth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; * &lt;/span&gt;&lt;span&gt;@returns&lt;/span&gt;&lt;span&gt; {string}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; fromBandwidthToQuality&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bandwidth&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; return&lt;/span&gt;&lt;span&gt; bandwidth &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; 320e3&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; &quot;360p&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;720p&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// first estimate the bandwidth. Most often, this is based on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// the time it took to download the last segments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; bandwidth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; estimateBandwidth&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; quality&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fromBandwidthToQuality&lt;/span&gt;&lt;span&gt;(bandwidth);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pushVideoSegment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, quality)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; pushVideoSegment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, quality))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; .&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; pushVideoSegment&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, quality));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Congratulations, you’ve learned multiple ways how to stream a video file into your browser. We have explored several tools that are helping you not just change some video parameters by using vanilla JS and HTML, and also Media Source Extensions (MSE) that are helping all developers around the world to build professional web players which you’re using yourself, such as YouTube, Twitch, TikTok and many others.&lt;/p&gt;
&lt;p&gt;If you want to give your users the ability to also edit videos within your mobile application check out our &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;Video Editor SDK&lt;/a&gt; offering features such as trimming, transforms, video composition and more. It’s fully customizable to match the look and feel of your app and the most performant solution on the market.&lt;/p&gt;
&lt;p&gt;Looking for more video capabilities? Check out our solutions for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;If you have questions, feedback or comments reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Mark</dc:creator><media:content url="https://blog.img.ly/2022/08/stream-videos-using-Javascript-and-HTML5.png" medium="image"/><category>How-To</category><category>Tech</category><category>Video Editing</category><category>Videos</category><category>Tutorial</category></item><item><title>How To Build a Canva Clone with CE.SDK</title><link>https://img.ly/blog/how-to-build-a-canva-clone-with-ce-sdk/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-a-canva-clone-with-ce-sdk/</guid><description>Design invitations, greeting cards, flyers, postcards, and business cards with a Canva clone built with CE.SDK in React in minutes. </description><pubDate>Wed, 27 Jul 2022 06:43:59 GMT</pubDate><content:encoded>&lt;p&gt;Canva has popularized image editing, and user expectations of creative capabilities have increased accordingly. If your web or mobile application includes any design functionality - for book covers, t-shirt designs, or social media content - it has to be on par with the design experience offered by prosumer creation tools such as Canva.&lt;/p&gt;
&lt;p&gt;Allowing users to edit images through an easy user interface, define high-quality templates, and give them the ability to share them with the community is a great way to boost engagement, add virality, and potentially new revenue streams.&lt;/p&gt;
&lt;p&gt;You might think this requires a lot of time and effort, but that is not the case. CreativeEditor SDK makes it dead simple to build a Canva-like design editor in minutes. I’ll show you how!&lt;/p&gt;
&lt;p&gt;Follow this step-by-step tutorial and learn how to implement a Canva clone in React with CE.SDK. At the end of this tutorial, you will achieve the following result:&lt;/p&gt;
&lt;p&gt;Try the final result live on &lt;a href=&quot;https://codesandbox.io/embed/how-to-build-a-canva-clone-with-ce-sdk-forked-nvxz3w&quot;&gt;CodeSandbox&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-canva&quot;&gt;What is Canva?&lt;/h2&gt;
&lt;p&gt;Canva is a free graphic design tool for image editing featuring a drag-and-drop interface for composing different elements on a canvas. You can easily create flyers, invitations, business cards, and more with professionally designed templates. You can think of it as a basic version of Photoshop that anyone can use.&lt;/p&gt;
&lt;p&gt;You can create Canva templates in a web browser or on the official app for iOS or Android, share them and collaborate easily. Templates make Canva a powerful tool - that is also true for CE.SDK, which makes it a perfect harbor to set sail for new horizons and create an excellent editor. Before we learn how to replicate Canva, let us evaluate the SDK in question.&lt;/p&gt;
&lt;h2 id=&quot;what-is-cesdk&quot;&gt;What is CE.SDK?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/products/creative-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;CreativeEditor SDK (CE.SDK)&lt;/a&gt; is a fully customizable, user-friendly design editor tool. You can integrate it easily into your application with just a few lines of code and benefit from the template-based editor in no time.&lt;/p&gt;
&lt;p&gt;The role-specific editing UI focuses heavily on content adaptation. CE.SDK offers two modes: in Creator Mode, you can create a design from scratch or customize existing templates. Once ready, creators can share their templates and decide which elements others can change and to what degree. Then, users can import the template and customize it in Adopter Mode (Default UI).&lt;/p&gt;
&lt;p&gt;Try CE.SDK &lt;a href=&quot;https://img.ly/showcases/cesdk&quot;&gt;live demos&lt;/a&gt; or &lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;start a free trial&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;designing-and-sharing-a-template-in-creator-mode&quot;&gt;Designing and Sharing a Template in Creator Mode&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://img.ly/docs/cesdk/react/configuration-2c1c3d/&quot;&gt;&lt;code&gt;Creator&lt;/code&gt;&lt;/a&gt; Mode is the most powerful and least restrictive role in CE.SDK. It empowers you to unleash your creativity and create custom templates for every imaginable use case. Add, move, modify, and delete elements as you wish and define constraints. Further elevate your designs with photo editings, such as filters, effects, and background removal.&lt;/p&gt;
&lt;p&gt;The well-rounded text editor also allows you to define variables and programmatically give them a value using the CE.SDK &lt;a href=&quot;https://img.ly/docs/cesdk/react/create-templates/add-dynamic-content/text-variables-7ecb50/&quot;&gt;Variable API&lt;/a&gt;. For example, you may introduce a &lt;code&gt;{{Name}}&lt;/code&gt; Variable and have CESDK import names from a database. This automation is perfect for batch-processing designs, such as greeting cards. Based on this principle, we have built an example &lt;a href=&quot;https://img.ly/blog/how-to-generate-an-nft-art-collection-with-react-using-ce-sdk/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;NFT Art Collection Generator in React with CE.SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the Creator Mode, you also can define &lt;a href=&quot;https://img.ly/docs/cesdk/react/create-templates/add-dynamic-content/placeholders-d9ba8a/&quot;&gt;placeholders&lt;/a&gt;. Turning an element into a placeholder will let you determine if it can be deleted, styled, or duplicated by the adopter.&lt;/p&gt;
&lt;p&gt;This is what the CE.SDK Creator Mode looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;creator-mode-cesdk&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1000px) 1000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1000&quot; height=&quot;506&quot; src=&quot;https://img.ly/_astro/creator-mode-cesdk_Z1H6JCC.webp&quot; srcset=&quot;/_astro/creator-mode-cesdk_Q0z9g.webp 640w, /_astro/creator-mode-cesdk_1WU4FC.webp 750w, /_astro/creator-mode-cesdk_Z1Ww1nc.webp 828w, /_astro/creator-mode-cesdk_Z1H6JCC.webp 1000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, you can use CE.SDK to easily create a wedding invitation card template. This is just an example, and you can find other cool sample templates for greeting cards, flyers, postcards, and business cards on &lt;a href=&quot;https://img.ly/showcases/cesdk?tags=custom-build-uis&quot;&gt;showcase page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-adopter-mode-offers-you&quot;&gt;What Adopter Mode Offers You&lt;/h3&gt;
&lt;p&gt;You can test the adopter view of the wedding invitation card template shown above &lt;a href=&quot;https://ubique.img.ly/main/apps/dashboard/#/template/Hoc0dPRe5l9BfJaqwCI3&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is what the Adopter Mode looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;adopter-mode&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 999px) 999px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;999&quot; height=&quot;485&quot; src=&quot;https://img.ly/_astro/adopter-mode_Z1qq5LI.webp&quot; srcset=&quot;/_astro/adopter-mode_gAVDi.webp 640w, /_astro/adopter-mode_Z1ThvMI.webp 750w, /_astro/adopter-mode_Z22GG6v.webp 828w, /_astro/adopter-mode_Z1qq5LI.webp 999w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As shown above, CE.SDK’s Adopter Mode enables you to add and modify colors, text, and images based on the constraints enabled by the creator of the template. This mode provides the &lt;a href=&quot;https://img.ly/docs/cesdk/js/configuration-2c1c3d/?ref=img.ly#adopter?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;&lt;code&gt;Adopter&lt;/code&gt;&lt;/a&gt; users with a simple interface, narrowed down to the properties that they are allowed to change. This prevents adopters from accidentally changing or deleting parts of a design that should not be modified.&lt;/p&gt;
&lt;h2 id=&quot;implement-a-canva-clone-with-cesdk&quot;&gt;Implement a Canva Clone with CE.SDK&lt;/h2&gt;
&lt;p&gt;Let’s now learn how to use CE.SDK to build a Canva clone in React.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;This is the list of all the prerequisites for the Canva clone application you are going to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/getting-started/&quot;&gt;Node.js and npm 8.0+ and higher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/&quot;&gt;React&lt;/a&gt; &gt;= 18&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@cesdk/cesdk-js&quot;&gt;&lt;code&gt;@cesdk/cesdk-js&lt;/code&gt;&lt;/a&gt; &gt; 1.6.3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add &lt;code&gt;@cesdk/cesdk-js&lt;/code&gt; to your project’s dependencies with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install @cesdk/cesdk-js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;initialize-a-react-project&quot;&gt;Initialize a React Project&lt;/h3&gt;
&lt;p&gt;You can try out the Canva clone by cloning the &lt;a href=&quot;https://github.com/imgly/canva-clone-react-cesdk&quot;&gt;GitHub repository supporting the article&lt;/a&gt; and running the demo web application with the following commands:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/canva-clone-cesdk-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd canva-clone-cesdk-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, follow this step-by-step tutorial and learn how to build the Canva clone with CE.SDK by yourself.&lt;/p&gt;
&lt;p&gt;First, you need to initialize a &lt;code&gt;canva-clone-cesdk-imgly&lt;/code&gt; React project. You can do it with &lt;a href=&quot;https://create-react-app.dev/docs/getting-started/&quot;&gt;Create React App&lt;/a&gt; by launching the command below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npx create-react-app canva-clone-cesdk-imgly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the &lt;code&gt;canva-clone-cesdk-imgly&lt;/code&gt; folder will have the following file structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;canva-clone-cesdk-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── node_modules&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── public&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── favicon.ico&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── index.html&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── logo192.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── logo512.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   ├── manifest.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── robots.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── App.test.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── index.css&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── logo.svg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ├── reportWebVitals.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── setupTests.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move into the &lt;code&gt;canva-clone-cesdk-imgly&lt;/code&gt; folder and start a local development server with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd canva-clone-cesdk-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reach &lt;a href=&quot;http://localhost:3000/&quot;&gt;http://localhost:3000/&lt;/a&gt; in your browser and make sure you see the default Create React App screen below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;react&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 983px) 983px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;983&quot; height=&quot;728&quot; src=&quot;https://img.ly/_astro/react_1REyVa.webp&quot; srcset=&quot;/_astro/react_2lRYbq.webp 640w, /_astro/react_Z21FBPj.webp 750w, /_astro/react_OLsOd.webp 828w, /_astro/react_1REyVa.webp 983w&quot;&gt;&lt;/p&gt;
&lt;p&gt;You are now ready to code!&lt;/p&gt;
&lt;h3 id=&quot;build-the-canva-clone-component-with-cesdk&quot;&gt;Build the Canva Clone Component with CE.SDK&lt;/h3&gt;
&lt;p&gt;If you want your image editing component to provide a Canva-like experience, it must include key features, such as template and custom resource management. Let’s see how to implement them all with CE.SDK.&lt;/p&gt;
&lt;h4 id=&quot;configure-cesdk-to-use-templates&quot;&gt;Configure CE.SDK to use templates&lt;/h4&gt;
&lt;p&gt;Managing templates in CE.SDK is easy. All you have to do is configure the set of predefined templates loaded by CE.SDK on initialization, as explained in the &lt;a href=&quot;https://img.ly/docs/cesdk/react/use-templates/library-b3c704/&quot;&gt;official documentation on adding templates&lt;/a&gt;. You can achieve this as below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; cesdk;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // initializing CE.SDK with a few templates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    presets: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      templates: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        postcard_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Postcard Design&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        postcard_2: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Postcard Tropical&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_2.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_2.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        business_card_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Business card&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        instagram_photo_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Instagram photo&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_photo_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_photo_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        instagram_story_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Instagram story&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_story_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_story_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        poster_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Poster&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_poster_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_poster_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        presentation_4: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Presentation&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_presentation_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_presentation_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        collage_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Collage&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_collage_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_collage_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (cesdkContainer.current) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    CreativeEditorSDK.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(cesdkContainer.current, config).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      cesdk &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; instance;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (cesdk) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      cesdk.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}, [cesdkContainer]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the &lt;code&gt;presets.templates&lt;/code&gt;, you can specify all the CE.SDK templates to be made available to users.&lt;br&gt;
Note that the &lt;code&gt;cesdkContainer&lt;/code&gt; variable stores the &lt;a href=&quot;https://legacy.reactjs.org/docs/refs-and-the-dom.html&quot;&gt;React reference&lt;/a&gt; to the div where to mount CE.SDK.&lt;/p&gt;
&lt;p&gt;This is what the CE.SDK template section will look like:&lt;br&gt;
&lt;img alt=&quot;mount&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1866px) 1866px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1866&quot; height=&quot;893&quot; src=&quot;https://img.ly/_astro/mount_iSSUR.webp&quot; srcset=&quot;/_astro/mount_2jvRJt.webp 640w, /_astro/mount_Zh1IIF.webp 750w, /_astro/mount_aEtiJ.webp 828w, /_astro/mount_Z2irfxL.webp 1080w, /_astro/mount_1YRGvg.webp 1280w, /_astro/mount_CvNzu.webp 1668w, /_astro/mount_iSSUR.webp 1866w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, all eight templates loaded with &lt;code&gt;presets.templates&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;integrate-external-asset-sources-into-cesdk&quot;&gt;Integrate External Asset Sources Into CE.SDK&lt;/h4&gt;
&lt;p&gt;CE.SDK supports external asset sources, meaning you can give your users a vast library of resources to supercharge their creative workflows. Instead of spending time building a repository of images manually, you can provide your users with image resources retrieved dynamically by connecting to services like Unsplash and Airtable. Learn &lt;a href=&quot;https://img.ly/docs/cesdk/react/import-media/asset-panel/customize-c9a4de/&quot;&gt;how to integrate external asset sources in CE.SDK&lt;/a&gt;, or see a live demo based on the &lt;a href=&quot;https://img.ly/showcases/cesdk/unsplash-image-assets/web?c_asset_library=airtable&quot;&gt;Unsplash and Airtable integration&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;add-custom-images-to-cesdk&quot;&gt;Add Custom Images to CE.SDK&lt;/h4&gt;
&lt;p&gt;You can load custom resources and make them available to users with CE.SDK. For example, you would like to add this &lt;a href=&quot;https://www.flaticon.com/free-icon/programming_1208884&quot;&gt;Flaticon&lt;/a&gt; below to the available images in CE.SDK:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;icon&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 512px) 512px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;512&quot; height=&quot;512&quot; src=&quot;https://img.ly/_astro/icon_7W96.webp&quot; srcset=&quot;/_astro/icon_7W96.webp 512w&quot;&gt;&lt;/p&gt;
&lt;p&gt;All you need to do is define a new asset source as explained in the documentation page on &lt;a href=&quot;https://img.ly/docs/cesdk/react/import-media/asset-panel/customize-c9a4de/&quot;&gt;Integrating Custom Resources into CE.SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can achieve this as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // path to the local image to load into CE.SDK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; customImagePath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;protocol&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; &apos;//&apos;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;host&lt;/span&gt;&lt;span&gt;}/resources/programming.png`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; cesdk;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // loading the business card template as default template&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    initialSceneURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // loading the external asset sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    assetSources: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // loading a custom image into CE.SDK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      custom: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        findAssets&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            assets: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                id: &lt;/span&gt;&lt;span&gt;&apos;custom-image-1&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                type: &lt;/span&gt;&lt;span&gt;&apos;ly.img.image&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                locale: &lt;/span&gt;&lt;span&gt;&apos;en&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                label: &lt;/span&gt;&lt;span&gt;&apos;Programming&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                thumbUri: customImagePath,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                size: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  width: &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  height: &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                meta: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  uri: customImagePath,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                context: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  sourceId: &lt;/span&gt;&lt;span&gt;&apos;custom&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                credits: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  name: &lt;/span&gt;&lt;span&gt;&apos;Freepik&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  url: &lt;/span&gt;&lt;span&gt;&apos;https://www.flaticon.com/free-icon/programming_1208884?related_id=1208782&amp;#x26;origin=search&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            currentPage: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            total: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            nextPage: &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // translating the labels associates with the external asset sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    i18n: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      en: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &apos;libraries.custom.label&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Custom&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // initializing CE.SDK with a few templates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    presets: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      templates: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        business_card_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          label: &lt;/span&gt;&lt;span&gt;&apos;Business card&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (cesdkContainer.current) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    CreativeEditorSDK.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(cesdkContainer.current, config).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      cesdk &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; instance;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (cesdk) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      cesdk.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}, [cesdkContainer]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;customImagePath&lt;/code&gt; variable stores the path to the local image file &lt;code&gt;programming.png&lt;/code&gt; located in the &lt;code&gt;public/resources&lt;/code&gt; folder of the React project. This variable is used in the &lt;code&gt;findAssets()&lt;/code&gt; function. This function is important because it defines the complete asset source. In other words, you only need one function to implement the custom asset retrieval logic. Note that providing images with labels is useful to make them searchable through the CE.SDK UI.&lt;/p&gt;
&lt;p&gt;Now, users will be able to use the image in their template.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;icon-template-ce-sdk-create-canva-yourself&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1000px) 1000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1000&quot; height=&quot;474&quot; src=&quot;https://img.ly/_astro/icon-template-ce-sdk-create-canva-yourself_1F6dsS.webp&quot; srcset=&quot;/_astro/icon-template-ce-sdk-create-canva-yourself_SgMiL.webp 640w, /_astro/icon-template-ce-sdk-create-canva-yourself_Z1zWsKX.webp 750w, /_astro/icon-template-ce-sdk-create-canva-yourself_wOR2.webp 828w, /_astro/icon-template-ce-sdk-create-canva-yourself_1F6dsS.webp 1000w&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;tie-it-together&quot;&gt;Tie it Together&lt;/h4&gt;
&lt;p&gt;This is what the final &lt;code&gt;CanvaClone&lt;/code&gt; component looks like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &apos;./CanvaClone.css&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; CreativeEditorSDK &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@cesdk/cesdk-js&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React, { useEffect, useRef } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { findAirtableAssets } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./airtableAssetLibrary&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { findUnsplashAssets } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./unsplashAssetLibrary&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; CanvaClone&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // initializing Airtable as default external asset library&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  assetLibrary&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;airtable&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cesdkContainer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; externalAssetSources&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ...&lt;/span&gt;&lt;span&gt;(assetLibrary &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;airtable&apos;&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        airtable: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          findAssets: findAirtableAssets,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          credits: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            name: &lt;/span&gt;&lt;span&gt;&apos;Airtable&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            url: &lt;/span&gt;&lt;span&gt;&apos;https://airtable.com/shr4x8s9jqaxiJxm5/tblSLR9GBwiVwFS8z?backgroundColor=orange&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ...&lt;/span&gt;&lt;span&gt;(assetLibrary &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;unsplash&apos;&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        unsplash: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          findAssets: findUnsplashAssets,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          credits: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            name: &lt;/span&gt;&lt;span&gt;&apos;Unsplash&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            url: &lt;/span&gt;&lt;span&gt;&apos;https://unsplash.com/&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          license: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            name: &lt;/span&gt;&lt;span&gt;&apos;Unsplash license (free)&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            url: &lt;/span&gt;&lt;span&gt;&apos;https://unsplash.com/license&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // path to the local image to load into CE.SDK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; customImagePath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;protocol&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; &apos;//&apos;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;host&lt;/span&gt;&lt;span&gt;}/resources/programming.png`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; cesdk;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // loading the business card template as default template&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      initialSceneURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // loading the external asset sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      assetSources: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // loading the AirTable or Unsplash asset library&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ...&lt;/span&gt;&lt;span&gt;externalAssetSources,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // loading a custom image into CE.SDK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        custom: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          findAssets&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              assets: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  id: &lt;/span&gt;&lt;span&gt;&apos;custom-image-1&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  type: &lt;/span&gt;&lt;span&gt;&apos;ly.img.image&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  locale: &lt;/span&gt;&lt;span&gt;&apos;en&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  label: &lt;/span&gt;&lt;span&gt;&apos;Programming&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  thumbUri: customImagePath,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  size: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    width: &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    height: &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  meta: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    uri: customImagePath,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  context: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    sourceId: &lt;/span&gt;&lt;span&gt;&apos;custom&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  credits: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    name: &lt;/span&gt;&lt;span&gt;&apos;Freepik&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    url: &lt;/span&gt;&lt;span&gt;&apos;https://www.flaticon.com/free-icon/programming_1208884?related_id=1208782&amp;#x26;origin=search&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              currentPage: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              total: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              nextPage: &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // translating the labels associates with the external asset sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      i18n: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        en: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &apos;libraries.airtable.label&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Airtable&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &apos;libraries.unsplash.label&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Unsplash&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &apos;libraries.custom.label&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;Custom&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // initializing CE.SDK with a few templates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      presets: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        templates: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          postcard_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Postcard Design&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          postcard_2: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Postcard Tropical&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_2.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_postcard_2.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          business_card_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Business card&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_business_card_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          instagram_photo_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Instagram photo&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_photo_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_photo_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          instagram_story_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Instagram story&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_story_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_instagram_story_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          poster_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Poster&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_poster_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_poster_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          presentation_4: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Presentation&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_presentation_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_presentation_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          collage_1: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            label: &lt;/span&gt;&lt;span&gt;&apos;Collage&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            scene: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_collage_1.scene`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            thumbnailURL: &lt;/span&gt;&lt;span&gt;`https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/templates/cesdk_collage_1.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (cesdkContainer.current) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      CreativeEditorSDK.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(cesdkContainer.current, config).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        (&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          cesdk &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; instance;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (cesdk) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        cesdk.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [cesdkContainer, assetLibrary]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;caseContainer&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrapper&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{cesdkContainer} &lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;cesdk&quot;&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; CanvaClone;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;findAirtableAssets()&lt;/code&gt; and &lt;code&gt;findUnsplashAssets()&lt;/code&gt; functions come from the &lt;a href=&quot;https://github.com/imgly/cesdk-web-examples/tree/showcases/showcase-custom-asset-libraries&quot;&gt;GitHub repo&lt;/a&gt; associated with the &lt;a href=&quot;https://img.ly/showcases/cesdk/unsplash-image-assets/web?c_asset_library=airtable&quot;&gt;Unsplash and Airtable integration showcase page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These functions allow CE.SDK to dynamically retrieve resources from online services. A search for images in the CE.SDK UI will perform a query on Airtable or Unsplash and provides them to your users. With just a few lines of code, you are making massive resources available to your users.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Canva has rapidly become one of the most popular graphic design tools. It is so prevalent that users expect similar features in your web and mobile applications. Building a Canva clone from scratch would take months, but with CreativeEditor SDK, it only takes minutes.&lt;/p&gt;
&lt;p&gt;In this article, we used CE.SDK to initialize an advanced design editor in React. We adopted its API to configure and customize a design editor to build a Canva clone. This would not be possible without the intuitive UI offered by CE.SDK and its advanced features, such as &lt;a href=&quot;https://img.ly/showcases/cesdk/placeholders/web?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;placeholders&lt;/a&gt; and the possibility to define &lt;a href=&quot;https://img.ly/showcases/cesdk/design-validation/web?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;validation rules&lt;/a&gt; to guide your users’ creation process.&lt;/p&gt;
&lt;p&gt;Here we only scratched the surface of what is possible with CE.SDK – you can use its powerful image processing API to implement many more features and make your Canva clone more and more complex. This is only the beginning!&lt;/p&gt;
&lt;p&gt;See how &lt;a href=&quot;https://img.ly/canva-alternative&quot;&gt;IMG.LY compares to Canva Connect API&lt;/a&gt; and check out all the use cases of &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt; or &lt;a href=&quot;https://img.ly/forms/contact-sales&quot;&gt;contact sales&lt;/a&gt; to learn more!&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2022/07/create-an-app-like-canva.png" medium="image"/><category>How-To</category><category>Canva</category><category>Design Editor</category><category>Creative Editor</category><category>Web Application</category><category>Web Development</category><category>React</category><category>Tutorial</category><category>Learning</category></item><item><title>How to Remove Backgrounds Using Core ML</title><link>https://img.ly/blog/how-to-remove-backgrounds-using-coreml/</link><guid isPermaLink="true">https://img.ly/blog/how-to-remove-backgrounds-using-coreml/</guid><description>Bring background removal and replacement to your iOS application.</description><pubDate>Tue, 28 Jun 2022 15:26:53 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial, you will learn how to use machine learning to identify an image background and mask it out. This is particularly useful for making stickers or avatars or adding a fake background to a video call. The process of assigning the pixels in an image to a specific object is called “segmentation”. Apple provides an optimized method with pictures of people in its &lt;a href=&quot;https://developer.apple.com/documentation/vision&quot;&gt;Vision framework&lt;/a&gt;. For performing the same tasks with non-human subjects, you can use the &lt;a href=&quot;https://github.com/tensorflow/models/tree/master/research/deeplab&quot;&gt;DeepLabV3 machine learning model with Core ML&lt;/a&gt;. The code examples in this tutorial have been tested using Xcode 13 and Swift 5. Because of the use of Core ML, Vision, and CoreImage in this tutorial, you should run the demo code on a device, not on the Simulator. An iOS project with the demo code is &lt;a href=&quot;https://github.com/waltertyree/super-eureka&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Image segmentation is a different process and requires other machine learning models than image recognition. With recognition, the model produces bounding rectangles that the system believes to contain the entire object. With segmentation, the model identifies the actual pixels of the object.&lt;/p&gt;
&lt;p&gt;In this tutorial, you’ll start with an image of your subject. Then you’ll generate a mask for the background using Vision. Finally, you’ll use CoreImage filters to blend the original image, image mask, and the new image background.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;segmentation-process-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;666&quot; src=&quot;https://img.ly/_astro/segmentation-process-1_Z2lEfI5.webp&quot; srcset=&quot;/_astro/segmentation-process-1_2i2R9H.webp 640w, /_astro/segmentation-process-1_Z2lEfI5.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Whether using the Vision framework alone or supplementing Vision with another Core ML model – the process will be the same:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a Vision request object with some parameters.&lt;/li&gt;
&lt;li&gt;Create a Vision request handler with the image to be processed.&lt;/li&gt;
&lt;li&gt;Process the image with the handler and object.&lt;/li&gt;
&lt;li&gt;Process the results into the final image using CoreImage.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When working with still images, Apple’s CoreImage framework is usually the best option, mainly because of the large number of available filters and the ability to create reusable pipelines that you can run on the CPU or the GPU.&lt;/p&gt;
&lt;h2 id=&quot;using-vision-to-segment-people&quot;&gt;Using Vision to Segment People&lt;/h2&gt;
&lt;p&gt;Without an external model, Vision can only segment documents or people in an image. It is vital to understand that Vision will only identify a pixel in the image as “this pixel is part of a person” or “this pixel is not part of a person.” If an image contains a group of people, Vision will not be able to separate individuals.&lt;/p&gt;
&lt;p&gt;To segment the image, start by creating an instance of a &lt;code&gt;VNGeneratePersonSegmentationRequest&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; segmentationRequest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VNGeneratePersonSegmentationRequest&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;segmentationRequest.qualityLevel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .balanced&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This type of request has a few options you can set. &lt;code&gt;.qualityLevel&lt;/code&gt; can be &lt;code&gt;.fast&lt;/code&gt;, &lt;code&gt;.balanced&lt;/code&gt; or &lt;code&gt;.accurate&lt;/code&gt;. The level of &lt;code&gt;.accurate&lt;/code&gt; is the default. This will determine how closely the mask conforms to the boundaries of the original image. The different levels process at different speeds. The &lt;code&gt;.fast&lt;/code&gt; setting is intended for use in a video so that frames don’t get dropped. Using &lt;code&gt;.balanced&lt;/code&gt; or &lt;code&gt;.accurate&lt;/code&gt; causes noticeable delay in an app processing the image on most devices. Experiment with different settings depending on your needs.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;speed-compare&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;516&quot; src=&quot;https://img.ly/_astro/speed-compare_Z1SlWxp.webp&quot; srcset=&quot;/_astro/speed-compare_ywD9u.webp 640w, /_astro/speed-compare_Z1SlWxp.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The output of the segmentation request will be a &lt;code&gt;CVPixelBuffer&lt;/code&gt;. This is a structure that contains information for each pixel of the image. Most of Apple’s video frameworks as well as CoreImage can work with pixel buffers. The default for &lt;code&gt;VNGeneratePersonSegmentationRequest&lt;/code&gt; is a buffer where the color of each pixel is represented by an 8-bit number. Any pixel that Vision thinks contains part of a person will be white and any not-a-person will be black and represented by zero. This will be exactly what you want for generating a mask to work with &lt;code&gt;CIBlendWithMask&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, create a &lt;code&gt;VNImageRequestHandler&lt;/code&gt; with the image to be processed. The handler class has a &lt;a href=&quot;https://developer.apple.com/documentation/vision/vnimagerequesthandler&quot;&gt;number of initializers&lt;/a&gt; for different types of data. In this example we will use &lt;code&gt;CGImage&lt;/code&gt;, but you could also start with &lt;code&gt;CIImage&lt;/code&gt;, &lt;code&gt;CVPixelbuffer&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, or others. You can also specify options for the handler. In this tutorial we will not specify any, but one of the options is to pass in a &lt;code&gt;CIContext&lt;/code&gt;. This can help with performance as you can tell your app to do all of the processing with a single context on the GPU using Metal.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; originalCG &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; originalImage&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.cgImage &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;abort&lt;/span&gt;&lt;span&gt;() }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; handler &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VNImageRequestHandler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cgImage&lt;/span&gt;&lt;span&gt;: originalCG)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;try?&lt;/span&gt;&lt;span&gt; handler.&lt;/span&gt;&lt;span&gt;perform&lt;/span&gt;&lt;span&gt;([segmentationRequest])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; maskPixelBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  segmentationRequest.results&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.pixelBuffer &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; maskImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGImage.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pixelBuffer&lt;/span&gt;&lt;span&gt;: maskPixelBuffer)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, the &lt;code&gt;originalImage&lt;/code&gt; (which happens to be a UIImage) gets converted to a CGImage. Then we use the image to initialize a request handler. The &lt;code&gt;VNImageRequestHandler&lt;/code&gt; has a method &lt;code&gt;.perform&lt;/code&gt; which takes an array of all of the Vision requests you want to use to process the image. The &lt;code&gt;.perform&lt;/code&gt; method will not return until all of the requests in the array have been completed. Depending on how you want to structure your code, you can either provide a completion handler to use with each of the requests or just process the results in-line. In this example, we’ll process in-line.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;segmentationRequest&lt;/code&gt; found any people in the image, the &lt;code&gt;results&lt;/code&gt; array will contain a &lt;code&gt;.pixelBuffer&lt;/code&gt;. Create the mask we need by converting the &lt;code&gt;pixelBuffer&lt;/code&gt; into a CGImage. To convert to a CGImage, the example uses some helper methods that &lt;a href=&quot;https://github.com/hollance/CoreMLHelpers&quot;&gt;Matthijs Hollemans published to GitHub&lt;/a&gt; specifically for working with Core ML inputs and outputs.&lt;/p&gt;
&lt;p&gt;Now that we have the mask image, use &lt;code&gt;CIFilter&lt;/code&gt; to compose the final image. It will take a few steps. First, resize the new background, mask, and original image to the same size. Then, blend the three images.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Convert main image to a CIImage and get the size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; mainImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cgImage&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.originalImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.cgImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; originalSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mainImage.extent.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Convert the maskimage to CIImage and set the size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//to be the same as the original&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; maskCI &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cgImage&lt;/span&gt;&lt;span&gt;: maskImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; scaleX &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; originalSize.width &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; maskCI.extent.width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; scaleY &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; originalSize.height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; maskCI.extent.height&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;maskCI &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; maskCI.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: .&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scaleX&lt;/span&gt;&lt;span&gt;: scaleX, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: scaleY))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Convert the new background to a CIImage and set the size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//to be the same as the original&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; backgroundUIImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;named&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;starfield&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resized&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: originalSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; background &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cgImage&lt;/span&gt;&lt;span&gt;: backgroundUIImage.cgImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Use CIBlendWithMask to combine the three images&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CIBlendWithMask&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(background, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputBackgroundImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(mainImage, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(maskCI, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputMaskImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Update the UI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.filteredImageView.&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ciImage&lt;/span&gt;&lt;span&gt;: filter&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code resizes the images to match the size of the original image and converts them to &lt;code&gt;CIImage&lt;/code&gt;. Many machine learning models commonly resize inputs and outputs. For example, the output of the &lt;code&gt;VNGeneratePersonSegmentationRequest&lt;/code&gt; using the demo image is 384x512.&lt;/p&gt;
&lt;p&gt;Both CIImage and UIImage formats describe an image but do not always have a bitmap representation. That is why each image gets converted to a CGImage first. Though this guarantees that the demo code will work, converting formats makes the code run slower. In your app, you should experiment with different methods to get your images into CIImage format for filtering. Also, the above code uses Matthijs’ &lt;code&gt;resized&lt;/code&gt; helper method to do some of the resizing.&lt;/p&gt;
&lt;p&gt;With everything resized, the &lt;a href=&quot;https://developer.apple.com/documentation/coreimage/ciblendwithmask&quot;&gt;CIBlendWithMask&lt;/a&gt; filter stitches the images together. In place of the black pixels in the mask, the final image will show the background – for white pixels, the foreground. Wherever the mask image has a gray pixel, the final image will blend background and foreground.&lt;/p&gt;
&lt;p&gt;Now let’s see how to use an additional &lt;code&gt;CoreML&lt;/code&gt; model to process images that don’t have a person as the main subject.&lt;/p&gt;
&lt;h2 id=&quot;choosing-a-model&quot;&gt;Choosing a Model&lt;/h2&gt;
&lt;p&gt;Apple provides a number of pre-built models for text and image processing. The DeepLab v3 Machine Learning model can perform segmentation requests on images that have subjects like dogs. You can download it from &lt;a href=&quot;https://developer.apple.com/machine-learning/models/&quot;&gt;Apple directly&lt;/a&gt; or also find it at the &lt;a href=&quot;https://github.com/tensorflow/models/tree/master/research/deeplab&quot;&gt;DeepLab repo on GitHub&lt;/a&gt;. Once you have downloaded the model, add it to your Xcode project the same as any other file. The DeepLab model will recognize people, the same as the &lt;code&gt;VNPersonSegmentationRequest&lt;/code&gt;, but also other objects. This does come at a cost in an increased filesize for your application. You may notice that Apple provides multiple versions of the model of different file sizes. They all perform the same task, but differ in how they represent the output and a few other things.&lt;/p&gt;
&lt;p&gt;Models on Apple’s website are packaged to work with Core ML and Xcode, so you can easily try a different model. The input and output method names will be the same. It is beyond the scope of this tutorial, yet Apple provides tutorials and example scripts on how to convert TensorFlow and other machine learning models to work with Core ML.&lt;/p&gt;
&lt;h3 id=&quot;core-ml-and-xcode&quot;&gt;Core ML and Xcode&lt;/h3&gt;
&lt;p&gt;When you are working with a Core ML model, Xcode provides some convenient tools. Access them by highlighting the name of the model in the File Navigator pane of Xcode. For instance, clicking “Preview” will let you test the model with your data.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;testing-screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;412&quot; src=&quot;https://img.ly/_astro/testing-screenshot_Z2l2s2a.webp&quot; srcset=&quot;/_astro/testing-screenshot_8v5CS.webp 640w, /_astro/testing-screenshot_Z2l2s2a.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can also use the “Predictions” tab to determine how the input needs to be formatted and what to expect for the output.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;predictions-screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;355&quot; src=&quot;https://img.ly/_astro/predictions-screenshot_Z1Bf2h7.webp&quot; srcset=&quot;/_astro/predictions-screenshot_Zh7lHX.webp 640w, /_astro/predictions-screenshot_Z1Bf2h7.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here you can see that the DeepLabV3 model expects images to be 513 x 513 and will output a multidimensional array of integers that is 513 x 513 in size.&lt;/p&gt;
&lt;p&gt;Finally, on this screen, you can see an entry for the “Model Class” in the headers. Double-click on the name to jump into the Swift wrapper class for the model to see how to call the model in your code and work with the inputs and outputs.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;model-class&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 418px) 418px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;418&quot; height=&quot;85&quot; src=&quot;https://img.ly/_astro/model-class_1pbhcQ.webp&quot; srcset=&quot;/_astro/model-class_1pbhcQ.webp 418w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-deeplabv3-model&quot;&gt;The DeepLabV3 Model&lt;/h3&gt;
&lt;p&gt;The DeepLab model input will be a color image that is 513 x 513 pixels. The Vision framework will handle resizing the input, but you can provide options on how that resize should work. The DeepLabV3 model has been trained to recognize and segment these items:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aeroplane&lt;/li&gt;
&lt;li&gt;bicycle&lt;/li&gt;
&lt;li&gt;bird&lt;/li&gt;
&lt;li&gt;boat&lt;/li&gt;
&lt;li&gt;bottle&lt;/li&gt;
&lt;li&gt;bus&lt;/li&gt;
&lt;li&gt;car&lt;/li&gt;
&lt;li&gt;cat&lt;/li&gt;
&lt;li&gt;chair&lt;/li&gt;
&lt;li&gt;cow&lt;/li&gt;
&lt;li&gt;dining table&lt;/li&gt;
&lt;li&gt;dog&lt;/li&gt;
&lt;li&gt;horse&lt;/li&gt;
&lt;li&gt;motorbike&lt;/li&gt;
&lt;li&gt;person&lt;/li&gt;
&lt;li&gt;potted plant&lt;/li&gt;
&lt;li&gt;sheep&lt;/li&gt;
&lt;li&gt;sofa&lt;/li&gt;
&lt;li&gt;train&lt;/li&gt;
&lt;li&gt;tv or monitor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anything that the model does not recognize, it will consider a &lt;em&gt;background&lt;/em&gt;. After performing the recognition, the model returns a two-dimensional 513 x 513 array. Each entry in the array corresponds to one pixel in the original 513 x 513 input image. For instance, every pixel in the original image that shows a dog will be represented in the output by a &lt;code&gt;12&lt;/code&gt; in the corresponding array entry. Any pixel that is not a recognized object will be given a &lt;code&gt;0&lt;/code&gt; in the output array to represent a &lt;em&gt;background&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If the model recognizes multiple objects, there will be multiple numbers in the array. If that is not what you want, you need to make changes to the array before creating the mask. For example, using an image of a dog riding a horse, some entries in the array will be &lt;code&gt;12&lt;/code&gt;, others will be &lt;code&gt;13&lt;/code&gt;, and the rest will be &lt;code&gt;0&lt;/code&gt;. To filter out the horse, you need to loop through the array and change any &lt;code&gt;13&lt;/code&gt;s to &lt;code&gt;0&lt;/code&gt;s.&lt;/p&gt;
&lt;h2 id=&quot;segmentation-with-deeplab&quot;&gt;Segmentation with DeepLab&lt;/h2&gt;
&lt;p&gt;To use the DeepLab model in your code, you again use a request to the Vision framework but this time you can specify a model.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MLModelConfiguration&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; segmentationModel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try!&lt;/span&gt;&lt;span&gt; DeepLabV3&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;configuration&lt;/span&gt;&lt;span&gt;: config)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; visionModel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; VNCoreMLModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: segmentationModel.model) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.request &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VNCoreMLRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt;: visionModel)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.request&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.imageCropAndScaleOption &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .scaleFill&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we create an instance of the DeepLab Core ML object and then initialize a generic &lt;code&gt;VNCoreMLRequest&lt;/code&gt; with its model. The only option we set on the request is to tell it how to modify the image when it resizes it for input.&lt;/p&gt;
&lt;p&gt;Now the code is very similar to the first example.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; cgImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; originalImage&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.cgImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; handler &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VNImageRequestHandler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cgImage&lt;/span&gt;&lt;span&gt;: cgImage, &lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;try?&lt;/span&gt;&lt;span&gt; handler.&lt;/span&gt;&lt;span&gt;perform&lt;/span&gt;&lt;span&gt;([request])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; observations &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; request.results &lt;/span&gt;&lt;span&gt;as?&lt;/span&gt;&lt;span&gt; [VNCoreMLFeatureValueObservation],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; segmentationmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; observations.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.featureValue.multiArrayValue {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; maskUIImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; segmentationmap.&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  applyBackgroundMask&lt;/span&gt;&lt;span&gt;(maskUIImage)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we are using a generic &lt;code&gt;VNCoreMLRequest&lt;/code&gt; we have to cast the results as &lt;code&gt;VNCoreMLFeatureValueObservation&lt;/code&gt;. The DeepLab model returns a &lt;code&gt;.multiArrayValue&lt;/code&gt; instead of a &lt;code&gt;.pixelBuffer&lt;/code&gt; so we will again rely on the helper methods to convert it to an image. Now that the mask image has been created, applying the background is the same as in the original example.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;dog-in-space-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;519&quot; src=&quot;https://img.ly/_astro/dog-in-space-1_pxayi.webp&quot; srcset=&quot;/_astro/dog-in-space-1_pxayi.webp 400w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;This tutorial focused on still images, yet both the standard Vision segmentation requests and DeepLabV3 allow processing video input as they can work with &lt;code&gt;CVPixelBuffer&lt;/code&gt; and &lt;code&gt;VNSequenceRequestHandler&lt;/code&gt;.&lt;br&gt;
The rest of the process will be almost identical to our example. You will need to finetune the performance, or else you will experience frame drops.&lt;/p&gt;
&lt;p&gt;Apple provides another method for identifying the background and primary subject in a still image: Portrait mode. When a device renders Portrait mode, it takes pictures with all cameras on the device and stitches them together. This way, the depth of field can change, and you can adjust what items are in focus or blurred.&lt;/p&gt;
&lt;p&gt;If the DeepLab model does not cover your subject matter, you can train the DeepLabV3 model to recognize other objects using your own data.&lt;/p&gt;
&lt;p&gt;However, you may consider using SDKs such as &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PE.SDK&lt;/a&gt; and &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VE.SDK&lt;/a&gt;. Enabling background removal for your users is easy — all it takes is a few lines in your configuration as described in the &lt;a href=&quot;https://img.ly/docs/pesdk/ios/guides/background-removal/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;official PE.SDK documentation for iOS&lt;/a&gt; and &lt;a href=&quot;https://img.ly/docs/pesdk/android/guides/background-removal/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;Android&lt;/a&gt;, which adds a control to the photo editor to toggle the setting.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;background-removal-photo-editor-app-pe-sdk-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 277px) 277px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;277&quot; height=&quot;600&quot; src=&quot;https://img.ly/_astro/background-removal-photo-editor-app-pe-sdk-1_21B1QF.webp&quot; srcset=&quot;/_astro/background-removal-photo-editor-app-pe-sdk-1_21B1QF.webp 277w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Integrate a fully customizable photo editor with Background Removal into your app with PE.SDK.&lt;/p&gt;
&lt;p&gt;The latest &lt;a href=&quot;https://img.ly/blog/photo-editor-video-editor-sdk-v_10-11-release-notes/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VE.SDK release&lt;/a&gt; introduced Background Removal for stickers. This feature recognizes people in pictures and removes the background with one tap – no need for manual outlines or masks.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;background-removal-photo-editor-app-pe-sdk-video&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 277px) 277px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;277&quot; height=&quot;600&quot; src=&quot;https://img.ly/_astro/background-removal-photo-editor-app-pe-sdk-video_1flT3l.webp&quot; srcset=&quot;/_astro/background-removal-photo-editor-app-pe-sdk-video_1flT3l.webp 277w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let your users create beautiful visuals with Sticker Background Removal.&lt;/p&gt;
&lt;p&gt;This feature is available on Android and iOS 15.0 and higher only. See the official documentation for Sticker Background Removal on &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/stickers/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes#background-removal&quot;&gt;Android&lt;/a&gt; or &lt;a href=&quot;https://img.ly/docs/vesdk/ios/guides/stickers/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes#background-removal&quot;&gt;iOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using an SDK will simplify and accelerate your app development, as you can save time and resources, and focus on the growth and innovation of your application instead.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw how to use Vision and Core ML to segment an image of a person and remove the background. You also saw how to use DeepLab to work with images of non-persons.&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions.&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/06/background-remove-removal-editor-app-development.png" medium="image"/><category>How-To</category><category>Background Removal</category><category>Machine Learning</category><category>Photo Editing</category><category>App Development</category><category>iOS App Development</category><category>Core ML</category><category>Tutorial</category></item><item><title>How to Apply Filter Effects to Video using VidEffects library on Android</title><link>https://img.ly/blog/how-to-apply-filter-effects-to-video-using-videffects-on-android/</link><guid isPermaLink="true">https://img.ly/blog/how-to-apply-filter-effects-to-video-using-videffects-on-android/</guid><description>Learn about the benefits and limitations of VidEffects for video editing on Android.</description><pubDate>Tue, 24 May 2022 11:41:23 GMT</pubDate><content:encoded>&lt;p&gt;Video manipulation is a complex topic on Android because there is no out-of-the-box support from the Android SDK, and using FFmpeg requires not just a very elaborate setup but having to climb a steep learning curve to learn its CLI commands.&lt;/p&gt;
&lt;p&gt;As an aside, we have also put together how-to blogs to elaborate on other video manipulation operations like merging still images to form videos, &lt;a href=&quot;https://img.ly/blog/working-with-large-video-and-image-files-on-ios-with-swift/&quot;&gt;compressing and resizing videos&lt;/a&gt;, or background removal.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/krazykira/VidEffects&quot;&gt;VidEffects&lt;/a&gt; library is much faster to set up than FFmpeg, and it works by applying effects to &lt;code&gt;GLSurfaceView&lt;/code&gt;. This blog post will demonstrate how to apply Filter effects to a video in an Android app using the VidEffects library.&lt;/p&gt;
&lt;p&gt;VidEffects does support storing videos on the disk, but it has limitations in this regard which we will discuss in a moment.&lt;/p&gt;
&lt;p&gt;Before we begin, keep in mind that the VidEffects library differentiates between &lt;strong&gt;Effects&lt;/strong&gt; and &lt;strong&gt;Filters&lt;/strong&gt;. As an example, we will apply both and discuss the crucial difference between the two.&lt;/p&gt;
&lt;p&gt;Without much further ado, let’s start coding.&lt;/p&gt;
&lt;h2 id=&quot;create-a-videffects-example-app&quot;&gt;Create a VidEffects Example App&lt;/h2&gt;
&lt;p&gt;Create a &lt;strong&gt;New Project&lt;/strong&gt; with an Empty Activity in Android Studio. Select &lt;em&gt;Kotlin&lt;/em&gt; as the language (or Java if you wish), and set **Minimum SDK to API 21.&lt;/p&gt;
&lt;p&gt;You can also download the final code from &lt;a href=&quot;https://github.com/numerative/videffects_sample&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;add-dependency&quot;&gt;Add Dependency&lt;/h3&gt;
&lt;p&gt;In your module-level &lt;strong&gt;build.gradle&lt;/strong&gt; (app/build.gradle) file add the latest VidEffects dependency.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;groovy&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dependencies {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    implementation &lt;/span&gt;&lt;span&gt;&quot;com.github.krazykira:videffects:1.1.1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, enable &lt;code&gt;viewBinding&lt;/code&gt; by adding the following lines within the &lt;code&gt;android{}&lt;/code&gt; closure.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;groovy&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;android {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    buildFeatures {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        viewBinding &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using View Binding, we can avoid the &lt;code&gt;findViewById()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;Sync Now&lt;/strong&gt; to sync the project with gradle files.&lt;/p&gt;
&lt;h3 id=&quot;add-video-asset-file&quot;&gt;Add Video Asset File&lt;/h3&gt;
&lt;p&gt;Place the sample video file in &lt;strong&gt;app/src/main/assets/&lt;/strong&gt; directory. Ideally, your app will load the video file from disk – but to avoid complexity, we are placing the video in the assets directory.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;directory_tree_android_video-effect&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 304px) 304px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;304&quot; height=&quot;299&quot; src=&quot;https://img.ly/_astro/directory_tree_android_video-effect_4vaxk.webp&quot; srcset=&quot;/_astro/directory_tree_android_video-effect_4vaxk.webp 304w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;create-a-layout&quot;&gt;Create a Layout&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;screen_no_filter-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 500px) 500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;500&quot; height=&quot;889&quot; src=&quot;https://img.ly/_astro/screen_no_filter-1_g8KRl.webp&quot; srcset=&quot;/_astro/screen_no_filter-1_g8KRl.webp 500w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Create the above layout with a &lt;code&gt;VideoSurfaceView&lt;/code&gt;, three Filter buttons, and one Reset Button.&lt;/p&gt;
&lt;p&gt;Replace your &lt;strong&gt;activity_main.xml&lt;/strong&gt;`s code with the following.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span&gt; xmlns:android&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://schemas.android.com/apk/res/android&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    xmlns:app&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://schemas.android.com/apk/res-auto&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    xmlns:tools&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://schemas.android.com/tools&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;match_parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;match_parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    tools:context&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;.MainActivity&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;com.sherazkhilji.videffects.view.VideoSurfaceView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/mVideoSurfaceView&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;match_parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;300dp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/wrapper&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintEnd_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintStart_toStartOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintTop_toBottomOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@id/mVideoSurfaceView&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/bwButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Black and White&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintStart_toStartOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/grainButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Grain&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintStart_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@id/bwButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/duotoneButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;DuoTone&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintStart_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@id/grainButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            app:layout_constraintTop_toTopOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;androidx.appcompat.widget.AppCompatButton&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/resetButton&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:layout_height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;wrap_content&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        android:text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Reset&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintEnd_toEndOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintStart_toStartOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;parent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        app:layout_constraintTop_toBottomOf&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;@+id/wrapper&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;androidx.constraintlayout.widget.ConstraintLayout&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;initialize-the-video&quot;&gt;Initialize the video&lt;/h2&gt;
&lt;p&gt;Before initializing the video, set up an instance of the Binding class to use it within the Activity.&lt;/p&gt;
&lt;h3 id=&quot;setup-view-binding&quot;&gt;Setup View Binding&lt;/h3&gt;
&lt;p&gt;Add the following code in &lt;strong&gt;MainActivity.kt&lt;/strong&gt; before calling &lt;code&gt;setContentView()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you do not wish to use View Binding and instead prefer using &lt;code&gt;findViewById()&lt;/code&gt;, skip to the next step &lt;em&gt;Initializing the Sample Video&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; lateinit&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; binding: &lt;/span&gt;&lt;span&gt;ActivityMainBinding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AppCompatActivity&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		private&lt;/span&gt;&lt;span&gt; lateinit&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; binding: &lt;/span&gt;&lt;span&gt;ActivityMainBinding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ActivityMainBinding.&lt;/span&gt;&lt;span&gt;inflate&lt;/span&gt;&lt;span&gt;(layoutInflater)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    companion&lt;/span&gt;&lt;span&gt; object&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        private&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; val&lt;/span&gt;&lt;span&gt; TAG &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;MainActivity&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ActivityMainBinding&lt;/code&gt; class is auto-generated. If it did not get auto-generated, then sync the project with the gradle file from &lt;strong&gt;File &gt; Sync Project with Gradle Files.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Next, pass the &lt;em&gt;root view&lt;/em&gt; to the &lt;code&gt;setContentView()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AppCompatActivity&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        setContentView&lt;/span&gt;&lt;span&gt;(binding.root)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;initializing-the-sample-video&quot;&gt;Initializing the Sample Video&lt;/h3&gt;
&lt;p&gt;Next, declare and initialize the &lt;code&gt;MediaPlayer&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MainActivity&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AppCompatActivity&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    private&lt;/span&gt;&lt;span&gt; lateinit&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; mMediaPlayer: &lt;/span&gt;&lt;span&gt;MediaPlayer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            mMediaPlayer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MediaPlayer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open the video file from our assets directory and pass it as the data source to the Media Player.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; afd &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; assets.&lt;/span&gt;&lt;span&gt;openFd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;sample.mp4&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        mMediaPlayer.&lt;/span&gt;&lt;span&gt;setDataSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            afd.fileDescriptor,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            afd.startOffset, afd.length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e: &lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Log.&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;(TAG, e.message, e)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, all that is remaining is to initialize the &lt;code&gt;VideoSurfaceView&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(mMediaPlayer, &lt;/span&gt;&lt;span&gt;NoEffectFilter&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your app should run. While the buttons still do not work, we will &lt;code&gt;setOnClickListeners()&lt;/code&gt; next.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screen_no_filter-2&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 500px) 500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;500&quot; height=&quot;889&quot; src=&quot;https://img.ly/_astro/screen_no_filter-2_biy8Y.webp&quot; srcset=&quot;/_astro/screen_no_filter-2_biy8Y.webp 500w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;apply-videffects-effects&quot;&gt;Apply VidEffects Effects&lt;/h2&gt;
&lt;p&gt;The VidEffects library offers &lt;a href=&quot;https://github.com/krazykira/VidEffects#supported-effects&quot;&gt;multiple effects&lt;/a&gt;, but we will be applying three: Black and White, Grain, and Duotone.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;MainActivity&lt;/strong&gt;’s &lt;code&gt;onCreate()&lt;/code&gt;, copy the following code to setup all the &lt;code&gt;setOnClickListeners&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;kotlin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; fun&lt;/span&gt;&lt;span&gt; onCreate&lt;/span&gt;&lt;span&gt;(savedInstanceState: &lt;/span&gt;&lt;span&gt;Bundle&lt;/span&gt;&lt;span&gt;?) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ..&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Black and White Effect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.bwButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; BlackAndWhiteEffect&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Grain Filter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.grainButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        val&lt;/span&gt;&lt;span&gt; grainFilter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; GrainFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        grainFilter.&lt;/span&gt;&lt;span&gt;setIntensity&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5f&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; grainFilter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Duotone Effect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.duotoneButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.shader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            DuotoneEffect&lt;/span&gt;&lt;span&gt;(Color.BLUE, Color.YELLOW)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //Reset with AutoFixFilter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    binding.resetButton.&lt;/span&gt;&lt;span&gt;setOnClickListener&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        binding.mVideoSurfaceView.filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NoEffectFilter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the app.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screen-bw-duo-grain&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1500px) 1500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1500&quot; height=&quot;889&quot; src=&quot;https://img.ly/_astro/screen-bw-duo-grain_U18c2.webp&quot; srcset=&quot;/_astro/screen-bw-duo-grain_6sBsy.webp 640w, /_astro/screen-bw-duo-grain_1q99jx.webp 750w, /_astro/screen-bw-duo-grain_Z1jzBKe.webp 828w, /_astro/screen-bw-duo-grain_ZowVa8.webp 1080w, /_astro/screen-bw-duo-grain_Z2lItwG.webp 1280w, /_astro/screen-bw-duo-grain_U18c2.webp 1500w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;difference-between-effects-and-filters&quot;&gt;Difference Between Effects and Filters&lt;/h2&gt;
&lt;p&gt;As mentioned before, Effects and Filters are two separate functions in the VidEffects library.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Effects&lt;/em&gt; are temporary, view-only overlays that cannot be stored on the disk. &lt;em&gt;Filters&lt;/em&gt; can be viewed and stored on the disk. Unfortunately, a majority of the overlays offered by VidEffects are &lt;a href=&quot;https://github.com/krazykira/VidEffects/tree/master/videffects/src/main/java/com/sherazkhilji/videffects&quot;&gt;Effects&lt;/a&gt; and therefore only useful for playback only. At the time of writing this article, only three &lt;a href=&quot;https://github.com/krazykira/VidEffects/tree/master/videffects/src/main/java/com/sherazkhilji/videffects/filter&quot;&gt;Filters&lt;/a&gt; are available.&lt;/p&gt;
&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;One of the biggest limitations of this library, as mentioned above, is that the majority of the effects provided by the library are just view-only.&lt;/p&gt;
&lt;p&gt;Moreover, to save videos with filters, the app must target &lt;code&gt;minSdk 23&lt;/code&gt; at least or use FFmpeg.&lt;/p&gt;
&lt;p&gt;Many of these Effects do not have an intensity parameter and therefore we cannot fine-tune these effects.&lt;/p&gt;
&lt;h2 id=&quot;alternative&quot;&gt;Alternative&lt;/h2&gt;
&lt;p&gt;If you are looking for an easy-to-integrate solution that overcomes these limitations, take a look at IMG.LY’s &lt;a href=&quot;https://img.ly/docs/vesdk/android/introduction/getting_started?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditor SDK&lt;/a&gt;. It provides more than 60 high-quality adjustable filters out-of-the-box. Moreover, you can easily add &lt;a href=&quot;https://img.ly/docs/vesdk/android/features/filters/#add-custom-lut-filters?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;custom filters&lt;/a&gt; using LUT files. VideoEditor SDK works for Android SDK version 21 and above and thus targets more devices.&lt;/p&gt;
&lt;p&gt;If the purpose is to apply effects during the playback, the VidEffects library is a solid and free open-source solution. While it handles all the complex video editing, developers will have to spend some time developing the layout.&lt;/p&gt;
&lt;h3 id=&quot;thanks-for-reading-let-us-know-what-you-think-on-twitter-or-stay-in-the-loop-with-our-newsletter&quot;&gt;Thanks for reading! Let us know what you think on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;, or stay in the loop with our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/h3&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2022/05/videffects-add-filter-to-video-android.png" medium="image"/><category>How-To</category><category>Video Editing</category><category>Android</category><category>Android App Development</category><category>Mobile App Development</category><category>Tutorial</category></item><item><title>Working with Large Video and Image Files on iOS with Swift</title><link>https://img.ly/blog/working-with-large-video-and-image-files-on-ios-with-swift/</link><guid isPermaLink="true">https://img.ly/blog/working-with-large-video-and-image-files-on-ios-with-swift/</guid><description>Find your best strategy for compressing and resizing images, videos, and other files with Swift.</description><pubDate>Tue, 10 May 2022 13:27:44 GMT</pubDate><content:encoded>&lt;p&gt;Video files and image files are among the largest files. The &lt;code&gt;AVFoundation&lt;/code&gt; and &lt;code&gt;CoreImage&lt;/code&gt; libraries that Apple supplies widely store data on the disk. They try to bring into memory only as much of a file as is needed to complete a task. As you are working on an application that uses files, there are times you will want to compress or resize them. Usually, this is to upload to a server or because you have noticed performance issues, such as stuttering when scrolling or long render times.&lt;/p&gt;
&lt;p&gt;This article will cover strategies for compressing and resizing images, videos, and other files. We will also be looking at using URLSession for file uploads and downloads.&lt;/p&gt;
&lt;p&gt;As iPhone cameras can take higher-quality pictures, the file sizes have grown quite large. For the last few years, Apple has been using a &lt;code&gt;12MP&lt;/code&gt; camera that takes pictures at &lt;code&gt;3024 x 4032&lt;/code&gt; resolution. Apple uses a special &lt;code&gt;HEIC&lt;/code&gt; compression to make each image about &lt;code&gt;1.7MB&lt;/code&gt;. The same image will generate a &lt;code&gt;15MB&lt;/code&gt; file when saved as a &lt;code&gt;.png&lt;/code&gt; and a &lt;code&gt;3MB&lt;/code&gt; file when saved as a &lt;code&gt;.jpeg&lt;/code&gt;. Though this will be a high-quality image, services like Instagram or Facebook will often only display images at less than half of that size. The extra pixel data will be compressed away. Perhaps the first and most important part of working with large image files in your app is planning around what size or quality you need. Social Media services similarly resize video files. For instance, TikTok displays images and video using &lt;code&gt;1080 x 1920&lt;/code&gt;, but any iPhone after X will capture video at a larger size.&lt;/p&gt;
&lt;h2 id=&quot;writing-an-image-to-disk&quot;&gt;Writing an Image to Disk&lt;/h2&gt;
&lt;p&gt;When working with a &lt;code&gt;UIImage&lt;/code&gt; or &lt;code&gt;CoreImage&lt;/code&gt; object, normally you can save it as an NSData object. These files are lossless (the image quality will not degrade), but they are only useful inside of your application. The standards on the Internet are &lt;code&gt;jpeg&lt;/code&gt; and &lt;code&gt;png&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Fortunately, Apple provides methods to convert &lt;code&gt;UIImage&lt;/code&gt; to &lt;code&gt;.jpegData&lt;/code&gt; or &lt;code&gt;.pngData&lt;/code&gt;. There are also ways to convert &lt;code&gt;CIImage&lt;/code&gt; and &lt;code&gt;CGImage&lt;/code&gt;, but they are slightly more complicated to configure.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; imageToSave &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ciImage&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;someImage&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; jpegData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageToSave.&lt;/span&gt;&lt;span&gt;jpegData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;compressionQuality&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;) \\&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; file &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt; \\&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;do&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; jpegData&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: file&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;sleeping_dog.jpg&quot;&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; err) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(err.localizedDescription) &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what’s going on in the code above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First we need to convert our image to a &lt;code&gt;UIImage&lt;/code&gt;. The &lt;code&gt;UIImage&lt;/code&gt; class has several different initializers. Use &lt;code&gt;UIImage(named: &quot;some image name&quot;)&lt;/code&gt; to read an image from the app bundle. Use &lt;code&gt;UIImage(contentsOfFile: &quot;some filepath&quot;)&lt;/code&gt; to read from the disk. Use &lt;code&gt;UIImage(cgImage: &quot;some cgImage&quot;)&lt;/code&gt; and &lt;code&gt;UIImage(ciImage: &quot;some ciImage&quot;)&lt;/code&gt; when you have images already in memory. If you are storing images in a &lt;code&gt;CoreData&lt;/code&gt; store or somewhere as &lt;code&gt;Data&lt;/code&gt; you can use &lt;code&gt;UIImage(data: &quot;some data&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;However you get a &lt;code&gt;UIImage&lt;/code&gt; the next task is to convert it to either a &lt;code&gt;.jpg&lt;/code&gt; or &lt;code&gt;.png&lt;/code&gt; file. Generally, &lt;code&gt;.jpg&lt;/code&gt; is a better option when working with photographs and &lt;code&gt;.png&lt;/code&gt; produces higher quality for line art. The example above creates a &lt;code&gt;.jpg&lt;/code&gt; at the highest quality possible.&lt;/li&gt;
&lt;li&gt;Ask the &lt;code&gt;FileManager&lt;/code&gt; for a &lt;code&gt;URL&lt;/code&gt; to the user’s documents directory. That is generally a safe place to write files. Because of the way the function is formed, it returns an array of &lt;code&gt;URL&lt;/code&gt; items. In this case, there is only one, but you still need to remember to specify the &lt;code&gt;.first&lt;/code&gt; item.&lt;/li&gt;
&lt;li&gt;Writing &lt;code&gt;Data&lt;/code&gt; object to disk can throw errors, so it is good practice to place it in a &lt;code&gt;try&lt;/code&gt;…&lt;code&gt;catch&lt;/code&gt; block of code. The &lt;code&gt;URL&lt;/code&gt; to the user’s documents directory needs to have the actual filename appended.&lt;/li&gt;
&lt;li&gt;Any errors that the system throws can be handled here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to create a &lt;code&gt;.png&lt;/code&gt; using the &lt;code&gt;imageToSave&lt;/code&gt; above, the line would be&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; pngData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageToSave.&lt;/span&gt;&lt;span&gt;pngData&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;.png&lt;/code&gt; filetype does not have any compression settings. The filetype is &lt;a href=&quot;https://en.wikipedia.org/wiki/Lossless_compression&quot;&gt;“lossless”&lt;/a&gt;, so it is always compressed as much as possible without losing any data.&lt;/p&gt;
&lt;h3 id=&quot;compression-options-for-jpeg&quot;&gt;Compression Options for JPEG&lt;/h3&gt;
&lt;p&gt;When generating &lt;code&gt;jpeg&lt;/code&gt; data above, we supplied a quality value. The value is a &lt;code&gt;Float&lt;/code&gt; between &lt;code&gt;0.0&lt;/code&gt; and &lt;code&gt;1.0&lt;/code&gt;. The &lt;code&gt;.jpg&lt;/code&gt; filetype uses a &lt;a href=&quot;https://en.wikipedia.org/wiki/Lossy_compression&quot;&gt;“lossy”&lt;/a&gt; compression algorithm that is specifically designed for photographs.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;compression-quality&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 800px) 800px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;800&quot; height=&quot;197&quot; src=&quot;https://img.ly/_astro/compression-quality_ZnCYfn.webp&quot; srcset=&quot;/_astro/compression-quality_2bNCSI.webp 640w, /_astro/compression-quality_Zi6PuL.webp 750w, /_astro/compression-quality_ZnCYfn.webp 800w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the image above saving the &lt;code&gt;jpeg&lt;/code&gt; with a quality of &lt;code&gt;1.0&lt;/code&gt; produced the dog on the right and the file is &lt;code&gt;1.3MB&lt;/code&gt;. Saving the jpeg with a quality of &lt;code&gt;0.0&lt;/code&gt; produced the image on the left and the middle image is produced saving with a quality of &lt;code&gt;0.6&lt;/code&gt;. The file on the left is only about 6% of the size of the original.&lt;/p&gt;
&lt;p&gt;Considering what the images are being used for can help you decide on an optimal compression setting for a particular app.&lt;/p&gt;
&lt;h2 id=&quot;compressing-data-with-zlib&quot;&gt;Compressing Data with &lt;code&gt;zlib&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Though &lt;code&gt;png&lt;/code&gt; and &lt;code&gt;jpeg&lt;/code&gt; files are already compressed sometimes you will want to compress other types of files before uploading them (compressing a &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;mov&lt;/code&gt; or &lt;code&gt;jpeg&lt;/code&gt; will often not affect the file size or will make the file &lt;em&gt;larger&lt;/em&gt;). A web server will often be able to accept a &lt;code&gt;POST&lt;/code&gt; that compresses its body data using &lt;code&gt;gzip&lt;/code&gt;. Additionally, some web servers will require that binary data is transformed into a &lt;code&gt;base64EncodedString&lt;/code&gt; before uploading. Base64 encoding will make data size grow by about 33%. Using &lt;code&gt;gzip&lt;/code&gt; will help mitigate that somewhat. Apple’s implementation of &lt;code&gt;gzip&lt;/code&gt; is called &lt;code&gt;.zlib&lt;/code&gt; when compressing data. Swift provides some other compression algorithms such as &lt;code&gt;.lzfse&lt;/code&gt; which will result in smaller files, but they are not widely adopted.&lt;/p&gt;
&lt;p&gt;For example, to encode something as base64 and then compress it with with &lt;code&gt;gzip&lt;/code&gt; so you can upload it, we can use code like below&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; jsonToUpload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;{&quot;&lt;/span&gt;&lt;span&gt;books&lt;/span&gt;&lt;span&gt;&quot;:[{&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;The Three Musketeers&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;Dumas&lt;/span&gt;&lt;span&gt;&quot;}]}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; jsonAsData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jsonToUpload.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .&lt;/span&gt;&lt;span&gt;utf8&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; encodedString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jsonAsData&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;base64EncodedString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; compressedString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; NSData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;: (encodedString&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .&lt;/span&gt;&lt;span&gt;utf8&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;compressed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .zlib) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; Data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates a string of data in &lt;code&gt;JSON&lt;/code&gt; format. It then encodes the &lt;code&gt;String&lt;/code&gt; as a &lt;code&gt;Data&lt;/code&gt; object using &lt;code&gt;.utf8&lt;/code&gt; encoding. Using &lt;code&gt;.utf8&lt;/code&gt; is a standard. If the web server you are uploading to requires a different encoding, you can change it. In the final step, the encoded string is compressed using &lt;code&gt;gzip&lt;/code&gt;. Notice that the &lt;code&gt;.compressed(using:)&lt;/code&gt; method is on &lt;code&gt;NSData&lt;/code&gt; not on &lt;code&gt;Data&lt;/code&gt;. &lt;code&gt;NSData&lt;/code&gt; is an older library, so you convert the compressed &lt;code&gt;NSData&lt;/code&gt; object back to &lt;code&gt;Data&lt;/code&gt; by casting using &lt;code&gt;as Data&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The above example was just to show syntax. The &lt;code&gt;compressedString&lt;/code&gt; will be slightly larger than the &lt;code&gt;encodedString&lt;/code&gt;. Based on the kind of data your app works with and the capabilities of the web server, you will need to experiment with how to encode and compress to get the best results.&lt;/p&gt;
&lt;h2 id=&quot;transferring-data-with-a-server&quot;&gt;Transferring Data With a Server&lt;/h2&gt;
&lt;p&gt;Depending on the original format of the data, your web server may want the &lt;code&gt;.httpBody&lt;/code&gt; compressed. You can do that with some code like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; someJSONData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Data&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; request &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://example.com/uploads/&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;request.httpMethod &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;POST&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set the other header variables here using&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//request.setValue(&quot;value to set&quot;, forHTTPHeaderField: &quot;header field name&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; compressedJSONData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; NSData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;: someJSONData).&lt;/span&gt;&lt;span&gt;compressed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .zlib) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; Data { &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  request.httpBody &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; compressedJSONData&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; task &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSession.shared.&lt;/span&gt;&lt;span&gt;dataTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request) {data, response, error &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //check for errors and response data when the task is done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //response will contain the status and other header messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //data will contain any payload the server returns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  task.&lt;/span&gt;&lt;span&gt;resume&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what the code above is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the payload into a &lt;code&gt;Data&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;URLRequest&lt;/code&gt; as a &lt;code&gt;var&lt;/code&gt; so that you can configure it. If you create it as a &lt;code&gt;let&lt;/code&gt; it will be a &lt;code&gt;GET&lt;/code&gt; request with default header fields.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;gzip&lt;/code&gt; to compress the data and if successful, assign it to the &lt;code&gt;.httpBody&lt;/code&gt; of the &lt;code&gt;request&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a data task for the upload. Upon completion, the &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;response&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; variables will have any response from the server.&lt;/li&gt;
&lt;li&gt;Actually start the task.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;uploading-a-large-file&quot;&gt;Uploading a Large File&lt;/h3&gt;
&lt;p&gt;An issue with the code above is that all of the initial data will be in memory. So for a large image or video upload, this may not work. However, Swift does provide a different method that will upload a file directly from the disk, reading into memory only what is needed at the time.&lt;/p&gt;
&lt;p&gt;Code to implement that would follow this form.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; request &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://example.com/uploads&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;request.httpMethod &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;POST&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set the other header variables here using&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//request.setValue(&quot;value to set&quot;, forHTTPHeaderField: &quot;header field name&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; documentsDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; fileToUpload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; documentsDirectory&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;the-big-giant-file.mp4&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; uploadTask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSession.shared.&lt;/span&gt;&lt;span&gt;uploadTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request, &lt;/span&gt;&lt;span&gt;fromFile&lt;/span&gt;&lt;span&gt;: fileToUpload) {data, response, error &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//get some information from the server when the file has been uploaded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;uploadTask.&lt;/span&gt;&lt;span&gt;resume&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks very similar to the previous example but notice that you do not set an &lt;code&gt;.httpBody&lt;/code&gt;. There will be header values to set though. Most web servers will want to know how large the file is, authentication credentials, and whether the data is multi-part, or raw or other metadata. These will all be set using &lt;code&gt;.setValue(forHTTPHeaderField:)&lt;/code&gt; in the &lt;code&gt;request&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;For larger files, it is often better to use a &lt;code&gt;URLSessionDataDelegate&lt;/code&gt; instead of the single closure with &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;response&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;. When using the delegate, you can write code to monitor the progress of the file transfer as well as respond to authentication challenges and make decisions about caching. You can also implement &lt;code&gt;URLSessionDelegate&lt;/code&gt; and &lt;code&gt;URLSessionTaskDelegate&lt;/code&gt; methods. All of the delegate methods are options, so you only need to implement ones that are important to your application.&lt;/p&gt;
&lt;h3 id=&quot;configuring-background-transfers&quot;&gt;Configuring Background Transfers&lt;/h3&gt;
&lt;p&gt;Another benefit of using the &lt;code&gt;uploadTask&lt;/code&gt; (and its related &lt;code&gt;downloadTask&lt;/code&gt;) instead of the &lt;code&gt;dataTask&lt;/code&gt; is that you can configure the transfer to continue for a time even if the app goes to the background. In that case, instead of using the &lt;code&gt;URLSession.shared&lt;/code&gt; object, you will want to configure a session that has some special properties to allow it to work in the background.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; configuration &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSessionConfiguration.&lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withIdentifier&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;some.unique.identifier&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;configuration&lt;/span&gt;&lt;span&gt;: configuration, &lt;/span&gt;&lt;span&gt;delegate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;delegateQueue&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; uploadTask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;uploadTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request, &lt;/span&gt;&lt;span&gt;fromFile&lt;/span&gt;&lt;span&gt;: fileToUpload)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Completely configuring and supporting the downloads and backgrounding is covered in &lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background&quot;&gt;this article by Apple&lt;/a&gt;. It will require implementing some delegate methods for the session as well as updating some of the methods in the main application delegate. The most important is the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622941-application&quot;&gt;&lt;code&gt;handleEventsForBackgroundURLSession&lt;/code&gt;&lt;/a&gt; which is how the system will wake up your app with information about the upload or download.&lt;/p&gt;
&lt;p&gt;Additionally, download tasks can be paused and restarted. The &lt;code&gt;cancel(byProducingResumeData:)&lt;/code&gt; method on &lt;code&gt;URLSessionDownloadTask&lt;/code&gt; will cancel a download and optionally provide some resume data you can use to resume the download at a later time. You can call this method on the task when the user taps a button or when the app goes to the background or similar. The web server must support byte-range requests and &lt;code&gt;ETag&lt;/code&gt; or &lt;code&gt;Last-Modified&lt;/code&gt; headers. The &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel&quot;&gt;documentation for pausing and restarting&lt;/a&gt; explains how to pause the download and how to resume using &lt;code&gt;downloadTaks(withResumeData:)&lt;/code&gt;. There is not a similar structure for pausing and resuming an upload.&lt;/p&gt;
&lt;h2 id=&quot;resizing-an-image&quot;&gt;Resizing an image&lt;/h2&gt;
&lt;p&gt;Perhaps the simplest way to make a file smaller is to reduce its dimensions. Using &lt;code&gt;CoreImage&lt;/code&gt; you can use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Lanczos_resampling&quot;&gt;Lanczos resampling&lt;/a&gt; algorithm to resize an image while keeping high quality.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;sleeping-dog&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jpeg&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; image &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;contentsOf&lt;/span&gt;&lt;span&gt;: url&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CILanczosScaleTransform&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(image, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputScaleKey) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; converter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ciImage&lt;/span&gt;&lt;span&gt;: result&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what this code is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;CIImage&lt;/code&gt; from a &lt;code&gt;URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;CILanczosScaleTransform&lt;/code&gt; filter.&lt;/li&gt;
&lt;li&gt;Configure the filter to reduce the image dimensions by 50%.&lt;/li&gt;
&lt;li&gt;Convert the scaled image to a &lt;code&gt;UIImage&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recently, Apple has provided a &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; that will work directly on &lt;code&gt;UIImage&lt;/code&gt; objects. The code below will resize a &lt;code&gt;UIImage&lt;/code&gt; to 50%. Notice that the &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; can also use an explicit value for size (unlike the &lt;code&gt;CILanczosScaleTransform&lt;/code&gt;)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; imageToSave &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;named&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;sleeping-dog.jpeg&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; //1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageToSave.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;applying&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scaleX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; renderer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIGraphicsImageRenderer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;: newSize) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; scaledImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderer.&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt; { (context) &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; rect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;origin&lt;/span&gt;&lt;span&gt;: CGPoint.zero, &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;: newSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  imageToSave.&lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: rect) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what this code is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load the image as a &lt;code&gt;UIImage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Scale the size down by half.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; that will render in this new size.&lt;/li&gt;
&lt;li&gt;Actually draw the image into the renderer at the new size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to rendering the image back as a &lt;code&gt;UIImage&lt;/code&gt; the renderer can create &lt;code&gt;jpegData&lt;/code&gt; and &lt;code&gt;pngData&lt;/code&gt; which would save a few steps if you are resizing and converting. The &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer&quot;&gt;full documentation for &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt;&lt;/a&gt; explains the details.&lt;/p&gt;
&lt;h2 id=&quot;resizing-a-video&quot;&gt;Resizing a Video&lt;/h2&gt;
&lt;p&gt;Though the &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; is faster, using the older &lt;code&gt;CoreImage&lt;/code&gt; resizing has a benefit. &lt;code&gt;CoreImage&lt;/code&gt; filters can be applied to video files as a composition. Large video files can be scaled down. The code below will create a composition that will scale a video asset by 50%. Remember that you will need to &lt;code&gt;import AVFoundation&lt;/code&gt; to use these tools.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newAsset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAsset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;:Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jumping-man&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; newSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;some size that you&apos;ve calculated&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; //2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; resizeComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: newAsset, &lt;/span&gt;&lt;span&gt;applyingCIFiltersWithHandler&lt;/span&gt;&lt;span&gt;: { request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; filter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CILanczosScaleTransform&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(request.sourceImage, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;some scale factor&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputScaleKey) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; resultImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: resultImage, &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resizeComposition.renderSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newSize &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s what the code above is doing to create a new composition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an &lt;code&gt;AVAsset&lt;/code&gt; from a &lt;code&gt;URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a variable &lt;code&gt;newSize&lt;/code&gt; to hold the final size.&lt;/li&gt;
&lt;li&gt;In the composition, configure the &lt;code&gt;CIFilter&lt;/code&gt; that will be applied to each frame.&lt;/li&gt;
&lt;li&gt;Calculate the scale factor based on the &lt;code&gt;newSize&lt;/code&gt; variable and the actual size of the &lt;code&gt;request.sourceImage.extent.size&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;renderSize&lt;/code&gt; property of the composition to the new size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don’t set the &lt;code&gt;renderSize&lt;/code&gt; then there will be a black letterbox around the video.&lt;/p&gt;
&lt;p&gt;With the resizing composition, you can now export the &lt;code&gt;AVAsset&lt;/code&gt; as a &lt;code&gt;.mov&lt;/code&gt; or &lt;code&gt;.m4v&lt;/code&gt; file using an &lt;code&gt;AVExportSession&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; asset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAsset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;:Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jumping-man&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; outputMovieURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;exported.mov&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; exporter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetExportSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: asset, &lt;/span&gt;&lt;span&gt;presetName&lt;/span&gt;&lt;span&gt;: AVAssetExportPresetHighestQuality)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//configure exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;the composition you created above&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; //3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; outputMovieURL&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputFileType &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .mov&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//export!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exportAsynchronously&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;completionHandler&lt;/span&gt;&lt;span&gt;: { [&lt;/span&gt;&lt;span&gt;weak&lt;/span&gt;&lt;span&gt; exporter] &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; //4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  DispatchQueue.main.&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;failed &lt;/span&gt;&lt;span&gt;\(error.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;file saved at &lt;/span&gt;&lt;span&gt;\(outputMovieURL)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s what the code above is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load an &lt;code&gt;AVAsset&lt;/code&gt; from a &lt;code&gt;URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;URL&lt;/code&gt; to save the resized movie.&lt;/li&gt;
&lt;li&gt;Apply the resizing composition to an &lt;code&gt;AVAssetExportSession&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Asynchronously export the asset to disk.&lt;/li&gt;
&lt;li&gt;Print the destination &lt;code&gt;URL&lt;/code&gt; of the new movie when it is saved.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another method to resize a video is to use one of the &lt;code&gt;AVAssetExportSession&lt;/code&gt; presets. Apple offers several &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avassetexportsession/export_presets&quot;&gt;size and quality presets&lt;/a&gt; for export sessions. In the line above that creates the &lt;code&gt;exporter&lt;/code&gt;, replace &lt;code&gt;AVAssetExportPresetHighestQuality&lt;/code&gt; with one of the other values. There are generic quality presets: &lt;code&gt;AVAssetExportPresetLowQuality&lt;/code&gt;, &lt;code&gt;AVAssetExportPresetMediumQuality&lt;/code&gt; and there are size presets including: &lt;code&gt;AVAssetExportPreset1280x720&lt;/code&gt; and &lt;code&gt;AVAssetExportPreset640x480&lt;/code&gt;. When using one of the presets you do not need to use a composition unless you want to do further manipulation or unless you need a size or quality combination that is not provided by any preset. As with images, experiment with different settings until you get a balance between quality and size that works for you.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;Some other strategies for working with large files are to chunk data file or split up video files. However, to use this strategy, you will need to coordinate with someone to stitch them back together after they are uploaded or downloaded.&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;AVAssetExportSession&lt;/code&gt; above, you could call it multiple times and pass in a time range perhaps using code like this&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeRangeFromTimeToTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;: startTime, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;: endTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//some code to create the exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; timeRange&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the video and audio of the &lt;code&gt;AVAsset&lt;/code&gt; the splits in the video may be noticable. Because of modern frame rates, the audio would probably be more likely to be noticed. Time ranges can be sub-second, so you would need to experiment. However, iOS would manage the file for you so that the entire &lt;code&gt;AVAsset&lt;/code&gt; is never loaded into memory.&lt;/p&gt;
&lt;p&gt;It is also possible to split &lt;code&gt;Data&lt;/code&gt; objects using &lt;code&gt;.subData(in:)&lt;/code&gt; and looping through the bytes of the object. Again, you would need to also write code to stitch them back together later. Additionally, you would likely need to bring the entire &lt;code&gt;Data&lt;/code&gt; object into a memory buffer, which might not be desirable.&lt;/p&gt;
&lt;p&gt;Another way to handle large video files is to use Apple’s &lt;a href=&quot;https://developer.apple.com/streaming/&quot;&gt;HTTP Live Streaming&lt;/a&gt; tools. These create files that you can upload to any web server that is recognized by iOS and Mac devices. The tools will segment the video as well as create multiple versions so that your users with different bandwidth capabilities can see different quality streams. Most Android devices and web browsers will at least be able to see the video, even if they cannot dynamically switch between streams.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw some different strategies for shrinking and compressing the large image and video files that an iPhone camera can produce. You also saw some strategies for transferring larger data payloads with a web server. As mentioned in the beginning, consider what sizes the images and videos will display and use that to determine what sizes of images to store. Also, consider storing multiple versions of images or using thumbnails when displaying a gallery or list of images.&lt;/p&gt;
&lt;p&gt;Looking to integrate video capabilities into your app? Check out our &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;Video Editor SDK&lt;/a&gt;, &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id=&quot;thanks-for-reading-let-us-know-what-you-think-on-twitter-or-stay-in-the-loop-with-our-newsletter&quot;&gt;Thanks for reading! Let us know what you think on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;, or stay in the loop with our &lt;a href=&quot;https://img.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/h3&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/05/large-file-swift-crop-optimize.png" medium="image"/><category>How-To</category><category>Swift</category><category>iOS App Development</category><category>Tutorial</category></item><item><title>How to Make an Animated GIF Using Swift</title><link>https://img.ly/blog/how-to-make-an-animated-gif-using-swift/</link><guid isPermaLink="true">https://img.ly/blog/how-to-make-an-animated-gif-using-swift/</guid><description>Learn how to create animated GIF files with Swift, and also extract still images from a video!  </description><pubDate>Tue, 10 May 2022 12:07:58 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift to create an animated GIF file. You will also see how to extract individual frames from a video file to convert the whole video or just clips from a video into an animated GIF. The code in this article uses Swift 5 and Xcode 13. The tutorial uses AVFoundation and CoreGraphics. Clone &lt;a href=&quot;https://github.com/waltertyree/expert-eureka&quot;&gt;this repository&lt;/a&gt; for a sample project and example code.&lt;/p&gt;
&lt;h2 id=&quot;animated-gif-basics&quot;&gt;Animated GIF Basics&lt;/h2&gt;
&lt;p&gt;The GIF is one of the older file types on the Internet. But, every platform supports it (not every platform supports every video format). Unlike some other image formats, GIF files can contain one image or a series of images. When a GIF file has a series of images and some metadata to describe how long to show each one, it can substitute for animation and video. Because of this, a platform that doesn’t necessarily support video files can display moving images and animations.&lt;/p&gt;
&lt;p&gt;GIF files are easy to make. However, animated GIF files are not perfect: they are large and inefficient compared to modern video files. They have a limited color palette, and none of the images in the file have compression. For example, the video file we use in the sample project is about 5MB in mp4 format but over 100MB after it becomes an animated GIF. Finally, GIF files don’t have a soundtrack. Still, they are popular and do certain tasks well, so let’s make some using AVFoundation and Swift.&lt;/p&gt;
&lt;h2 id=&quot;the-basic-strategy&quot;&gt;The Basic Strategy&lt;/h2&gt;
&lt;p&gt;In order to create animated GIF files you need a series of images and some metadata to describe how long each image will show. This tutorial will start with how to put these images into a GIF. Then you will see some different strategies for gathering the images. A GIF file contains bitmap images, not vector graphics. When working with bitmap images, a powerful framework to explore is &lt;code&gt;CoreGraphics&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;creating-an-empty-file&quot;&gt;Creating an Empty File&lt;/h3&gt;
&lt;p&gt;The first step is to create an empty file on the disk that will become our animated GIF.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create empty file to hold gif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; destinationFilename &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NSUUID&lt;/span&gt;&lt;span&gt;().uuidString &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;.gif&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; destinationURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fileURLWithPath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NSTemporaryDirectory&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(destinationFilename)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//metadata for gif file to describe it as an animated gif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; fileDictionary &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [kCGImagePropertyGIFDictionary &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  kCGImagePropertyGIFLoopCount &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create the file and set the file properties&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; animatedGifFile &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGImageDestinationCreateWithURL&lt;/span&gt;&lt;span&gt;(destinationURL &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFURL, UTType.gif.identifier &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFString, totalFrames, &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;error creating gif file&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CGImageDestinationSetProperties&lt;/span&gt;&lt;span&gt;(animatedGifFile, fileDictionary &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFDictionary)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates a URL to hold the animated GIF in the user’s &lt;code&gt;Temporary&lt;/code&gt; directory. The location is not crucial – it just needs to be where you have write permission. Next it creates a &lt;code&gt;Dictionary&lt;/code&gt; that will describe the file as an animated GIF file that will loop forever. The &lt;a href=&quot;https://developer.apple.com/documentation/imageio/gif_image_properties&quot;&gt;Apple Documentation for GIF Image Properties&lt;/a&gt; contains the full list of options for the dictionary. Next, you create the empty file using &lt;code&gt;CGImageDestinatonCreateWithURL&lt;/code&gt;. After creating the file, set the properties dictionary. Now there is an empty file ready to accept the frames that will make up the animated GIF.&lt;/p&gt;
&lt;p&gt;When creating the file with &lt;code&gt;CGImageDestinationCreateWithURL&lt;/code&gt;, you give parameters for the URL to the file location, the &lt;code&gt;UTType&lt;/code&gt; identifier to show it will be a &lt;code&gt;.gif&lt;/code&gt; file, and the &lt;code&gt;totalFrames&lt;/code&gt; that it should expect. &lt;code&gt;totalFrames&lt;/code&gt; is just a variable that holds how many actual frames the file will eventually hold. You will need to work out the actual number of frames for your file manually. For example, if you want to display 20 images each second and your GIF will be four seconds long, the total number of frames will be 20 * 4 or 80. The last parameter is always &lt;code&gt;nil&lt;/code&gt;. The &lt;a href=&quot;https://developer.apple.com/documentation/imageio/1465361-cgimagedestinationcreatewithurl&quot;&gt;documentation&lt;/a&gt; indicates it “is reserved for future use” (but this function has been available to the Mac since 2005 and iOS since 2010, so we will see). The documentation also indicates you are responsible for releasing the memory allocated by the creation command, but Swift will handle that for you.&lt;/p&gt;
&lt;p&gt;As you work with &lt;code&gt;CoreGraphics&lt;/code&gt; classes and types, you will notice that they have a slightly different format than other frameworks. That is a hint that &lt;code&gt;CoreGraphics&lt;/code&gt; is one of the older frameworks and that it is all &lt;code&gt;C&lt;/code&gt; based. Swift has bridging code so that the system will worry about translating your &lt;code&gt;Dictionary&lt;/code&gt; to a &lt;code&gt;CFDictionary&lt;/code&gt; and your &lt;code&gt;String&lt;/code&gt; to a &lt;code&gt;CFString&lt;/code&gt; and so on.&lt;/p&gt;
&lt;h3 id=&quot;adding-images&quot;&gt;Adding Images&lt;/h3&gt;
&lt;p&gt;To add images to the empty file, you will need an image for each frame of the animation, and you will need a small &lt;code&gt;Dictionary&lt;/code&gt; to tell the system how long to display the image during the animation. As mentioned above, there are other options you can apply, such as color mapping or frame dimensions.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;Dictionary&lt;/code&gt; to display each frame for 1/20 second could look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; frameDictionary &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [kCGImagePropertyGIFDictionary &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      kCGImagePropertyGIFDelayTime&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 20.0&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the values above are &lt;code&gt;Double&lt;/code&gt; and are not &lt;code&gt;Int&lt;/code&gt;. Using &lt;code&gt;0.05&lt;/code&gt; would have also been fine as it is equal to 1.0/20.0.&lt;/p&gt;
&lt;p&gt;Compressed video files and movies are usually 30, 60, or even 120 frames per second. Recall that animated GIF images do not have compression, so you want to have as low of a frame rate (or as small of an image frame) as you can. Experiment with different frame rates depending on the speed of the action in an animation. Often times 20 or even 10 frames per second will produce high enough quality.&lt;/p&gt;
&lt;p&gt;Now, loop through your images and append each image to the &lt;code&gt;.gif&lt;/code&gt; file on disk using code like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CGImageDestinationAddImage&lt;/span&gt;&lt;span&gt;(animatedGifFile, frameImage, frameDictionary &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFDictionary)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;animatedGifFile&lt;/code&gt; is the URL to the empty file on the disk that you created above. The &lt;code&gt;frameDictionary&lt;/code&gt; is the metadata to add to each frame and the &lt;code&gt;frameImage&lt;/code&gt; is the actual image for the frame. Your image for the frame needs to be a &lt;code&gt;CGImage&lt;/code&gt;. Depending on the source of images you may have &lt;code&gt;UIImage&lt;/code&gt; or &lt;code&gt;CIImage&lt;/code&gt; or something else. Most image formats in Swift have a conversion method. For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let cgImage = UIImage().cgImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let anotherCGImage = CIImage().cgImage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are also &lt;code&gt;CGImage&lt;/code&gt; initializers to read &lt;code&gt;.jpeg&lt;/code&gt; and &lt;code&gt;.png&lt;/code&gt; data.&lt;/p&gt;
&lt;h3 id=&quot;finalizing-the-file&quot;&gt;Finalizing the File&lt;/h3&gt;
&lt;p&gt;Once you finish writing every frame image to the GIF file, close the file and instruct the system to clean up using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CGImageDestinationFinalize(animatedGifFile)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function will return &lt;code&gt;true&lt;/code&gt; if it is successful. You must call this function after appending the frames or the resulting GIF will be unusable. Also, after you call this function, you cannot add more frames.&lt;/p&gt;
&lt;p&gt;At this point the &lt;code&gt;animatedGifFile&lt;/code&gt; URL points to an animated GIF file you can use as needed.&lt;/p&gt;
&lt;h2 id=&quot;collecting-frame-images&quot;&gt;Collecting Frame Images&lt;/h2&gt;
&lt;p&gt;To create the animated GIF, it is necessary to have an image in &lt;code&gt;CGImage&lt;/code&gt; for each frame. It will be easier if they are all rotated the same way and have the same dimensions. Where those images come from is not crucial. They might be a series of files on disk. It could be that your user snaps a series of photos that your app converts to a GIF in real-time. Your app could even intersperse photographs and bitmap drawings. Something to consider regardless of the source of the frame images is how much memory they consume. Loading the entire set of images into an array before making the animated GIF could cause your app to ask the system for gigabytes of memory.&lt;/p&gt;
&lt;h3 id=&quot;extracting-images-from-video&quot;&gt;Extracting Images from Video&lt;/h3&gt;
&lt;p&gt;A common use of animated GIF files is to display videos. So, let’s look at how to extract frames from a video. In some of the other tutorials, you have seen how we can use &lt;code&gt;CoreImage&lt;/code&gt; and &lt;code&gt;AVFoundation&lt;/code&gt; to manipulate each frame of a video. In those cases, you can’t easily change the frames per second. A 60 frames-per-second animated GIF would quickly become an unusably large file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AVFoundation&lt;/code&gt; provides the &lt;code&gt;AVAssetImageGenerator&lt;/code&gt; class specifically to extract frames from &lt;code&gt;AVAssets&lt;/code&gt; at specific times. To extract a single image from an &lt;code&gt;AVAsset&lt;/code&gt; you can use:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;copyCGImage(at requestedTime: CMTime, actualTime: UnsafeMutablePointer&amp;#x3C;CMTime&gt;?)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a &lt;code&gt;CGImage&lt;/code&gt; at or near the &lt;code&gt;requestedTime&lt;/code&gt; in the video. Sometimes, the requested time is between two frames, so the function will extract the closest frame it can and return the &lt;code&gt;actualTime&lt;/code&gt; of that frame. Your code can then decide if it’s an acceptable substitution and what to do about it. Usually, though, you don’t care and can pass &lt;code&gt;nil&lt;/code&gt; for the &lt;code&gt;actualTime&lt;/code&gt; parameter. Notice that the &lt;code&gt;actualTime&lt;/code&gt; is a pointer to a &lt;code&gt;CMTime&lt;/code&gt; variable. This means it is an &lt;a href=&quot;https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID545&quot;&gt;“inout” parameter&lt;/a&gt;. Using “inout” parameters was a common practice when &lt;code&gt;CoreGraphics&lt;/code&gt; and &lt;code&gt;AVFoundation&lt;/code&gt; were originally written. To use an “inout” parameter, you declare the variable before calling the function and then check its value after. Additionally, when you pass the variable into the function, place an ampersand before the variable name.&lt;/p&gt;
&lt;p&gt;For extracting a series of images, &lt;code&gt;copyCGImage&lt;/code&gt; is not efficient. It runs on the calling thread and may block your application. Apple provides a different function when you want to extract many frames from a video asset.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;generateCGImagesAsynchronously&lt;/span&gt;&lt;span&gt;(forTimes requestedTimes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [NSValue], completionHandler handler&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; @escaping&lt;/span&gt;&lt;span&gt; AVAssetImageGeneratorCompletionHandler)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function takes an array of &lt;code&gt;requestedTimes&lt;/code&gt; and attempts to extract the image at each of those timestamps. It will pass the extracted image to the &lt;code&gt;completionHandler&lt;/code&gt;. The handler will execute its code one time for every extraction attempt and will not block the calling thread.&lt;/p&gt;
&lt;p&gt;Before calling the function to extract images, you need to prepare the inputs. First, you create an &lt;code&gt;AVAssetImageGenerator&lt;/code&gt; and configure it.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; movie &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAsset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;swish&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mp4&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; frameGenerator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetImageGenerator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: movie)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frameGenerator.requestedTimeToleranceBefore &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frameGenerator.requestedTimeToleranceAfter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code loads a movie file from the app bundle into an &lt;code&gt;AVAsset&lt;/code&gt; and then creates a generator for that asset. The &lt;code&gt;.requestedTimeToleranceBefore&lt;/code&gt; and &lt;code&gt;.requestedTimeToleranceAfter&lt;/code&gt; parameters say you want the exact frame for any requested time. This will make the extraction of frames slower. If you are trying to tune performance, consider modifying these parameters.&lt;/p&gt;
&lt;p&gt;Now you will need to calculate the timestamp of each frame of the animated GIF. Determine the start time and the duration of the clip you want to create. Then decide what frame rate (frames per second) to use. You can see in the image below that the entire video consists of about 480 frames at 60 frames per second.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;fps-example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 631px) 631px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;631&quot; height=&quot;222&quot; src=&quot;https://img.ly/_astro/fps-example_11Uf6h.webp&quot; srcset=&quot;/_astro/fps-example_11Uf6h.webp 631w&quot;&gt;&lt;/p&gt;
&lt;p&gt;An animated GIF of just the player making the basketball shot would start at 2 seconds and end at 6 seconds of the original video. In the original video, that segment is 240 frames. An animated GIF of the same length at 20 frames per second only needs about 80 frames. The first frame is the one showing at the 2-second timestamp. The next frame should be the one shown at 2 + 1/20 or 2.05 seconds. The next frame would be the one for 2.10 seconds.&lt;/p&gt;
&lt;p&gt;The generator requires the array of timestamps as &lt;code&gt;CMTime&lt;/code&gt; values. You can create the array using code like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; startTime: &lt;/span&gt;&lt;span&gt;Double&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 2.0&lt;/span&gt;&lt;span&gt; //seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; endTime: &lt;/span&gt;&lt;span&gt;Double&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt;  6.0&lt;/span&gt;&lt;span&gt; //seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; frameRate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt; //how many frames per second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; totalFrames &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Int&lt;/span&gt;&lt;span&gt;((endTime &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; startTime) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(frameRate))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; timeStamps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [NSValue]()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; currentFrame &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;..&amp;#x3C;&lt;/span&gt;&lt;span&gt;totalFrames {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; frameTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(currentFrame) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(frameRate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; timeStamp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt;: startTime &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(frameTime), &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  timeStamps.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NSValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;: timeStamp))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code calculates a value for &lt;code&gt;totalFrames&lt;/code&gt; which must be an &lt;code&gt;Int&lt;/code&gt; so that you can use it to loop. But, &lt;code&gt;startTime&lt;/code&gt; and &lt;code&gt;endTime&lt;/code&gt; need to be &lt;code&gt;Double&lt;/code&gt; since the timestamps will be sub-second times. The &lt;code&gt;frameTime&lt;/code&gt; of the first frame is equal to zero in the animated GIF but 2.0 seconds in the original video. Convert that to the video &lt;code&gt;timeStamp&lt;/code&gt; by adding the &lt;code&gt;startTime&lt;/code&gt;. When creating the &lt;code&gt;CMTime&lt;/code&gt; using “600” as the &lt;code&gt;preferredTimescale&lt;/code&gt; is common because most frame rates are divisible into 600 evenly so the math for the timestamp will be more accurate. Finally, wrap the &lt;code&gt;timeStamp&lt;/code&gt; in an &lt;code&gt;NSValue&lt;/code&gt; object and add it to the array. The &lt;code&gt;AVAssetImageGenerator&lt;/code&gt; uses timestamps wrapped in &lt;code&gt;NSValue&lt;/code&gt; objects. &lt;code&gt;NSValue&lt;/code&gt; is just a generic type that holds values.&lt;/p&gt;
&lt;p&gt;With the generator configured and the array of timestamps created, you can extract the images using code like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frameGenerator.generateCGImagesAsynchronously(forTimes: timeStamps) { requestedTime, frameImage, actualTime, result, error in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard let frameImage = frameImage else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print(&quot;no image&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if error != nil {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print(&quot;an error&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //do someting interesting with frameImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;frameImage&lt;/code&gt; in the completion handler will be a &lt;code&gt;CGImage&lt;/code&gt; type if the extraction was successful. As with the function to extract a single image, if the system had to choose a different time than requested, you will be informed of the actual time. You can read about the other parameters in the &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avassetimagegeneratorcompletionhandler&quot;&gt;documentation for &lt;code&gt;AVAssetImageGeneratorCompletionHandler&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, you can add the frame to an open &lt;code&gt;CGImageDestination&lt;/code&gt;, you could write it to a disk to use later, and you could save it to an array. If you don’t write it to an animated GIF file right away, you will need to devise a way to keep the frames in sequence. You may notice that there is not an explicit way for the function to signal that it is finished. Because the completion handler is called for every extraction attempt, a common strategy is to make a simple counter and increment the counter in the completion handler. When the counter variable is equal to the number of frames requested, then you are finished. Another way is to match the &lt;code&gt;requestedTime&lt;/code&gt; to the last &lt;code&gt;timeStamp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;shot-clip-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 337px) 337px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;337&quot; height=&quot;600&quot; src=&quot;https://img.ly/_astro/shot-clip-1_Z1FLwej.webp&quot; srcset=&quot;/_astro/shot-clip-1_Z1FLwej.webp 337w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this article, you saw how to create an animated GIF from a series of images. You also saw how to extract still images from a video at pre-determined timestamps. Clone &lt;a href=&quot;https://github.com/waltertyree/expert-eureka&quot;&gt;this repository&lt;/a&gt; for a simple project that contains the sample code and a video file you can experiment with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! Let us know what you think on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt;, or stay in the loop with our &lt;a href=&quot;https://img.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/05/animate-gif-in-Swift.png" medium="image"/><category>How-To</category><category>Swift</category><category>Tutorial</category></item><item><title>How To Apply Image Filters in WebGL</title><link>https://img.ly/blog/how-to-add-image-filters-in-webgl/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-image-filters-in-webgl/</guid><description>Learn everything you need to get started with kernel-based image filters in WebGL.</description><pubDate>Fri, 08 Apr 2022 13:06:16 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will learn how to apply custom filters to an image in WebGL. You can achieve this goal without using any external library. That is because the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL&quot;&gt;HTML5 &lt;code&gt;canvas&lt;/code&gt;&lt;/a&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL&quot;&gt;element natively supports WebGL&lt;/a&gt; and does not require the use of plug-ins.&lt;/p&gt;
&lt;p&gt;As we have already covered in &lt;a href=&quot;https://img.ly/blog/tag/html5/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;previous articles&lt;/a&gt;, &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt; is a powerful tool that equips you with everything required to manipulate images. This is true regardless of the use of WebGL. So, you can &lt;a href=&quot;https://img.ly/blog/how-to-apply-filters-in-javascript/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;filter images also without using WebGL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, let’s see how to filter images in Vanilla JavaScript with WebGL in your browser. Follow this step-by-step tutorial and learn how to build the following &lt;a href=&quot;https://codesandbox.io/s/how-to-filter-an-image-in-webgl-forked-zkgccv&quot;&gt;demo&lt;/a&gt;:&lt;/p&gt;
&lt;iframe src=&quot;https://codesandbox.io/embed/how-to-filter-an-image-in-webgl-forked-zkgccv?fontsize=14&amp;#x26;hidenavigation=1&amp;#x26;theme=dark&quot; title=&quot;How To Filter an Image in WebGL (forked)&quot; allow=&quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&quot; sandbox=&quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;getting-started-with-webgl&quot;&gt;Getting Started with WebGL&lt;/h2&gt;
&lt;p&gt;If you are not familiar with &lt;a href=&quot;https://get.webgl.org/&quot;&gt;WebGL&lt;/a&gt;, you need to learn a few things before approaching it. To build an application in WebGL, start with the following three concepts.&lt;/p&gt;
&lt;h3 id=&quot;1-textures&quot;&gt;1. Textures&lt;/h3&gt;
&lt;p&gt;Keep in mind that to draw an image in WebGL you have to use a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL&quot;&gt;texture&lt;/a&gt;. To use a texture, WebGL requires you to define the texture coordinates. These coordinates go from 0.0 to 1.0, regardless of the texture size.&lt;/p&gt;
&lt;h3 id=&quot;2-vertex-shader&quot;&gt;2. Vertex shader&lt;/h3&gt;
&lt;p&gt;The vertex shader is a function you have to write in &lt;a href=&quot;https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language&quot;&gt;GLSL&lt;/a&gt; that is in charge of computing the vertex positions. Thanks to it, WebGL can &lt;a href=&quot;https://en.wikipedia.org/wiki/Rasterisation&quot;&gt;rasterize&lt;/a&gt; the draw primitives, which include points, lines, and triangles. When rasterizing these primitives, WebGL calls another user-defined function called fragment shader. In other words, WebGL interpolates the values provided in the vertex shader function while it draws each pixel using the fragment shader function execution.&lt;/p&gt;
&lt;h3 id=&quot;3-fragment-shader&quot;&gt;3. Fragment shader&lt;/h3&gt;
&lt;p&gt;The fragment shader is a function you have to write in GLSL whose goal is to generate a color for each pixel of the draw primitive currently being drawn. This function has little info per pixel, but you can provide it with everything required by using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Data#varyings&quot;&gt;&lt;code&gt;varyings&lt;/code&gt;&lt;/a&gt; variables. These allow you to pass values from the vertex shader function to the fragment shader function.&lt;/p&gt;
&lt;h2 id=&quot;filtering-images-with-kernels-in-webgl-with-canvas&quot;&gt;Filtering Images with Kernels in WebGL with &lt;code&gt;&amp;#x3C;canvas&gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;There are many ways to filter images, but the most common one involves the &lt;a href=&quot;https://en.wikipedia.org/wiki/Convolution&quot;&gt;convolution operation&lt;/a&gt;. This is because when used on images, convolution applies a filter by taking the weighted sum of a square of pixels and assigning the resulting value to the current pixel. This logic is applied to every pixel the image consists of. Therefore, you can now imagine why convolution is one of the most relevant concepts when it comes to image processing.&lt;/p&gt;
&lt;p&gt;The coefficients used to perform the weighted sum come from a matrix called &lt;a href=&quot;https://en.wikipedia.org/wiki/Kernel%5F(image_processing)&quot;&gt;&lt;em&gt;kernel&lt;/em&gt;&lt;/a&gt;. The kernel represents the filter you want to apply through the convolution operation. So, by changing the kernel, the resulting image will change accordingly. Some kernels are more useful than others and can be used for blurring, sharpening, performing edge detection, and other operations. You can find a list of the most popular &lt;a href=&quot;https://en.wikipedia.org/wiki/Kernel%5F(image_processing)#Details&quot;&gt;kernels on Wikipedia&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, let’s see how to implement kernel image filtering in WebGL.&lt;/p&gt;
&lt;p&gt;Clone the &lt;a href=&quot;https://github.com/Tonel/how-to-filter-an-image-in-webgl-imgly&quot;&gt;GitHub repository that supports this article&lt;/a&gt; with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/how-to-filter-an-image-in-webgl-imgly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Additionally, try the demo application by launching &lt;code&gt;how-to-filter-an-image-in-webgl-imgly/index.html&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;p&gt;Otherwise, you can find the JavaScript function taking care of implementing the filter logic in WebGL below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; filterImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;canvas&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;originalImage&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;kernel&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // assuming the kernel is a square matrix&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; kernelSize&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;sqrt&lt;/span&gt;&lt;span&gt;(kernel.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; gl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; canvas.&lt;/span&gt;&lt;span&gt;getContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;webgl&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // clearing the canvas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;clearColor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;clear&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;COLOR_BUFFER_BIT&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; vertexShaderSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  attribute vec2 position;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  varying vec2 v_coordinate;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void main() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl_Position = vec4(position, 0, 1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    v_coordinate = gl_Position.xy * 0.5 + 0.5;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; fragmentShaderSource&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  precision mediump float;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // the varible defined in the vertex shader above&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  varying vec2 v_coordinate;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  uniform vec2 imageSize;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  uniform sampler2D u_texture;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  void main() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    vec2 position = vec2(v_coordinate.x, 1.0 - v_coordinate.y);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    vec2 onePixel = vec2(1, 1) / imageSize;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    vec4 color = vec4(0);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mat3 kernel = mat3(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ${&lt;/span&gt;&lt;span&gt;kernel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;,&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // implementing the convolution operation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for(int i = 0; i &amp;#x3C; ${&lt;/span&gt;&lt;span&gt;kernelSize&lt;/span&gt;&lt;span&gt;}; i++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      for(int j = 0; j &amp;#x3C; ${&lt;/span&gt;&lt;span&gt;kernelSize&lt;/span&gt;&lt;span&gt;}; j++) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // retrieving the sample position pixel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        vec2 samplePosition = position + vec2(i - 1 , j - 1) * onePixel;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // retrieving the sample color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        vec4 sampleColor = texture2D(u_texture, samplePosition);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        sampleColor *= kernel&lt;/span&gt;&lt;span&gt;\[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;\]&lt;/span&gt;&lt;span&gt;[j];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        color += sampleColor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    color.a = 1.0;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl_FragColor = color;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; vertexShader&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; compileShader&lt;/span&gt;&lt;span&gt;(gl, gl.&lt;/span&gt;&lt;span&gt;VERTEX_SHADER&lt;/span&gt;&lt;span&gt;, vertexShaderSource);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; fragmentShader&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; compileShader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;FRAGMENT_SHADER&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fragmentShaderSource&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // iniziailing the program&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; program&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; createProgram&lt;/span&gt;&lt;span&gt;(gl, vertexShader, fragmentShader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; positionAttributeLocation&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;getAttribLocation&lt;/span&gt;&lt;span&gt;(program, &lt;/span&gt;&lt;span&gt;&apos;position&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; imageSizeLocation&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;getUniformLocation&lt;/span&gt;&lt;span&gt;(program, &lt;/span&gt;&lt;span&gt;&apos;imageSize&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // binding the position buffer to positionBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; positionBuffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;createBuffer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;bindBuffer&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;ARRAY_BUFFER&lt;/span&gt;&lt;span&gt;, positionBuffer);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // using the program defined above&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;useProgram&lt;/span&gt;&lt;span&gt;(program);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // enabling the texcoord attribute&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;enableVertexAttribArray&lt;/span&gt;&lt;span&gt;(positionAttributeLocation);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // setting up the size of the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;uniform2f&lt;/span&gt;&lt;span&gt;(imageSizeLocation, canvas.width, canvas.height);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // telling positionAttributeLocation how to retrieve data out of positionBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;vertexAttribPointer&lt;/span&gt;&lt;span&gt;(positionAttributeLocation, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;FLOAT&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // provide the texture coordinates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;bufferData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;ARRAY_BUFFER&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    new&lt;/span&gt;&lt;span&gt; Float32Array&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;STATIC_DRAW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // loading the original image as a texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; texture&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;createTexture&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  texture.image &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // setting the anonymous mode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Learn more about it here:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  texture.image.crossOrigin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  texture.image.src &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; originalImage.src;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  texture.image.&lt;/span&gt;&lt;span&gt;onload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;bindTexture&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;TEXTURE_2D&lt;/span&gt;&lt;span&gt;, texture);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // setting the parameters to be able to render any image,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // regardless of its size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;texParameteri&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;TEXTURE_2D&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;TEXTURE_WRAP_S&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;CLAMP_TO_EDGE&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;texParameteri&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;TEXTURE_2D&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;TEXTURE_WRAP_T&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;CLAMP_TO_EDGE&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;texParameteri&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;TEXTURE_2D&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;TEXTURE_MAG_FILTER&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;NEAREST&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;texParameteri&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;TEXTURE_2D&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;TEXTURE_MIN_FILTER&lt;/span&gt;&lt;span&gt;, gl.&lt;/span&gt;&lt;span&gt;NEAREST&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // loading the original image as a texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;texImage2D&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      gl.&lt;/span&gt;&lt;span&gt;TEXTURE_2D&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      gl.&lt;/span&gt;&lt;span&gt;RGBA&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      gl.&lt;/span&gt;&lt;span&gt;RGBA&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      gl.&lt;/span&gt;&lt;span&gt;UNSIGNED_BYTE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      texture.image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;drawArrays&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;TRIANGLES&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; compileShader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gl&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;shaderSource&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; shader&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;createShader&lt;/span&gt;&lt;span&gt;(type);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;shaderSource&lt;/span&gt;&lt;span&gt;(shader, shaderSource);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;compileShader&lt;/span&gt;&lt;span&gt;(shader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; outcome&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;getShaderParameter&lt;/span&gt;&lt;span&gt;(shader, gl.&lt;/span&gt;&lt;span&gt;COMPILE_STATUS&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (outcome &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // logging the error message on failure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;getShaderInfoLog&lt;/span&gt;&lt;span&gt;(shader));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;deleteShader&lt;/span&gt;&lt;span&gt;(shader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; shader;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; createProgram&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;gl&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;vertexShader&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fragmentShader&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; program&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;createProgram&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;attachShader&lt;/span&gt;&lt;span&gt;(program, vertexShader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;attachShader&lt;/span&gt;&lt;span&gt;(program, fragmentShader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  gl.&lt;/span&gt;&lt;span&gt;linkProgram&lt;/span&gt;&lt;span&gt;(program);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; outcome&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gl.&lt;/span&gt;&lt;span&gt;getProgramParameter&lt;/span&gt;&lt;span&gt;(program, gl.&lt;/span&gt;&lt;span&gt;LINK_STATUS&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (outcome &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // logging the error message on failure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(gl.&lt;/span&gt;&lt;span&gt;getProgramInfoLog&lt;/span&gt;&lt;span&gt;(program));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    gl.&lt;/span&gt;&lt;span&gt;deleteProgram&lt;/span&gt;&lt;span&gt;(program);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; program;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;filterImage()&lt;/code&gt; function is where the magic happens. It takes the following three parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;canvas&lt;/code&gt;: an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element&quot;&gt;&lt;code&gt;Element&lt;/code&gt;&lt;/a&gt; object representing an HTML &lt;code&gt;canvas&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;&lt;code&gt;originalImage&lt;/code&gt;: an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element&quot;&gt;&lt;code&gt;Element&lt;/code&gt;&lt;/a&gt; object representing an HTML &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img&quot;&gt;&lt;code&gt;img&lt;/code&gt;&lt;/a&gt; element&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kernel&lt;/code&gt;: an array containing the values of the kernel to use in the convolution operation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first part of the function takes care of extracting the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext&quot;&gt;&lt;code&gt;WebGLRenderingContext&lt;/code&gt;&lt;/a&gt; object representing a three-dimensional rendering context from the &lt;code&gt;canvas&lt;/code&gt;  element. Then, the vertex shader and fragment shader functions are defined as strings written in GLSL. Next, they are compiled and finally used to create a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGLProgram&quot;&gt;WebGL program&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The last part of the function creates a texture starting from the original image passed as a parameter and passes it to the &lt;code&gt;WebGLRenderingContext&lt;/code&gt; to produce the final result. This represents the filtered image and is finally displayed by the browser in the &lt;code&gt;canvas&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;The results produced by the &lt;code&gt;filterImage()&lt;/code&gt; function depend on the kernel chosen, as you can verify by playing with the live demo you can find at the beginning of the article.&lt;/p&gt;
&lt;p&gt;Et voilà! You just learned how to filter images in WebGL!&lt;/p&gt;
&lt;h2 id=&quot;final-considerations&quot;&gt;Final Considerations&lt;/h2&gt;
&lt;p&gt;As shown above, you can filter images with WebGL in Vanilla JavaScript with a hundred lines. At the same time, this cannot be considered an easy task to achieve. The reason is that getting into WebGL takes time and effort. Plus, you have to learn how to use GLSL to write the vertex shader and the fragment shader functions. So, things can get more complicated than expected.&lt;/p&gt;
&lt;p&gt;Plus, you learned how to perform non-complex filters, but several filtering techniques are complex and do not involve the convolution operation. That also means that implementing them can be challenging and result in inefficient algorithms.&lt;/p&gt;
&lt;p&gt;To avoid a headache, you should consider a complete and all-in-one solution like &lt;a href=&quot;https://img.ly/products/photo-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditor SDK&lt;/a&gt;. Commercial solutions make things easier and shield you from all difficulties, offering features that would be complex, time-consuming, and challenging to implement. That is particularly true when it comes to using WebGL, which is &lt;a href=&quot;https://img.ly/docs/pesdk/web/introduction/migration-guide/#canvas-renderer/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditor SDK’s main renderer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You will be able to harness WebGL’s power without writing a single line of GLSL or knowing about its existence. WebGL will be used behind the scene for you! Keep also in mind that you would not be alone, since developers at &lt;a href=&quot;https://img.ly/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;IMG.LY&lt;/a&gt; are happy to provide support.&lt;/p&gt;
&lt;h2 id=&quot;filtering-images-with-photoeditor-sdk&quot;&gt;Filtering Images With PhotoEditor SDK&lt;/h2&gt;
&lt;p&gt;Read the article from &lt;a href=&quot;https://img.ly/docs/pesdk/guides/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;the official documentation&lt;/a&gt; to learn &lt;a href=&quot;https://img.ly/docs/pesdk/web/guides/umd/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;how to get started&lt;/a&gt; with &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; in HTML and Vanilla JavaScript. In detail, the &lt;a href=&quot;https://img.ly/docs/pesdk/web/features/filters/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;Filters&lt;/a&gt; feature gives you more than 60 high-quality filters to play with. Achieve the following result with a few clicks:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;PE.SDK ships with stunning preset filters for aesthetic photo creations.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 600px) 600px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;600&quot; height=&quot;321&quot; src=&quot;https://img.ly/_astro/add-image-filters-webGL-1_1apHk1.webp&quot; srcset=&quot;/_astro/add-image-filters-webGL-1_1apHk1.webp 600w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Check out this feature on the &lt;a href=&quot;https://img.ly/products/photo-sdk/demo&quot;&gt;PhotoEditor SDK demo page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this article, we looked at how to filter an image in WebGL with the HTML &lt;code&gt;canvas&lt;/code&gt; element. Implementing a feature allowing users to filter images through a kernel-based approach in WebGL involves only a dozen of lines of code. However, understanding how WebGL works and coding in GLSL cannot be considered easy tasks.&lt;/p&gt;
&lt;p&gt;As a result, you might want to avoid dealing with WebGL entirely. In this case, consider a commercial and easy-to-adopt solution using WebGL behind the scene – such as &lt;a href=&quot;https://img.ly/products/photo-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;PhotoEditor SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this article helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions.‌&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2022/04/add-filter-image-webgl.png" medium="image"/><category>How-To</category><category>Photo Editing</category><category>Web Development</category><category>App Development</category><category>Photo Editor</category><category>Photo Filter</category><category>Tech</category><category>Tutorial</category></item><item><title>How To Add Overlays to a Video in React Native</title><link>https://img.ly/blog/how-to-add-overlays-to-a-video-in-react-native/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-overlays-to-a-video-in-react-native/</guid><description>Learn how to place text or images over your videos in React Native.</description><pubDate>Mon, 28 Mar 2022 16:23:32 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will learn to add a text and image overlay to a video with JavaScript in &lt;a href=&quot;https://reactnative.dev/&quot;&gt;React Native&lt;/a&gt;. All you need is &lt;a href=&quot;https://github.com/TheWidlarzGroup/react-native-video&quot;&gt;React Native Video&lt;/a&gt;, the correct styling rules, and just a few lines of code.&lt;/p&gt;
&lt;p&gt;By following this guide, you will easily achieve a logo and text overlay:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;qtNLErZ&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 188px) 188px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;188&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/qtNLErZ_Z1IVhSU.webp&quot; srcset=&quot;/_astro/qtNLErZ_Z1IVhSU.webp 188w&quot;&gt;&lt;/p&gt;
&lt;p&gt;First, let’s learn how to implement a React Native component that allows you to add an overlay image and overlay text to a video.&lt;/p&gt;
&lt;h2 id=&quot;what-is-react-native-video&quot;&gt;What is React Native Video?&lt;/h2&gt;
&lt;p&gt;Mobile apps come with several native interfaces and features. This is what makes React Native different from React, which is based on browser rendering. So, &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; becomes &lt;a href=&quot;https://reactnative.dev/docs/view&quot;&gt;&lt;code&gt;&amp;#x3C;View&gt;&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;&amp;#x3C;p&gt;&lt;/code&gt; becomes &lt;a href=&quot;https://reactnative.dev/docs/text&quot;&gt;&lt;code&gt;&amp;#x3C;Text&gt;&lt;/code&gt;&lt;/a&gt;, and so on. In particular, React Native supports only a limited amount of components. You can find the entire list &lt;a href=&quot;https://reactnative.dev/docs/components-and-apis&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To this day, React Native does not have a native &lt;code&gt;&amp;#x3C;Video&gt;&lt;/code&gt; component. That is why &lt;code&gt;react-native-video&lt;/code&gt; was born - a widely used library without considering alternatives. This 500kB package equips you with everything you need to get started with videos in React Native.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Here is the list of all the prerequisites for the demo application you are going to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.npmjs.com/getting-started/&quot;&gt;Node.js and npm&lt;/a&gt; &lt;a href=&quot;https://docs.npmjs.com/getting-started/&quot;&gt;7+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native&quot;&gt;&lt;code&gt;react-native&lt;/code&gt;&lt;/a&gt; &gt;= 0.6x&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-video&quot;&gt;&lt;code&gt;react-native-video&lt;/code&gt;&lt;/a&gt; &gt;= 5.x&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can add &lt;code&gt;react-native-video&lt;/code&gt; to your project’s dependencies by launching the following npm command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install react-native-video&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or if you are a yarn user:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;yarn add react-native-video&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, follow &lt;a href=&quot;https://github.com/TheWidlarzGroup/react-native-video#installation&quot;&gt;this&lt;/a&gt; guide to learn how to link &lt;code&gt;react-native-video&lt;/code&gt; to your project. This step is not required for Android users with React Native 0.60 and above.&lt;/p&gt;
&lt;h2 id=&quot;adding-overlay-image-and-text-to-a-video-in-react-native&quot;&gt;Adding Overlay Image and Text to a Video in React Native&lt;/h2&gt;
&lt;p&gt;You can clone the &lt;a href=&quot;https://github.com/Tonel/how-to-add-overlays-to-videos-in-react-native-imgly&quot;&gt;GitHub repository supporting this article&lt;/a&gt; and launch the following commands to try the demo application:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;git clone https://github.com/Tonel/how-to-add-overlays-to-videos-in-react-native-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd how-to-add-overlays-to-videos-in-react-native-imgly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The project is a React Native Create App project, and you can learn more about how it works and how to run it &lt;a href=&quot;https://reactnative.dev/blog/2017/03/13/introducing-create-react-native-app&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Otherwise, keep following this step-by-step tutorial and learn how to build a React Native component for adding overlays to a video.&lt;/p&gt;
&lt;h3 id=&quot;1-initializing-a-react-native-project&quot;&gt;1. Initializing a React Native Project&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/blog/2017/03/13/introducing-create-react-native-app&quot;&gt;Create React Native App&lt;/a&gt; is the officially supported and easiest way to create an empty and working React Native application. Install it globally with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm install -g create-react-native-app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can initialize a new project called &lt;code&gt;react-native-video-overlays-demo&lt;/code&gt; with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;create-react-native-app react-native-video-overlays-demo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;react-native-video-overlays-demo&lt;/code&gt; folder should now contain a demo project with the following file structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;react-native-video-overlays-demo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .buckconfig&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitattributes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── App.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── app.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── babel.config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── index.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── metro.config.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── package-lock.json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── yarn.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;├── android&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;│   └── ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;└── ios&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    └── ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enter the &lt;code&gt;react-native-video-overlays-demo&lt;/code&gt; folder:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cd react-native-video-overlays-demo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then launch the development server with one of the following commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for launching an &lt;a href=&quot;https://expo.dev/&quot;&gt;Expo&lt;/a&gt; app&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for Android development on your phone or an emulator&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run android&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for iOS development on your phone or an emulator&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run ios&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for &lt;a href=&quot;https://github.com/necolas/react-native-web&quot;&gt;&lt;code&gt;react-native-web&lt;/code&gt;&lt;/a&gt; development&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm run web&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-defining-a-component-to-add-overlays-on-a-video&quot;&gt;2. Defining a Component to Add Overlays on a Video&lt;/h3&gt;
&lt;p&gt;Let’s see how to build a &lt;code&gt;VideoWithOverlays&lt;/code&gt; component that allows you to add overlays such as text and an image to a video. You can implement it as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React, { useEffect, useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Image, StyleSheet, Text, View } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react-native&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Video &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react-native-video&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; VideoWithOverlays&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoComponentProps&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  text&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  imageSrc&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    video&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayText&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    videoWithOverlays&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayTextView&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayImageView&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    overlayImage&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; styles;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setHeight&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;overlayHeight&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setOverlayHeight&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // setting the overlay height to 10px starting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // from the bottom of the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    setOverlayHeight&lt;/span&gt;&lt;span&gt;(height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }, [height]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{videoWithOverlays}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;Video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;videoComponentProps}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{video}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        onLayout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; event.nativeEvent.layout;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          setHeight&lt;/span&gt;&lt;span&gt;(height);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;overlayTextView, top: overlayHeight }}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {text &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{overlayText}&gt;{text}&amp;#x3C;/&lt;/span&gt;&lt;span&gt;Text&lt;/span&gt;&lt;span&gt;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;overlayImageView, top: overlayHeight }}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        {imageSrc &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;&lt;/span&gt;&lt;span&gt;Image&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{overlayImage} &lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{imageSrc} &lt;/span&gt;&lt;span&gt;resizeMode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;contain&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; styles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; StyleSheet.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayTextView: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;relative&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alignItems: &lt;/span&gt;&lt;span&gt;&apos;flex-end&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    justifyContent: &lt;/span&gt;&lt;span&gt;&apos;flex-end&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    right: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayImageView: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;relative&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alignItems: &lt;/span&gt;&lt;span&gt;&apos;flex-start&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    justifyContent: &lt;/span&gt;&lt;span&gt;&apos;flex-end&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayImage: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;absolute&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    height: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  overlayText: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fontSize: &lt;/span&gt;&lt;span&gt;25&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    fontWeight: &lt;/span&gt;&lt;span&gt;&apos;bold&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    color: &lt;/span&gt;&lt;span&gt;&apos;white&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  video: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;absolute&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    top: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bottom: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    right: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    marginTop: &lt;/span&gt;&lt;span&gt;&apos;50%&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoWithOverlays: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    position: &lt;/span&gt;&lt;span&gt;&apos;absolute&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    top: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    left: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bottom: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    right: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;react-native-video&lt;/code&gt; &lt;code&gt;Video&lt;/code&gt; component is used to play the video and its props are exposed thanks to the &lt;code&gt;videoComponentProps&lt;/code&gt; prop. Then, after being placed into the layout, its height depending on the size of the device screen is stored in the &lt;code&gt;height&lt;/code&gt; variable. This is used to calculate the height the overlays should be placed at in the &lt;a href=&quot;https://legacy.reactjs.org/docs/hooks-effect.html&quot;&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/a&gt; hook. In detail, the overlays are placed at 10px from the bottom edge of the video.&lt;/p&gt;
&lt;p&gt;The first internal &lt;code&gt;View&lt;/code&gt; represents the optional text overlay. The overlay logic depends entirely on the custom &lt;code&gt;overlayTextView&lt;/code&gt; and &lt;code&gt;overlayText&lt;/code&gt; &lt;a href=&quot;https://reactnative.dev/docs/stylesheet&quot;&gt;StyleSheet&lt;/a&gt; fields, representing the CSS classes responsible for displaying the text passed as a prop with &lt;code&gt;text&lt;/code&gt; as an overlay. By default, the text overlay is placed in the bottom-right corner of the video.&lt;/p&gt;
&lt;p&gt;The second internal &lt;code&gt;View&lt;/code&gt; represents the optional image overlay. The overlay logic depends entirely on the custom &lt;code&gt;overlayImageView&lt;/code&gt; and &lt;code&gt;overlayImage&lt;/code&gt; &lt;a href=&quot;https://reactnative.dev/docs/stylesheet&quot;&gt;StyleSheet&lt;/a&gt; fields, representing the CSS classes taking care of showing the image passed as a prop with &lt;code&gt;imageSrc&lt;/code&gt; as an overlay. By default, the image overlay is placed in the bottom-left corner of the video. As you can see, both overlays are optional.&lt;/p&gt;
&lt;h3 id=&quot;3-putting-it-all-together&quot;&gt;3. Putting It All Together&lt;/h3&gt;
&lt;p&gt;Let’s see the &lt;code&gt;VideoWithOverlays&lt;/code&gt; component defined above in action. All you need to do is replace the &lt;code&gt;App.js&lt;/code&gt; file with this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; React &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { StyleSheet, View } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;react-native&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; VideoWithOverlays &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./src/components/VideoWithOverlays/VideoWithOverlays&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // a static image stored into the assets folder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; imgLy&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;./src/assets/images/IMG_LY.png&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{styles.container}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;&lt;/span&gt;&lt;span&gt;VideoWithOverlays&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        videoComponentProps&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          // replace this free video source with your video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          source: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            uri: &lt;/span&gt;&lt;span&gt;&apos;https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // overlay text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        text&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;IMG.LY&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // overlay image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        imageSrc&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{imgLy}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span&gt;View&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; styles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; StyleSheet.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  container: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    flex: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    backgroundColor: &lt;/span&gt;&lt;span&gt;&apos;#fff&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    alignItems: &lt;/span&gt;&lt;span&gt;&apos;center&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    justifyContent: &lt;/span&gt;&lt;span&gt;&apos;center&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;&amp;#x3C;VideoWitOverlays&gt;&lt;/code&gt; is initialized with a free video source retrieved from a public URL and two overlays. The text overlay states “IMG.LY” and it is placed in the bottom-right corner of the video. While the image overlay represents the IMG.LY logo and is placed in the bottom-left corner of the video. This is a screenshot of the &lt;code&gt;VideoWitOverlays&lt;/code&gt; in action:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;react-native-video-add-text-image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 189px) 189px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;189&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/react-native-video-add-text-image_Z1GJNu7.webp&quot; srcset=&quot;/_astro/react-native-video-add-text-image_Z1GJNu7.webp 189w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;final-considerations&quot;&gt;Final Considerations&lt;/h2&gt;
&lt;p&gt;Adding overlays elements to a video in React Native is not complex and takes just a few lines of code. On the other hand, the approach presented above is just a frontend trick and does not modify the video. You are just showing it behind text or an image, giving the illusion to the users that it is a single source of content.&lt;/p&gt;
&lt;p&gt;If you wanted to let users modify their videos by adding overlays or other elements and then export them, this would involve complex logic and require an advanced UI. Building such a React Native component is a time-consuming task that represents a waste of energy. If that is your goal, you should take into consideration a complete and ready-to-use SDK solution, such as &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditorSDK&lt;/a&gt;. This would provide your users with the ability to add overlays and use many other cool features.&lt;/p&gt;
&lt;h2 id=&quot;add-overlays-to-a-video-with-react-native-module-for-videoeditor-sdk&quot;&gt;Add Overlays to a Video With &lt;a href=&quot;https://www.npmjs.com/package/react-native-videoeditorsdk&quot;&gt;React Native module for VideoEditor SDK&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href=&quot;https://img.ly/docs/pesdk/react-native/getting-started/integration/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;this&lt;/a&gt; page from the official documentation to learn how to get started with VideoEditorSDK in React Native. Then, load your video and start editing it by adding &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/overlays/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;overlays&lt;/a&gt;, &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/text-fonts/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;text&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/stickers/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;stickers&lt;/a&gt;. Follow the previous links to learn more about each feature.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;add-image-text-to-video-react-native&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 333px) 333px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;333&quot; height=&quot;685&quot; src=&quot;https://img.ly/_astro/add-image-text-to-video-react-native_1Mw6kk.webp&quot; srcset=&quot;/_astro/add-image-text-to-video-react-native_1Mw6kk.webp 333w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Download our free mobile demo app from the &lt;a href=&quot;https://apps.apple.com/us/app/img-ly-photo-video-editor/id589839231&quot;&gt;App Store&lt;/a&gt; or &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.photoeditorsdk.android.app&quot;&gt;Google Play&lt;/a&gt;, and try an extensive set of tools to edit videos.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, we learned how to define a React Native component that allows you to reproduce a video and add overlays. Specifically, we used &lt;code&gt;react-native-video&lt;/code&gt; and defined everything required to place text or an image on your video. However, if you intend to export your edit, you will have to build a complex video editing application. In this scenario, you will want to consider a fully-featured, cutting-edge, and easy-to-adopt solution – such as &lt;a href=&quot;https://img.ly/products/video-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt;.&lt;br&gt;
Looking for more video capabilities? Check out our solution for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this article helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions.&lt;/p&gt;</content:encoded><dc:creator>Antonello</dc:creator><media:content url="https://blog.img.ly/2022/03/add-image-text-to-video-react-native-mobile-3.png" medium="image"/><category>How-To</category><category>Image Editing</category><category>Video Editing</category><category>React Native</category><category>React</category><category>Mobile App Development</category><category>Tech</category><category>Tutorial</category></item></channel></rss>