Hôm nay mình sẽ hướng dẫn các bạn một cách dễ nhất để tạo một ứng dụng Android chuyển đổi video sang GIF.
Để tạo ứng dụng này, mình không sử dụng OpenCV vừa phức tạp, lại cồng kềnh. Đơn giản và nhanh nhất là sử dụng một dịch vụ có sẵn, có tên là Cloudinary.
Cụ thể Cloudinary là gì? Cách convert video sang gif sử dụng Cloudinary như thế nào? Việc tích hợp vào ứng dụng android có đơn giản không?
Chúng ta cùng theo dõi bài viết dưới đây nhé.
Nội dung chính của bài viết
Giới thiệu tổng quan về ảnh ảnh động GIF
Trước khi chúng ta bắt tay vào xây dựng app tạo ảnh động cho android, mình muốn giới thiệu kiến thức nền tảng trước về ảnh gif.
#Ảnh GIF là gì?
GIF (viết tắt của Graphics Interchange Format) là một định dạng hình ảnh bitmap có khả năng chuyển động. Chúng ta hiểu đơn giản, ảnh gif là một tập hợp nhiều khung hình chạy liên tục.
Gọi là ảnh động vì GIF không phải là video, tất nhiên là không có âm thanh rồi. Tuy nhiên, khi lật lại vấn đề, nếu video mà bỏ âm thanh đi thì nó cũng có thể coi là ảnh gif.
Bới vậy, nhu cầu về một ứng dụng Android chuyển đổi video thành GIF là rất lớn. Cũng là một ý tưởng tạo ứng dụng không tồi phải không?
Rất nhiều ảnh GIF đã biến người bình thường thành người nổi tiếng trên mạng. Trên mạng không thiếu những ảnh gif rất độc đáo nếu bạn chịu khó search một chút.
#Dịch vụ lưu trữ ảnh/video Cloudinary
Cloudinary là một giải pháp quản lý video và hình ảnh từ đầu đến cuối dựa trên công nghệ đám mây. Cloudinary hỗ trợ lưu trữ, các thao tác, tối ưu và phân phối các file media.
Cloudinary giúp bạn lưu trữ hình ảnh/video và chỉnh sửa ở mức cơ bản như chỉnh kích thước, thêm các hiệu ứng.
Với sự trợ giúp của Cloudinary, bạn có thể convert video thành một ảnh GIF một cách dễ dàng, chỉ cần upload video lên và tận hưởng thành quả.
Cũng giống như các dịch vụ cloud như Firebase, Cloudinary có nhiều gói dịch vụ cả miễn phí và trả phí. Mới đầu bạn cứ dùng gói miễn phí, cũng xả láng cho bạn rồi.
Xây dựng app Android chuyển đổi video sang gif
#Công tác chuẩn bị
Để xây dựng ứng dụng như mình nói, chúng ta cần chuẩn bị trước 2 điều sau:
- Một tài khoản Cloudinary. Nếu chưa bạn có thể đăng ký miễn phí tại đây.
- Android Studio 3.0 hoặc mới hơn.
#Tạo Android Project (Client side)
1. Tạo một android project mới
Đầu tiên để tạo ứng dụng Android chuyển đổi video thành ảnh gif, bạn hãy mở Android Studio và tạo một project Android mới.
Điền tên ứng dụng và package. Nhớ tick vào checkbox “Include Kotlin Support”. Mình sẽ code bằng ngôn ngữ Kotlin thay vì Java nhé (cho hợp với thời đại).
Chúng ta đặt min SDK là level 16 (tức là Android 4.1).
Sau đó bạn chọn Empty Activity, nhập tên cho main Activity và nhấn finish.
Như vậy là xong phần thiết lập cơ bản, chúng ta chuyển sang phần code nhé.
2. Cài đặt và tích hợp Cloudinary vào dự án android
Để sử dụng được các tính năng của Cloudinary, chúng ta cần chèn thêm thư viện (dependency).
Bạn thêm đoạn code sau vào tệp build.gradle
:
implementation group: 'com.cloudinary', name: 'cloudinary-android', version: '1.21.0'
Sau đó bạn mở file AndroidManifest.xml
, và chèn các configurations dưới tag <application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.android.cloudinarysample">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application ... > <meta-data android:name="CLOUDINARY_URL" android:value="cloudinary://@myCloudName"/> </application> </manifest>
Đừng quên thay thế myCloudName bằng tên Cloudinary cá nhân của bạn nhé.
Ngoài ra ứng dụng cũng cần Storage Permission để đọc ghi vào bộ nhớ. Nên các bạn cũng cần phải add thêm android.permission.WRITE_EXTERNAL_STORAGE
permission vào manifest.
❤ Cách yêu cầu cấp quyền runtime permission: Yêu cầu Runtime permission cho ứng dụng Android
Tiếp theo bạn tạo ra một AppController
Class extend từ Application.
public class AppController : Application() { override fun onCreate() { super.onCreate() // Initialize Cloudinary MediaManager.init(this) } }
Class này giúp chúng ta khởi tạo một instance của MediaManager
tồn tại trong suốt vòng đời của ứng dụng.
Sự khởi tạo này giúp thiết lập thư viện với tham số như tên cloud được chèn trong tập tin AndroidManifest trước đó .
Ngoài ra, chúng ta cũng phải bổ sung AppController
vào AndroidManifest
để làm tên của tag ứng dụng như bên dưới.
<application android:name=".AppController" >
3. Thiết kế Layout ứng dụng
Vì tạo layout chỉ đơn giản là kéo thả các UI nên mình sẽ không giải thích về phần này nhé. Kết quả là mình có một snippet cho layout ứng dụng mà chúng ta đang làm.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:layout_margin="16dp" android:gravity="center" android:orientation="vertical" tools:context="com.example.android.cloudinarysample.MainActivity"> <ProgressBar android:id="@+id/progress_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" /> <Button android:id="@+id/button_upload_video" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="Upload Video" /> </LinearLayout>
Trong layout này, chúng ta có một thanh ProgressBar. Tác dụng là nó sẽ hiểu thị trong khi chúng ta upload video hoặc download ảnh gif.
Ngoài ra, chúng ta có một Button để chọn video từ bộ nhớ (local storage).
#Cấu hình Upload video trên Cloudinary (back-end side)
Để tạo app Android chuyển đổi video thành GIF thì ta cần đến Cloudinary hỗ trợ hai kiểu upload: signed và unsigned
1. Signed uploads yêu cầu một chữ ký xác thực phía back-end. Với loại upload này, hình ảnh và video của bạn sẽ được signed với API và secret key được tìm ở bảng console.
Vì việc sử dụng các key phía client side khá không an toàn (có thể dễ dàng bị dịch ngược) nên việc này sẽ thực hiện ở phía Back-end.
2. Unsigned uploads kém an toàn hơn so với Signed uploads. Ngoài ra, Unsigned uploads còn có những hạn chế khác.
Ví dụ như những hình ảnh hiện ra bị đè lên nhau. Mục option mà bạn đã thiết lập trên upload preset cũng bị giới hạn bởi kích thước hay loại tập tin mà người dùng upload lên tài khoản của bạ
Trong demo này, chúng ta sẽ thực hiện unsigned upload cho nó dễ (Còn bạn nào muốn hiểu và code nâng cao thì có thể tìm hiểu thêm về signed upload nhé).
Chúng ta cần kích hoạt unsigned upload trên bảng điều khiển (console). Chọn setting -> tab upload
, cuộn tới nơi bạn có upload preset, chọn unsigned uploading.
Một preset mới được tạo ra với một chuỗi ngẫu nhiên. Sao chép nó lại vì bạn sẽ cần nó để điền vào project.
Tiếp theo bạn lấy video từ bộ nhớ điện thoại, upload lên Cloudinary và Cloudinary sẽ giúp bạn convert video sang gif. Nhiệm vụ của bạn khá đơn giản, chỉ là download tập tin .gif về.
Sau khi cấu hình xong, chúng ta sẽ implement cho phần upload video trong MainActivity.kt
.
private val SELECT_VIDEO: Int = 100 lateinit var TAG:String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) this.TAG = localClassName button_upload_video.setOnClickListener { if (checkStoragePermission()) { openMediaChooser() } else { requestPermission() } } } fun openMediaChooser() { val intent = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) startActivityForResult(intent, SELECT_VIDEO) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == SELECT_VIDEO && resultCode == Activity.RESULT_OK) { progress_bar.visibility = VISIBLE MediaManager.get() .upload(data!!.data) .unsigned("YOUR_PRESET") .option("resource_type", "video") .callback(object : UploadCallback { override fun onStart(requestId: String) { Log.d(TAG, "onStart") } override fun onProgress(requestId: String, bytes: Long, totalBytes: Long) { } override fun onSuccess(requestId: String, resultData: Map<*, *>) { Toast.makeText(this@MainActivity, "Upload successful", Toast.LENGTH_LONG).show() } override fun onError(requestId: String, error: ErrorInfo) { Log.d(TAG,error.description) progress_bar.visibility = INVISIBLE Toast.makeText(this@MainActivity,"Upload was not successful",Toast.LENGTH_LONG).show() } override fun onReschedule(requestId: String, error: ErrorInfo) { Log.d(TAG, "onReschedule") } }).dispatch() } }
#Cấu hình thông số ảnh GIF
Nếu upload thành công, hàm onSuccess()
sẽ được gọi. Hàm này cung cấp cho chúng ta requestId và những thông tin chi tiết từ việc upload.
Bạn có thể truy cập vào URL của những video vừa được upload bằng việc gọi hàm resultData[“url”]. Thay vì một video, bạn cần một tập tin.gif.
Chúng ta có thể dễ dàng có được nó bởi việc thay đổi phần mở rộng của một video thành .gif. Đại khái sẽ như thế này:
val publicId:String = resultData["public_id"] as String val gifUrl: String = MediaManager.get() .url() .resourceType("video") .transformation(Transformation<Transformation<out Transformation<*>>?>().videoSampling("12")!!.width("174").height("232").effect("loop:2")) .generate("$publicId.gif")
Mỗi video được upload đều có một ID riêng biệt từ Cloudinary. ID này được truy cập nằng việc gọi hàm resultData["public_url"].
Giá trị đưa ra gifUrl là kết quả của việc lấy URL Cloudinary dựa trên tên cloud của bạn (thường giống như là res.cloudinary.com/{cloud name}/
), được nối thêm dữ liệu transformations, resource type được truy cập.
unique id
được lưu trữ trên đám mây, và để định dạng đầu ra (trong trường hợp demo của chúng ta thì là gif).
Mình sẽ giải thích kỹ hơn về các thông số trong đoạn code trên: Chiều cao, chiền rộng ảnh gif lần lượt là 232 và 174. Tỉ lệ khung là 12fps, ảnh GIF hai lần.
Nếu không có thiết lập trên, chúng ta có thể sẽ nhận được ảnh gif có dung lượng còn lớn cả video mà ta đã upload lên trước đó. Bạn có thể không rành những thông số này thì nên đọc thêm ở đây
#Tạo tính năng tải ảnh động GIF
Sau khi đã chuyển đổi video sang gif, chúng ta cần cung cấp thêm tính năng tải ảnh gif đó về máy điện thoại của người dùng.
Trong tập tin build.gradle
, chúng ta sẽ thêm các thư viện dependency download:
implementation 'com.mindorks.android:prdownloader:0.2.0'
Trên AppController.kt
, chúng ta khởi tạo thư viện PRDownloader:
override fun onCreate() { super.onCreate() // Initialize Cloudinary public class AppController : Application() { MediaManager.init(this) // Initialize the PRDownload library PRDownloader.initialize(this) } } We then create a function downloadGIF : private fun downloadGIF(url: String, name: String) { val downloadId = PRDownloader .download(url, getRootDirPath(), name).build() .setOnStartOrResumeListener(object : OnStartOrResumeListener { override fun onStartOrResume() { Log.d(TAG,"download started") } }) .setOnPauseListener(object : OnPauseListener { override fun onPause() { } }) .setOnCancelListener(object : OnCancelListener { override fun onCancel() { } }) .setOnProgressListener(object : OnProgressListener { override fun onProgress(progress: Progress) { } }) .start(object : OnDownloadListener { override fun onDownloadComplete() { progress_bar.visibility = INVISIBLE Toast.makeText(this@MainActivity,"Download complete",Toast.LENGTH_LONG).show() } override fun onError(error: Error) { Log.d(TAG,error.toString()) } }) } private fun getRootDirPath(): String { return if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) { val file = ContextCompat.getExternalFilesDirs(this@MainActivity, null)[0] file.absolutePath } else { [email protected] } }
Hàm này xử lí việc download các ảnh động GIF. Hàm RDownload.download()
có ba tham số cần truyền vào:
- Đường link file cần download.
- Thư mục chứa file sẽ download.
- Tên của file sẽ download.
Hàm này sẽ được gọi trong hàn onSuccess
của UploadCallback
sau khi gifUrl được tạo ra:
override fun onSuccess(requestId: String, resultData: Map<*, *>) { ... downloadGIF(gifUrl,"$publicId.gif") }
Ảnh gif được tải về sẽ lưu ở đây: Android/data/{app package name}/files.
Kết luận
Cloudinary cung cấp nhiều APIs giúp giảm gánh nặng lưu trữ và thao tác ảnh/video. Với tutorial này của mình chỉ là một phần nhỏ trong số các tính năng hay ho của Cloudinary.
Bạn có thể download trọn bộ source code của bài viết tại đây:
Như vậy, mình đã kết thúc tutorial về chuyển đổi video sang gif cho Android . Nếu có chỗ nào không hiểu thì comment phía bên dưới nhé.
Đọc thêm:
Cho mình hỏi cái này có mất phí không ạ?
Tùy vào mức bạn sử dụng API, họ có gọi FREE nhưng có giới hạn số lượt gọi.