Skip to content

Commit 04d9db3

Browse files
dbrantmaskaravivek
authored andcommitted
Remove dependency on Exif parsing library. (#2947)
* Remove dependency on Exif parsing library. * Fix test.
1 parent dfe59c4 commit 04d9db3

File tree

6 files changed

+58
-116
lines changed

6 files changed

+58
-116
lines changed

app/build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ dependencies {
8888
implementation "androidx.cardview:cardview:1.0.0"
8989
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
9090
implementation "androidx.exifinterface:exifinterface:1.0.0"
91-
//metadata extractor
92-
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
91+
92+
//swipe_layout
93+
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
9394
}
9495

9596
android {

app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
package fr.free.nrw.commons.filepicker;
22

3+
import android.annotation.SuppressLint;
34
import android.content.Context;
45
import android.database.Cursor;
56
import android.net.Uri;
67
import android.os.Parcel;
78
import android.os.Parcelable;
89

10+
import androidx.exifinterface.media.ExifInterface;
911
import androidx.annotation.Nullable;
1012

11-
import com.drew.imaging.ImageMetadataReader;
12-
import com.drew.imaging.ImageProcessingException;
13-
import com.drew.metadata.Metadata;
14-
import com.drew.metadata.exif.ExifSubIFDDirectory;
15-
1613
import java.io.File;
1714
import java.io.IOException;
1815
import java.util.Date;
@@ -125,16 +122,13 @@ private DateTimeWithSource getFileCreatedDateFromCP(Context context) {
125122
* @return
126123
*/
127124
private DateTimeWithSource getDateTimeFromExif() {
128-
Metadata metadata;
129125
try {
130-
metadata = ImageMetadataReader.readMetadata(file);
131-
ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
132-
if (directory!=null && directory.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) {
133-
Date date = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
126+
ExifInterface exif = new ExifInterface(file.getAbsolutePath());
127+
@SuppressLint("RestrictedApi") long dateTime = exif.getDateTime();
128+
if (dateTime != -1) {
129+
Date date = new Date(dateTime);
134130
return new DateTimeWithSource(date, DateTimeWithSource.EXIF_SOURCE);
135131
}
136-
} catch (ImageProcessingException e) {
137-
e.printStackTrace();
138132
} catch (IOException e) {
139133
e.printStackTrace();
140134
}
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,36 @@
11
package fr.free.nrw.commons.upload;
22

3-
import com.drew.imaging.ImageMetadataReader;
4-
import com.drew.imaging.ImageProcessingException;
5-
import com.drew.metadata.Directory;
6-
import com.drew.metadata.Metadata;
7-
8-
import java.io.File;
9-
import java.io.IOException;
3+
import androidx.exifinterface.media.ExifInterface;
104

115
import javax.inject.Inject;
126
import javax.inject.Singleton;
137

148
import fr.free.nrw.commons.utils.ImageUtils;
159
import io.reactivex.Single;
16-
import timber.log.Timber;
1710

1811
/**
19-
* We try to avoid copyright violations in commons app.
20-
* For doing that we read EXIF data using the library metadata-reader
21-
* If an image doesn't have any EXIF Directoris in it's metadata then the image is an
22-
* internet download image(and not the one taken using phone's camera) */
23-
12+
* We try to minimize uploads from the Commons app that might be copyright violations.
13+
* If an image does not have any Exif metadata, then it was likely downloaded from the internet,
14+
* and is probably not an original work by the user. We detect these kinds of images by looking
15+
* for the presence of some basic Exif metadata.
16+
*/
2417
@Singleton
2518
public class EXIFReader {
2619
@Inject
2720
public EXIFReader() {
28-
//Empty
2921
}
30-
/**
31-
* The method takes in path of the image and reads metadata using the library metadata-extractor
32-
* And the checks for the presence of EXIF Directories in metadata object
33-
* */
3422

3523
public Single<Integer> processMetadata(String path) {
36-
Metadata readMetadata = null;
3724
try {
38-
readMetadata = ImageMetadataReader.readMetadata(new File(path));
39-
} catch (ImageProcessingException e) {
40-
Timber.d(e.toString());
41-
} catch (IOException e) {
42-
Timber.d(e.toString());
43-
}
44-
if (readMetadata != null) {
45-
for (Directory directory : readMetadata.getDirectories()) {
46-
// In case of internet downloaded image these three fields are not present
47-
if (directory.getName().equals("Exif IFD0") //Contains information about the device capturing the photo
48-
|| directory.getName().equals("Exif SubIFD") //contains information like date, time and pixels of the image
49-
|| directory.getName().equals("Exif Thumbnail")) //contains information about image thumbnail like compression and reolution
50-
{
51-
Timber.d(directory.getName() + " Contains metadata");
52-
return Single.just(ImageUtils.IMAGE_OK);
53-
}
25+
ExifInterface exif = new ExifInterface(path);
26+
if (exif.getAttribute(ExifInterface.TAG_MAKE) != null
27+
|| exif.getAttribute(ExifInterface.TAG_DATETIME) != null) {
28+
return Single.just(ImageUtils.IMAGE_OK);
5429
}
30+
} catch (Exception e) {
31+
return Single.just(ImageUtils.FILE_NO_EXIF);
5532
}
5633
return Single.just(ImageUtils.FILE_NO_EXIF);
5734
}
58-
5935
}
6036

app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java

+14-36
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
package fr.free.nrw.commons.upload;
22

3-
import android.content.Context;
4-
import android.net.Uri;
5-
63
import org.apache.commons.lang3.StringUtils;
74

8-
import java.io.IOException;
9-
105
import javax.inject.Inject;
116
import javax.inject.Singleton;
127

@@ -23,30 +18,24 @@
2318

2419
/**
2520
* Methods for pre-processing images to be uploaded
26-
*//*if (dataInBytes[0] == 70 && dataInBytes[1] == 66 && dataInBytes[2] == 77 && dataInBytes[3] == 68) {
27-
Timber.d("Contains FBMD");
28-
return Single.just(ImageUtils.FILE_FBMD);
29-
}*/
21+
*/
3022
@Singleton
3123
public class ImageProcessingService {
3224
private final FileUtilsWrapper fileUtilsWrapper;
3325
private final ImageUtilsWrapper imageUtilsWrapper;
3426
private final MediaWikiApi mwApi;
3527
private final ReadFBMD readFBMD;
3628
private final EXIFReader EXIFReader;
37-
private final Context context;
3829

3930
@Inject
4031
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
4132
ImageUtilsWrapper imageUtilsWrapper,
42-
MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader,
43-
Context context) {
33+
MediaWikiApi mwApi, ReadFBMD readFBMD, EXIFReader EXIFReader) {
4434
this.fileUtilsWrapper = fileUtilsWrapper;
4535
this.imageUtilsWrapper = imageUtilsWrapper;
4636
this.mwApi = mwApi;
4737
this.readFBMD = readFBMD;
4838
this.EXIFReader = EXIFReader;
49-
this.context = context;
5039
}
5140

5241
/**
@@ -64,12 +53,11 @@ Single<Integer> validateImage(UploadModel.UploadItem uploadItem, boolean checkTi
6453
}
6554
Timber.d("Checking the validity of image");
6655
String filePath = uploadItem.getMediaUri().getPath();
67-
Uri contentUri=uploadItem.getContentUri();
6856
Single<Integer> duplicateImage = checkDuplicateImage(filePath);
6957
Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath);
7058
Single<Integer> darkImage = checkDarkImage(filePath);
7159
Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK);
72-
Single<Integer> checkFBMD = checkFBMD(context,contentUri);
60+
Single<Integer> checkFBMD = checkFBMD(filePath);
7361
Single<Integer> checkEXIF = checkEXIF(filePath);
7462

7563
Single<Integer> zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
@@ -84,31 +72,21 @@ Single<Integer> validateImage(UploadModel.UploadItem uploadItem, boolean checkTi
8472
}
8573

8674
/**
87-
* Other than the Image quality we need to check that using this Image doesn't violate's facebook's copyright's.
88-
* Whenever a user tries to upload an image that was downloaded from Facebook then we warn the user with a message to stop the upload
89-
* To know whether the Image is downloaded from facebook:
90-
* -We read the metadata of any Image and check for FBMD
91-
* -Facebook downloaded image's contains metadata of the type IPTC
92-
* - From this IPTC metadata we extract a byte array that contains FBMD as it's initials. If the image was downloaded from facebook
93-
* Thus we successfully protect common's from Facebook's copyright violation
75+
* We want to discourage users from uploading images to Commons that were taken from Facebook.
76+
* This attempts to detect whether an image was downloaded from Facebook by heuristically
77+
* searching for metadata that is specific to images that come from Facebook.
9478
*/
95-
96-
public Single<Integer> checkFBMD(Context context,Uri contentUri) {
97-
try {
98-
return readFBMD.processMetadata(context,contentUri);
99-
} catch (IOException e) {
100-
return Single.just(ImageUtils.FILE_FBMD);
101-
}
79+
private Single<Integer> checkFBMD(String filepath) {
80+
return readFBMD.processMetadata(filepath);
10281
}
10382

10483
/**
105-
* To avoid copyright we check for EXIF data in any image.
106-
* Images that are downloaded from internet generally don't have any EXIF data in them
107-
* while images taken via camera or screenshots in phone have EXIF data with them.
108-
* So we check if the image has no EXIF data then we display a warning to the user
109-
* * */
110-
111-
public Single<Integer> checkEXIF(String filepath){
84+
* We try to minimize uploads from the Commons app that might be copyright violations.
85+
* If an image does not have any Exif metadata, then it was likely downloaded from the internet,
86+
* and is probably not an original work by the user. We detect these kinds of images by looking
87+
* for the presence of some basic Exif metadata.
88+
*/
89+
private Single<Integer> checkEXIF(String filepath) {
11290
return EXIFReader.processMetadata(filepath);
11391
}
11492

app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java

+22-29
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
package fr.free.nrw.commons.upload;
22

3-
import android.content.Context;
4-
import android.net.Uri;
5-
6-
import com.drew.imaging.ImageMetadataReader;
7-
import com.drew.imaging.ImageProcessingException;
8-
import com.drew.metadata.Metadata;
9-
import com.drew.metadata.Tag;
10-
import com.drew.metadata.iptc.IptcDirectory;
11-
3+
import java.io.FileInputStream;
124
import java.io.IOException;
135

146
import javax.inject.Inject;
@@ -17,37 +9,38 @@
179
import fr.free.nrw.commons.utils.ImageUtils;
1810
import io.reactivex.Single;
1911

12+
/**
13+
* We want to discourage users from uploading images to Commons that were taken from Facebook.
14+
* This attempts to detect whether an image was downloaded from Facebook by heuristically
15+
* searching for metadata that is specific to images that come from Facebook.
16+
*/
2017
@Singleton
2118
public class ReadFBMD {
2219

2320
@Inject
2421
public ReadFBMD() {
25-
2622
}
2723

28-
public Single<Integer> processMetadata(Context context, Uri contentUri) throws IOException {
29-
Metadata readMetadata = null;
24+
public Single<Integer> processMetadata(String path) {
3025
try {
31-
readMetadata = ImageMetadataReader.readMetadata(context.getContentResolver().openInputStream(contentUri));
32-
} catch (ImageProcessingException e) {
33-
e.printStackTrace();
34-
} catch (IOException e) {
35-
e.printStackTrace();
36-
}
26+
int psBlockOffset;
27+
int fbmdOffset;
28+
29+
try (FileInputStream fs = new FileInputStream(path)) {
30+
byte[] bytes = new byte[4096];
31+
fs.read(bytes);
32+
fs.close();
33+
String fileStr = new String(bytes);
34+
psBlockOffset = fileStr.indexOf("8BIM");
35+
fbmdOffset = fileStr.indexOf("FBMD");
36+
}
3737

38-
IptcDirectory iptcDirectory = readMetadata != null ? readMetadata.getFirstDirectoryOfType(IptcDirectory.class) : null;
39-
if (iptcDirectory == null) {
40-
return Single.just(ImageUtils.IMAGE_OK);
41-
}
42-
/**
43-
* We parse through all the tags in the IPTC directory if the tagname equals "Special Instructions".
44-
* And the description string starts with FBMD.
45-
* Then the source of image is facebook
46-
* */
47-
for (Tag tag : iptcDirectory.getTags()) {
48-
if (tag.getTagName().equals("Special Instructions") && tag.getDescription().substring(0, 4).equals("FBMD")) {
38+
if (psBlockOffset > 0 && fbmdOffset > 0
39+
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
4940
return Single.just(ImageUtils.FILE_FBMD);
5041
}
42+
} catch (IOException e) {
43+
e.printStackTrace();
5144
}
5245
return Single.just(ImageUtils.IMAGE_OK);
5346
}

app/src/test/kotlin/fr/free/nrw/commons/upload/ImageProcessingServiceTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class u {
7878
.thenReturn(false)
7979
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
8080
.thenReturn(false)
81-
`when`(readFBMD?.processMetadata(ArgumentMatchers.any(),ArgumentMatchers.any()))
81+
`when`(readFBMD?.processMetadata(ArgumentMatchers.any()))
8282
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
8383
`when`(readEXIF?.processMetadata(ArgumentMatchers.anyString()))
8484
.thenReturn(Single.just(ImageUtils.IMAGE_OK))

0 commit comments

Comments
 (0)