Browse Source

Remove IPTC from downloaded JPG files

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
3ac2ee36aa
  1. 5
      .idea/jarRepositories.xml
  2. 8
      app/build.gradle
  3. 103
      app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
  4. 77
      app/src/main/java/awais/instagrabber/workers/DownloadWorker.java
  5. 1
      build.gradle

5
.idea/jarRepositories.xml

@ -31,5 +31,10 @@
<option name="name" value="Google" /> <option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="http://maven.geotoolkit.org/" />
</remote-repository>
</component> </component>
</project> </project>

8
app/build.gradle

@ -51,7 +51,9 @@ dependencies {
def nav_version = '2.3.1' def nav_version = '2.3.1'
implementation 'com.google.android.material:material:1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'com.google.android.exoplayer:exoplayer:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version" implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
@ -68,7 +70,6 @@ dependencies {
implementation 'com.google.guava:guava:27.0.1-android' implementation 'com.google.guava:guava:27.0.1-android'
// implementation 'com.github.hendrawd:StorageUtil:1.1.0' // implementation 'com.github.hendrawd:StorageUtil:1.1.0'
// implementation 'com.github.armcha:AutoLinkTextViewV2:2.1.1'
implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT' implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT'
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.13.1'
@ -79,6 +80,9 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.github.dragon66:icafe:master-SNAPSHOT'
implementation 'javax.media:jai_imageio:1.1.1'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'

103
app/src/main/java/awais/instagrabber/utils/DownloadUtils.java

@ -5,7 +5,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Environment; import android.os.Environment;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -37,13 +36,9 @@ import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.models.BasePostModel; import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild; import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.workers.DownloadWorker; import awais.instagrabber.workers.DownloadWorker;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
@ -63,96 +58,22 @@ public final class DownloadUtils {
return lastNotificationId; return lastNotificationId;
} }
public static void batchDownload(@NonNull final Context context,
@Nullable String username,
final DownloadMethod method,
final List<? extends BasePostModel> itemsToDownload) {
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);
if (itemsToDownload == null || itemsToDownload.size() < 1) return;
if (username != null && username.charAt(0) == '@') username = username.substring(1);
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED)
batchDownloadImpl(context, username, method, itemsToDownload);
else if (context instanceof Activity)
ActivityCompat.requestPermissions((Activity) context, PERMS, 8020);
}
@NonNull
private static File getDownloadDir() {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
private static void batchDownloadImpl(@NonNull final Context context,
@Nullable final String username,
final DownloadMethod method,
final List<? extends BasePostModel> itemsToDownload) {
final File dir = getDownloadDir(context, username);
if (dir == null) return;
boolean checkEachPost = false;
switch (method) {
case DOWNLOAD_SAVED:
case DOWNLOAD_MAIN:
case DOWNLOAD_DISCOVER:
checkEachPost = true;
break;
}
final int itemsToDownloadSize = itemsToDownload.size();
for (int i = 0; i < itemsToDownloadSize; i++) {
final BasePostModel selectedItem = itemsToDownload.get(i);
if (!checkEachPost) {
final boolean isSlider = itemsToDownloadSize > 1;
final File saveFile = getDownloadSaveFile(dir,
selectedItem.getShortCode(),
isSlider ? "_slide_" + (i + 1) : "",
selectedItem.getDisplayUrl()
);
new DownloadAsync(context,
selectedItem.getDisplayUrl(),
saveFile,
file -> selectedItem.setDownloaded(true))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
final File finalDir = dir;
new PostFetcher(selectedItem.getShortCode(), result -> {
if (result == null) return;
final boolean isSlider = result.getItemType() == MediaItemType.MEDIA_TYPE_SLIDER;
if (isSlider) {
for (int j = 0; j < result.getSliderItems().size(); j++) {
final PostChild model = result.getSliderItems().get(j);
final File saveFile = getDownloadSaveFile(
finalDir,
model.getShortCode(),
"_slide_" + (j + 1),
model.getDisplayUrl()
);
new DownloadAsync(context,
model.getDisplayUrl(),
saveFile,
file -> {}/*model.setDownloaded(true)*/)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
final File saveFile = getDownloadSaveFile(
finalDir,
result.getPostId(),
result.getDisplayUrl()
);
new DownloadAsync(context,
result.getDisplayUrl(),
saveFile,
file -> result.setDownloaded(true))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) {
dir = new File(customPath);
} }
} }
return dir;
} }
@Nullable @Nullable
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) { private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
}
File dir = getDownloadDir();
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) { if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
final String finaleUsername = username.startsWith("@") ? username : "@" + username; final String finaleUsername = username.startsWith("@") ? username : "@" + username;
@ -216,6 +137,12 @@ public final class DownloadUtils {
return new File(finalDir, fileName); return new File(finalDir, fileName);
} }
@NonNull
public static File getTempFile() {
final File dir = getDownloadDir();
return new File(dir, UUID.randomUUID().toString());
}
/** /**
* Copied from {@link MimeTypeMap#getFileExtensionFromUrl(String)}) * Copied from {@link MimeTypeMap#getFileExtensionFromUrl(String)})
* <p> * <p>

77
app/src/main/java/awais/instagrabber/workers/DownloadWorker.java

@ -26,9 +26,12 @@ import androidx.work.WorkerParameters;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.icafe4j.image.meta.Metadata;
import com.icafe4j.image.meta.MetadataType;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
@ -45,6 +48,7 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.services.DeleteImageIntentService; import awais.instagrabber.services.DeleteImageIntentService;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
@ -121,7 +125,9 @@ public class DownloadWorker extends Worker {
final int total, final int total,
final String url, final String url,
final String filePath) { final String filePath) {
final File outFile = new File(filePath);
final boolean isJpg = filePath.endsWith("jpg");
// using temp file approach to remove IPTC so that download progress can be reported
final File outFile = isJpg ? DownloadUtils.getTempFile() : new File(filePath);
try { try {
final URLConnection urlConnection = new URL(url).openConnection(); final URLConnection urlConnection = new URL(url).openConnection();
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() :
@ -131,44 +137,32 @@ public class DownloadWorker extends Worker {
final FileOutputStream fos = new FileOutputStream(outFile)) { final FileOutputStream fos = new FileOutputStream(outFile)) {
final byte[] buffer = new byte[0x2000]; final byte[] buffer = new byte[0x2000];
int count; int count;
boolean deletedIPTC = false;
while ((count = bis.read(buffer, 0, 0x2000)) != -1) { while ((count = bis.read(buffer, 0, 0x2000)) != -1) {
totalRead = totalRead + count; totalRead = totalRead + count;
// if (!deletedIPTC) {
// int iptcStart = -1;
// int fbmdStart = -1;
// int fbmdBytesLen = -1;
// for (int i = 0; i < buffer.length; ++i) {
// if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED)
// iptcStart = i;
// else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B'
// && buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') {
// fbmdStart = i;
// fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 |
// (buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) |
// (buffer[i - 6] & 0xFF);
// break;
// }
// }
// if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) {
// final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4;
// fos.write(buffer, 0, iptcStart);
// fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart);
// // setProgressAsync(new Data.Builder().putString(URL, url)
// // .putFloat(PROGRESS, totalRead * 100f / fileSize)
// // .build());
// updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize);
// deletedIPTC = true;
// continue;
// }
// }
fos.write(buffer, 0, count); fos.write(buffer, 0, count);
// setProgressAsync(new Data.Builder().putString(URL, url)
// .putFloat(PROGRESS, totalRead * 100f / fileSize)
// .build());
setProgressAsync(new Data.Builder().putString(URL, url)
.putFloat(PROGRESS, totalRead * 100f / fileSize)
.build());
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize); updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize);
} }
fos.flush(); fos.flush();
} catch (final Exception e) {
Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.getAbsolutePath(), e);
}
if (isJpg) {
final File finalFile = new File(filePath);
try (FileInputStream bis = new FileInputStream(outFile);
FileOutputStream fos = new FileOutputStream(finalFile)) {
Metadata.removeMetadata(bis, fos, MetadataType.IPTC);
} catch (Exception e) {
Log.e(TAG, "Error while removing iptc: url: " + url
+ ", tempFile: " + outFile.getAbsolutePath()
+ ", finalFile: " + finalFile.getAbsolutePath(), e);
}
final boolean deleted = outFile.delete();
if (!deleted) {
Log.w(TAG, "download: tempFile not deleted!");
}
} }
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Error while downloading: " + url, e); Log.e(TAG, "Error while downloading: " + url, e);
@ -176,23 +170,6 @@ public class DownloadWorker extends Worker {
updateDownloadProgress(notificationId, position, total, 100); updateDownloadProgress(notificationId, position, total, 100);
} }
// private void showCompleteNotification(final String url) {
// final Context context = getApplicationContext();
// final Notification notification = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID)
// .setCategory(NotificationCompat.CATEGORY_STATUS)
// .setSmallIcon(R.drawable.ic_download)
// .setAutoCancel(false)
// .setOnlyAlertOnce(true)
// .setContentTitle(context.getString(R.string.downloader_complete))
// .setGroup(DOWNLOAD_GROUP)
// .build();
// final int id = Math.abs(url.hashCode());
// Log.d(TAG, "showCompleteNotification: cancelling: " + id);
// notificationManager.cancel(id);
// // WorkManager.getInstance(getApplicationContext()).
// notificationManager.notify(id + 1, notification);
// }
private void updateDownloadProgress(final int notificationId, private void updateDownloadProgress(final int notificationId,
final int position, final int position,
final int total, final int total,

1
build.gradle

@ -16,6 +16,7 @@ allprojects {
google() google()
jcenter() jcenter()
mavenCentral() mavenCentral()
maven { url 'http://maven.geotoolkit.org/' }
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
} }

Loading…
Cancel
Save