package me.ccrama.redditslide.util;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.v7.app.NotificationCompat;
import android.view.View;
import android.widget.MediaController;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream;
import com.nostra13.universalimageloader.utils.IoUtils;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.DecimalFormat;
import java.util.UUID;
import me.ccrama.redditslide.Activities.MediaView;
import me.ccrama.redditslide.Activities.Shadowbox;
import me.ccrama.redditslide.Activities.Website;
import me.ccrama.redditslide.Fragments.FolderChooserDialogCreate;
import me.ccrama.redditslide.R;
import me.ccrama.redditslide.Reddit;
import me.ccrama.redditslide.SettingValues;
import me.ccrama.redditslide.Views.MediaVideoView;
import okhttp3.OkHttpClient;
/**
* Created by carlo_000 on 1/29/2016.
*/
public class GifUtils {
private GifUtils() {
}
public static String getSmallerGfy(String gfyUrl) {
gfyUrl = gfyUrl.replaceAll("fat|zippy|giant", "thumbs");
if (!gfyUrl.endsWith("-mobile.mp4")) gfyUrl = gfyUrl.replaceAll("\\.mp4", "-mobile.mp4");
return gfyUrl;
}
public static class AsyncLoadGif extends AsyncTask<String, Void, Void> {
public Activity c;
public MediaVideoView video;
public ProgressBar progressBar;
public View placeholder;
public View gifSave;
public boolean closeIfNull;
public boolean hideControls;
public boolean autostart;
public Runnable doOnClick;
public String subreddit = "";
public boolean cacheOnly;
public TextView size;
public AsyncLoadGif(@NotNull Activity c, @NotNull MediaVideoView video,
@Nullable ProgressBar p, @Nullable View placeholder, @Nullable Runnable gifSave,
@NotNull boolean closeIfNull, @NotNull boolean hideControls, boolean autostart, String subreddit) {
this.c = c;
this.subreddit = subreddit;
this.video = video;
this.progressBar = p;
this.closeIfNull = closeIfNull;
this.placeholder = placeholder;
this.doOnClick = gifSave;
this.hideControls = hideControls;
this.autostart = autostart;
}
public AsyncLoadGif(@NotNull Activity c, @NotNull MediaVideoView video,
@Nullable ProgressBar p, @Nullable View placeholder, @Nullable Runnable gifSave,
@NotNull boolean closeIfNull, @NotNull boolean hideControls, boolean autostart,
TextView size, String subreddit) {
this.c = c;
this.video = video;
this.subreddit = subreddit;
this.progressBar = p;
this.closeIfNull = closeIfNull;
this.placeholder = placeholder;
this.doOnClick = gifSave;
this.hideControls = hideControls;
this.autostart = autostart;
this.size = size;
}
public void onError() {
}
public AsyncLoadGif(@NotNull Activity c, @NotNull MediaVideoView video,
@Nullable ProgressBar p, @Nullable View placeholder, @NotNull boolean closeIfNull,
@NotNull boolean hideControls, boolean autostart, String subreddit) {
this.c = c;
this.video = video;
this.subreddit = subreddit;
this.progressBar = p;
this.closeIfNull = closeIfNull;
this.placeholder = placeholder;
this.hideControls = hideControls;
this.autostart = autostart;
}
public AsyncLoadGif() {
cacheOnly = true;
}
public void cancel() {
LogUtil.v("cancelling");
if (stream != null) {
try {
stream.close();
is.close();
} catch (IOException e) {
LogUtil.e(e, "Error cancelling");
}
}
}
@Override
public void onCancelled() {
super.onCancelled();
cancel();
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
public void showGif(final URL url, final int tries, final String subreddit) {
if (tries < 2) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
final File downloaded = GifCache.getGif(url);
LogUtil.v("Path is " + "file://" + downloaded);
video.setVideoPath("file://" + downloaded);
//videoView.set
if (placeholder != null && !hideControls && !(c instanceof Shadowbox)) {
MediaController mediaController = new MediaController(c);
mediaController.setAnchorView(placeholder);
video.setMediaController(mediaController);
}
showProgressBar(c, progressBar, false);
if (gifSave != null) {
gifSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
saveGif(downloaded, c, subreddit);
}
});
} else if (doOnClick != null) {
MediaView.doOnClick = new Runnable() {
@Override
public void run() {
saveGif(downloaded, c, subreddit);
try {
Toast.makeText(c, "Downloading image...",
Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {
}
}
};
}
video.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
if (placeholder != null) placeholder.setVisibility(View.GONE);
mp.setLooping(true);
}
});
if (autostart) {
video.start();
if (!video.isPlaying()) {
showGif(url, tries + 1, subreddit);
}
}
}
});
}
}
public enum VideoType {
IMGUR, VID_ME, STREAMABLE, GFYCAT, DIRECT, OTHER
}
public String formatUrl(String s) {
if (s.endsWith("v") && !s.contains("streamable.com")) {
s = s.substring(0, s.length() - 1);
} else if (s.contains("gfycat") && (!s.contains("mp4") && !s.contains("webm"))) {
if (s.contains("-size_restricted")) s = s.replace("-size_restricted", "");
}
if ((s.contains(".webm") || s.contains(".gif")) && !s.contains(".gifv") && s.contains(
"imgur.com")) {
s = s.replace(".gif", ".mp4");
s = s.replace(".webm", ".mp4");
}
if (s.endsWith("/")) s = s.substring(0, s.length() - 1);
if(s.endsWith("?r")) s = s.substring(0, s.length() - 2);
return s;
}
public VideoType getVideoType(String url) {
if (url.contains(".mp4") || url.contains("webm")) return VideoType.DIRECT;
if (url.contains("gfycat") && !url.contains("mp4")) return VideoType.GFYCAT;
if (url.contains("imgur.com")) return VideoType.IMGUR;
if (url.contains("vid.me")) return VideoType.VID_ME;
if (url.contains("streamable.com")) return VideoType.STREAMABLE;
return VideoType.OTHER;
}
OkHttpClient client = Reddit.client;
public void loadGfycat(String name,Gson gson ) throws Exception {
showProgressBar(c, progressBar, false);
if(!name.startsWith("/"))
name = "/" + name;
String gfycatUrl = "https://gfycat.com/cajax/get" + name;
LogUtil.v(gfycatUrl);
final JsonObject result = HttpUtil.getJsonObject(client, gson, gfycatUrl);
String obj = "";
if (result == null
|| result.get("gfyItem") == null
|| result.getAsJsonObject("gfyItem").get("mp4Url").isJsonNull()) {
onError();
if (closeIfNull) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
new AlertDialogWrapper.Builder(c).setTitle(
R.string.gif_err_title)
.setMessage(R.string.gif_err_msg)
.setCancelable(false)
.setPositiveButton(R.string.btn_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
c.finish();
}
})
.create()
.show();
} catch (Exception e) {
}
}
});
}
} else {
if (result.getAsJsonObject("gfyItem").has("mobileUrl")) {
obj = result.getAsJsonObject("gfyItem")
.get("mobileUrl")
.getAsString();
} else {
obj = result.getAsJsonObject("gfyItem").get("mp4Url").getAsString();
}
}
showProgressBar(c, progressBar, false);
final URL finalUrl = new URL(obj);
writeGif(finalUrl, progressBar, c, AsyncLoadGif.this, subreddit);
}
@Override
protected Void doInBackground(String... sub) {
MediaView.didLoadGif = false;
Gson gson = new Gson();
final String url = formatUrl(sub[0]);
VideoType videoType = getVideoType(url);
LogUtil.v(url + ", VideoType: " + videoType);
switch (videoType) {
case GFYCAT:
String name = url.substring(
url.lastIndexOf("/", url.length()));
String gfycatUrl = "https://gfycat.com/cajax/get" + name;
try {
loadGfycat(name, gson);
} catch (Exception e) {
LogUtil.e(e, "Error loading gfycat video url = ["
+ url
+ "] gfycatUrl = ["
+ gfycatUrl
+ "]");
}
break;
case DIRECT:
case IMGUR:
try {
writeGif(new URL(url), progressBar, c, AsyncLoadGif.this, subreddit);
} catch (Exception e) {
LogUtil.e(e,
"Error loading URL " + url); //Most likely is an image, not a gif!
if (c instanceof MediaView && url.contains("imgur.com") && url.endsWith(
".mp4")) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
(c).startActivity(new Intent(c, MediaView.class).putExtra(
MediaView.EXTRA_URL, url.replace(".mp4",
".png"))); //Link is likely an image and not a gif
(c).finish();
}
});
} else {
if (closeIfNull) {
Intent web = new Intent(c, Website.class);
web.putExtra(Website.EXTRA_URL, url);
web.putExtra(Website.EXTRA_COLOR, Color.BLACK);
c.startActivity(web);
c.finish();
}
}
}
break;
case STREAMABLE:
String hash = url.substring(url.lastIndexOf("/") + 1, url.length());
String streamableUrl = "https://api.streamable.com/videos/" + hash;
LogUtil.v(streamableUrl);
try {
final JsonObject result =
HttpUtil.getJsonObject(client, gson, streamableUrl);
String obj = "";
if (result == null
|| result.get("files") == null
|| !(result.getAsJsonObject("files").has("mp4")
|| result.getAsJsonObject("files").has("mp4-mobile"))) {
onError();
if (closeIfNull) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialogWrapper.Builder(c).setTitle(
R.string.error_video_not_found)
.setMessage(R.string.error_video_message)
.setCancelable(false)
.setPositiveButton(R.string.btn_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
c.finish();
}
})
.create()
.show();
}
});
}
} else {
if (result.getAsJsonObject()
.get("files")
.getAsJsonObject()
.has("mp4-mobile") && !result.getAsJsonObject().get("files").getAsJsonObject().get("mp4-mobile").getAsJsonObject().get("url").getAsString().isEmpty()) {
obj = "https:" + result.getAsJsonObject()
.get("files")
.getAsJsonObject()
.get("mp4-mobile")
.getAsJsonObject()
.get("url")
.getAsString();
} else {
obj = "https:" + result.getAsJsonObject()
.get("files")
.getAsJsonObject()
.get("mp4")
.getAsJsonObject()
.get("url")
.getAsString();
}
}
final URL finalUrl = new URL(obj);
writeGif(finalUrl, progressBar, c, AsyncLoadGif.this, subreddit);
} catch (Exception e) {
LogUtil.e(e, "Error loading streamable video url = ["
+ url
+ "] streamableUrl = ["
+ streamableUrl
+ "]");
c.runOnUiThread(new Runnable() {
@Override
public void run() {
onError();
}
});
if (closeIfNull) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
new AlertDialogWrapper.Builder(c).setTitle(
R.string.error_video_not_found)
.setMessage(R.string.error_video_message)
.setCancelable(false)
.setPositiveButton(R.string.btn_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
c.finish();
}
})
.create()
.show();
} catch (Exception e) {
}
}
});
}
}
break;
case VID_ME:
String vidmeUrl = "https://api.vid.me/videoByUrl?url=" + url;
LogUtil.v(vidmeUrl);
try {
final JsonObject result = HttpUtil.getJsonObject(client, gson, vidmeUrl);
String obj = "";
if (result == null
|| result.isJsonNull()
|| !result.has("video")
|| result.get("video").isJsonNull()
|| !result.get("video").getAsJsonObject().has("complete_url")
|| result.get("video")
.getAsJsonObject()
.get("complete_url")
.isJsonNull()) {
onError();
if (closeIfNull) {
Intent web = new Intent(c, Website.class);
web.putExtra(Website.EXTRA_URL, url);
web.putExtra(Website.EXTRA_COLOR, Color.BLACK);
c.startActivity(web);
c.finish();
}
} else {
obj = result.getAsJsonObject()
.get("video")
.getAsJsonObject()
.get("complete_url")
.getAsString();
}
final URL finalUrl = new URL(obj);
writeGif(finalUrl, progressBar, c, AsyncLoadGif.this, subreddit);
} catch (Exception e) {
LogUtil.e(e, "Error loading vid.me video url = ["
+ url
+ "] vidmeUrl = ["
+ vidmeUrl
+ "]");
}
break;
case OTHER:
LogUtil.v("https://gfycat.com/cajax/checkUrl/" + Uri.encode(url));
try {
final JsonObject result = HttpUtil.getJsonObject(client, gson,
"https://gfycat.com/cajax/checkUrl/" + Uri.encode(url));
if (result != null && result.has("urlKnown") && result.get("urlKnown")
.getAsBoolean()) {
final URL finalUrl =
new URL(getSmallerGfy(result.get("mp4Url").getAsString()));
writeGif(finalUrl, progressBar, c, AsyncLoadGif.this, subreddit);
} else {
LogUtil.v("https://upload.gfycat.com/transcode?fetchUrl=" + Uri.encode(
url));
showProgressBar(c, progressBar, false);
final JsonObject transcodeResult = HttpUtil.getJsonObject(client, gson,
"https://upload.gfycat.com/transcode?fetchUrl=" + Uri.encode(
url));
// Handle the transcode result
showProgressBar(c, progressBar, false);
if (transcodeResult == null
|| transcodeResult.get("mp4Url") == null
|| transcodeResult.get("mp4Url").isJsonNull()) {
if(transcodeResult != null && transcodeResult.has("gfyname")){
loadGfycat(transcodeResult.get("gfyname").getAsString(), gson);
} else if (transcodeResult != null
&& transcodeResult.has("error")
&& transcodeResult.get("error")
.getAsString()
.contains("not animated")) {
if (c instanceof MediaView
&& c.getIntent() != null
&& c.getIntent()
.hasExtra(MediaView.EXTRA_DISPLAY_URL)) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
((MediaView) c).imageShown = false;
((MediaView) c).displayImage(c.getIntent()
.getStringExtra(
MediaView.EXTRA_DISPLAY_URL));
}
});
} else if (c instanceof Shadowbox) {
//todo maybe load in shadowbox
}
} else {
onError();
if (closeIfNull && !c.isFinishing()) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialogWrapper.Builder b =
new AlertDialogWrapper.Builder(c).setTitle(
R.string.gif_err_title)
.setMessage(
R.string.mediaview_converting_fail)
.setCancelable(false)
.setPositiveButton(
R.string.mediaview_converting_fail_btn,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
Intent i =
new Intent(
c,
Website.class);
i.putExtra(
Website.EXTRA_URL,
url);
c.startActivity(i);
if (closeIfNull) {
c.finish();
}
}
});
if (closeIfNull) {
b.setNegativeButton(R.string.btn_close,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
c.finish();
}
});
}
b.create().show();
}
});
}
}
} else {
final URL finalUrl = new URL(transcodeResult.get("mp4Url")
.getAsString()); //wont exist on server yet, just load the full version
writeGif(finalUrl, progressBar, c, AsyncLoadGif.this, subreddit);
}
}
} catch (Exception e) {
LogUtil.e(e, "Error loading media url = [" + url + "]");
}
break;
}
return null;
}
ContentLengthInputStream stream;
URLConnection ucon;
InputStream is;
public static String readableFileSize(long size) {
if (size <= 0) return "0";
final String[] units = new String[]{"B", "kB", "MB", "GB", "TB"};
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups))
+ " "
+ units[digitGroups];
}
public void writeGif(final URL url, final ProgressBar progressBar, final Activity c,
final AsyncLoadGif afterDone, final String subreddit) throws Exception {
try {
if (!GifCache.fileExists(url)) {
ucon = url.openConnection();
ucon.setReadTimeout(5000);
ucon.setConnectTimeout(10000);
is = ucon.getInputStream();
//todo MediaView.fileLoc = f.getAbsolutePath();
if (size != null && c != null) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
size.setText(readableFileSize(ucon.getContentLength()));
}
});
}
stream = new ContentLengthInputStream(new BufferedInputStream(is, 5 * 1024),
ucon.getContentLength());
GifCache.writeGif(url.toString(), stream, new IoUtils.CopyListener() {
@Override
public boolean onBytesCopied(int current, int total) {
final int percent = Math.round(100.0f * current / total);
if (isCancelled()) {
return false;
}
if (progressBar != null && c != null) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setProgress(percent);
if (percent == 100) {
progressBar.setVisibility(View.GONE);
afterDone.showGif(url, 0, subreddit);
if (size != null) size.setVisibility(View.GONE);
}
}
});
}
if (percent == 100) {
MediaView.didLoadGif = true;
}
return true;
}
});
} else {
if (progressBar != null) {
c.runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.GONE);
afterDone.showGif(url, 0, subreddit);
}
});
}
}
} catch (Exception e) {
onError();
LogUtil.e("Error writing GIF: url = ["
+ url
+ "], progressBar = ["
+ progressBar
+ "], c = ["
+ c
+ "], afterDone = ["
+ afterDone
+ "]");
throw (e);
}
}
}
public static void showErrorDialog(final Activity a) {
new AlertDialogWrapper.Builder(a).setTitle(R.string.err_something_wrong)
.setMessage(R.string.err_couldnt_save_choose_new)
.setPositiveButton(R.string.btn_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new FolderChooserDialogCreate.Builder((MediaView) a).chooseButton(
R.string.btn_select) // changes label of the choose button
.initialPath(Environment.getExternalStorageDirectory()
.getPath()) // changes initial path, defaults to external storage directory
.show();
}
})
.setNegativeButton(R.string.btn_no, null)
.show();
}
public static void showFirstDialog(final Activity a) {
new AlertDialogWrapper.Builder(a).setTitle(R.string.set_gif_save_loc)
.setMessage(R.string.set_gif_save_loc_msg)
.setPositiveButton(R.string.btn_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new FolderChooserDialogCreate.Builder((MediaView) a).chooseButton(
R.string.btn_select) // changes label of the choose button
.initialPath(Environment.getExternalStorageDirectory()
.getPath()) // changes initial path, defaults to external storage directory
.show();
}
})
.setNegativeButton(R.string.btn_no, null)
.show();
}
public static void saveGif(File from, Activity a, String subreddit) {
try {
Toast.makeText(a, "Downloading image...", Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {
}
if (Reddit.appRestart.getString("imagelocation", "").isEmpty()) {
showFirstDialog(a);
} else if (!new File(Reddit.appRestart.getString("imagelocation", "")).exists()) {
showErrorDialog(a);
} else {
if(SettingValues.imageSubfolders && !subreddit.isEmpty()){
File directory = new File( Reddit.appRestart.getString("imagelocation",
"")
+ (SettingValues.imageSubfolders && !subreddit.isEmpty() ?File.separator + subreddit : ""));
directory.mkdirs();
}
File f = new File(Reddit.appRestart.getString("imagelocation", "")
+ (SettingValues.imageSubfolders && !subreddit.isEmpty() ?File.separator + subreddit : "")
+ File.separator
+ UUID.randomUUID().toString()
+ ".mp4");
FileOutputStream out = null;
InputStream in = null;
try {
in = new FileInputStream(from);
out = new FileOutputStream(f);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
} catch (Exception e) {
LogUtil.e("Error saving GIF called with: "
+ "from = ["
+ from
+ "], in = ["
+ in
+ "]");
showErrorDialog(a);
} finally {
try {
if (out != null) {
out.close();
doNotifGif(f.getAbsolutePath(), a);
}
if (in != null) {
in.close();
}
} catch (IOException e) {
LogUtil.e("Error closing GIF called with: "
+ "from = ["
+ from
+ "], out = ["
+ out
+ "]");
showErrorDialog(a);
}
}
}
}
public static void doNotifGif(String s, Activity c) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.parse("file://" + s);
mediaScanIntent.setData(contentUri);
c.sendBroadcast(mediaScanIntent);
final Intent shareIntent = new Intent(Intent.ACTION_VIEW);
shareIntent.setDataAndType(Uri.parse(s), "video/*");
PendingIntent contentIntent =
PendingIntent.getActivity(c, 0, shareIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notif =
new NotificationCompat.Builder(c).setContentTitle(c.getString(R.string.gif_saved))
.setSmallIcon(R.drawable.save_png)
.setContentIntent(contentIntent)
.build();
NotificationManager mNotificationManager =
(NotificationManager) c.getSystemService(Activity.NOTIFICATION_SERVICE);
mNotificationManager.notify((int) System.currentTimeMillis(), notif);
}
/**
* Shows a ProgressBar in the UI. If this method is called from a non-main thread, it will run
* the UI code on the main thread
*
* @param activity The activity context to use to display the ProgressBar
* @param progressBar The ProgressBar to display
* @param isIndeterminate True to show an indeterminate ProgressBar, false otherwise
*/
private static void showProgressBar(final Activity activity, final ProgressBar progressBar,
final boolean isIndeterminate) {
if (activity == null) return;
if (Looper.myLooper() == Looper.getMainLooper()) {
// Current Thread is Main Thread.
if (progressBar != null) progressBar.setIndeterminate(isIndeterminate);
} else {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (progressBar != null) progressBar.setIndeterminate(isIndeterminate);
}
});
}
}
}