Skip to content

Commit 3a822d3

Browse files
vanshikaaroramaskaravivek
authored andcommitted
Added IPTC reader (commons-app#2383)
* graddle changes * readfbmd class * jar files * added icafe * gradle changes * ReadFBMD * recent commit * commit changes * commit changes * fixed code quality issues * optimised imports * changes for reviews * not instantiating class * removed irrelevant code * added documentation * modified test cases * commit changes * modified variable * modified variable * javadoc * test changes * removed .log file * Show warning dialog for FBMD
1 parent 00b95ea commit 3a822d3

File tree

8 files changed

+125
-18
lines changed

8 files changed

+125
-18
lines changed

app/build.gradle

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if(isRunningOnTravisAndIsNotPRBuild) {
1212
}
1313

1414
dependencies {
15+
1516
// Utils
1617
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
1718
implementation 'commons-codec:commons-codec:1.10'
@@ -40,21 +41,20 @@ dependencies {
4041
implementation files('libs/simplemagic-1.9.jar')
4142
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
4243
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
43-
4444
// Logging
4545
implementation 'ch.acra:acra:4.9.2'
4646
implementation 'com.jakewharton.timber:timber:4.4.0'
4747
implementation 'org.slf4j:slf4j-api:1.7.25'
48-
api ("com.github.tony19:logback-android-classic:1.1.1-6") {
48+
api('com.github.tony19:logback-android-classic:1.1.1-6') {
4949
exclude group: 'com.google.android', module: 'android'
5050
}
51-
51+
5252
// Dependency injector
5353
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
5454
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
5555
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
5656
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
57-
57+
5858
// Unit testing
5959
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
6060
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
@@ -70,8 +70,8 @@ dependencies {
7070
androidTestImplementation 'com.android.support.test:runner:1.0.2'
7171
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
7272
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
73-
androidTestImplementation "org.mockito:mockito-core:2.10.0"
74-
73+
androidTestImplementation 'org.mockito:mockito-core:2.10.0'
74+
7575
// Debugging
7676
implementation 'com.tspoon.traceur:traceur:1.0.1'
7777
implementation 'com.facebook.stetho:stetho:1.5.0'
@@ -86,9 +86,11 @@ dependencies {
8686
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
8787
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
8888
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
89+
8990
//swipe_layout
9091
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
9192
implementation 'com.nineoldandroids:library:2.4.0'
93+
implementation files('libs/icafe-1.1-SNAPSHOT.jar')
9294
}
9395

9496
android {

app/libs/icafe-1.1-SNAPSHOT.jar

988 KB
Binary file not shown.

app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ public interface MediaWikiApi {
9797
@NonNull
9898
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
9999

100-
101100
boolean isUserBlockedFromCommons();
102101

103102
void logout();

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package fr.free.nrw.commons.upload;
22

3+
import java.io.IOException;
4+
35
import javax.inject.Inject;
46
import javax.inject.Singleton;
57

@@ -25,16 +27,18 @@ public class ImageProcessingService {
2527
private final BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper;
2628
private final ImageUtilsWrapper imageUtilsWrapper;
2729
private final MediaWikiApi mwApi;
30+
private final ReadFBMD readFBMD;
2831

2932
@Inject
3033
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
3134
BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper,
3235
ImageUtilsWrapper imageUtilsWrapper,
33-
MediaWikiApi mwApi) {
36+
MediaWikiApi mwApi, ReadFBMD readFBMD) {
3437
this.fileUtilsWrapper = fileUtilsWrapper;
3538
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
3639
this.imageUtilsWrapper = imageUtilsWrapper;
3740
this.mwApi = mwApi;
41+
this.readFBMD = readFBMD;
3842
}
3943

4044
/**
@@ -56,18 +60,44 @@ Single<Integer> validateImage(UploadModel.UploadItem uploadItem, boolean checkTi
5660
Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath);
5761
Single<Integer> darkImage = checkDarkImage(filePath);
5862
Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK);
63+
Single<Integer> checkFBMD = checkFBMD(filePath);
5964

60-
return Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
65+
Single<Integer> zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
6166
(duplicate, wrongGeo, dark, title) -> {
6267
Timber.d("Result for duplicate: %d, geo: %d, dark: %d, title: %d", duplicate, wrongGeo, dark, title);
6368
return duplicate | wrongGeo | dark | title;
6469
});
70+
71+
return Single.zip(zipResult, checkFBMD, (zip, fbmd) -> {
72+
Timber.d("zip:" + zip + "fbmd:" + fbmd);
73+
return zip | fbmd;
74+
});
75+
}
76+
77+
/**
78+
* Other than the Image quality we need to check that using this Image doesn't violate's facebook's copyright's.
79+
* 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
80+
* To know whether the Image is downloaded from facebook:
81+
* -We read the metadata of any Image and check for FBMD
82+
* -Facebook downloaded image's contains metadata of the type IPTC
83+
* - From this IPTC metadata we extract a byte array that contains FBMD as it's initials. If the image was downloaded from facebook
84+
* Thus we successfully protect common's from Facebook's copyright violation
85+
*/
86+
87+
public Single<Integer> checkFBMD(String filePath) {
88+
try {
89+
return readFBMD.processMetadata(filePath);
90+
} catch (IOException e) {
91+
return Single.just(ImageUtils.FILE_FBMD);
92+
}
6593
}
6694

95+
6796
/**
6897
* Checks item title
6998
* - empty title
7099
* - existing title
100+
*
71101
* @param uploadItem
72102
* @return
73103
*/
@@ -87,6 +117,7 @@ private Single<Integer> validateItemTitle(UploadModel.UploadItem uploadItem) {
87117

88118
/**
89119
* Checks for duplicate image
120+
*
90121
* @param filePath file to be checked
91122
* @return IMAGE_DUPLICATE or IMAGE_OK
92123
*/
@@ -104,6 +135,7 @@ private Single<Integer> checkDuplicateImage(String filePath) {
104135

105136
/**
106137
* Checks for dark image
138+
*
107139
* @param filePath file to be checked
108140
* @return IMAGE_DARK or IMAGE_OK
109141
*/
@@ -118,6 +150,7 @@ private Single<Integer> checkDarkImage(String filePath) {
118150
/**
119151
* Checks for image geolocation
120152
* returns IMAGE_OK if the place is null or if the file doesn't contain a geolocation
153+
*
121154
* @param filePath file to be checked
122155
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
123156
*/
@@ -136,3 +169,4 @@ private Single<Integer> checkImageGeoLocation(Place place, String filePath) {
136169
});
137170
}
138171
}
172+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package fr.free.nrw.commons.upload;
2+
3+
import com.icafe4j.image.meta.Metadata;
4+
import com.icafe4j.image.meta.MetadataType;
5+
import com.icafe4j.image.meta.iptc.IPTC;
6+
7+
import java.io.IOException;
8+
import java.util.Map;
9+
10+
import javax.inject.Inject;
11+
import javax.inject.Singleton;
12+
13+
import fr.free.nrw.commons.utils.ImageUtils;
14+
import io.reactivex.Single;
15+
import timber.log.Timber;
16+
17+
@Singleton
18+
public class ReadFBMD {
19+
20+
@Inject
21+
public ReadFBMD(){
22+
23+
}
24+
public Single<Integer> processMetadata(String path) throws IOException {
25+
Map<MetadataType, Metadata> metadataMap = Metadata.readMetadata(path);
26+
Metadata metadata = metadataMap.get(MetadataType.IPTC);
27+
byte[] dataInBytes = new byte[0];
28+
try {
29+
dataInBytes = ((IPTC) metadata).getDataSets().get("SpecialInstructions").get(0).getData();
30+
} catch (NullPointerException e) {
31+
return Single.just(ImageUtils.IMAGE_OK);
32+
}
33+
/**
34+
* The byte array so obtained is used is tested to contain FBMD data
35+
* Note: Any image downloaded from Facebook contains the ASCII code of the letters 'FBMD' in this bytecode extracted from it's IPTC metadata
36+
* */
37+
if (dataInBytes[0] == 70 && dataInBytes[1] == 66 && dataInBytes[2] == 77 && dataInBytes[3] == 68) {
38+
Timber.d("Contains FBMD");
39+
return Single.just(ImageUtils.FILE_FBMD);
40+
}
41+
return Single.just(ImageUtils.IMAGE_OK);
42+
}
43+
}
44+

app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,28 @@
3434

3535
public class ImageUtils {
3636

37-
public static final int IMAGE_DARK = 1;
38-
static final int IMAGE_BLURRY = 1 << 1;
37+
/**
38+
* Set 0th bit as 1 for dark image ie. 0001
39+
*/
40+
public static final int IMAGE_DARK = 1 << 0; // 1
41+
/**
42+
* Set 1st bit as 1 for blurry image ie. 0010
43+
*/
44+
static final int IMAGE_BLURRY = 1 << 1; // 2
45+
/**
46+
* Set 2nd bit as 1 for duplicate image ie. 0100
47+
*/
3948
public static final int IMAGE_DUPLICATE = 1 << 2; //4
40-
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3;
49+
/**
50+
* Set 3rd bit as 1 for image with different geo location ie. 1000
51+
*/
52+
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3; //8
53+
/**
54+
* The parameter FILE_FBMD is returned from the class ReadFBMD if the uploaded image contains FBMD data else returns IMAGE_OK
55+
* ie. 10000
56+
*/
57+
public static final int FILE_FBMD = 1 << 4;
58+
4159
public static final int IMAGE_OK = 0;
4260
public static final int IMAGE_KEEP = -1;
4361
public static final int IMAGE_WAIT = -2;
@@ -214,11 +232,12 @@ private static void setWallpaper(Context context, Bitmap bitmap) {
214232
}
215233
}
216234

235+
/**
236+
* Result variable is a result of an or operation of all possible problems. Ie. if result
237+
* is 0001 means IMAGE_DARK
238+
* if result is 1100 IMAGE_DUPLICATE and IMAGE_GEOLOCATION_DIFFERENT
239+
*/
217240
public static String getErrorMessageForResult(Context context, @Result int result) {
218-
/**
219-
* Result variable is a result of an or operation of all possible problems. Ie. if result
220-
* is 0001 means IMAGE_DARK, if result is 1100 IMAGE_DUPLICATE and IMAGE_GEOLOCATION_DIFFERENT
221-
*/
222241
StringBuilder errorMessage = new StringBuilder();
223242
if (result <= 0 ) {
224243
Timber.d("No issues to warn user is found");
@@ -243,6 +262,10 @@ public static String getErrorMessageForResult(Context context, @Result int resul
243262
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_different_geolocation));
244263
}
245264

265+
if ((FILE_FBMD & result) != 0) {
266+
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_fbmd));
267+
}
268+
246269
errorMessage.append("\n\n").append(context.getResources().getString(R.string.upload_problem_do_you_continue));
247270
}
248271

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,12 @@
238238
<string name="upload_image_too_dark">This picture is too dark, are you sure you want to upload it? Wikimedia Commons is only for pictures with encyclopedic value.</string>
239239
<string name="upload_image_blurry">This picture is blurry, are you sure you want to upload it? Wikimedia Commons is only for pictures with encyclopedic value.</string>
240240

241-
<string name="upload_problem_exist">Potential problems with this image:</string>
241+
<string name="upload_problem_exist">Potential problems with this image :\n\n</string>
242242
<string name="upload_problem_image_dark">Image is too dark.</string>
243243
<string name="upload_problem_image_blurry">Image is blurry.</string>
244244
<string name="upload_problem_image_duplicate">Image is already on Commons.</string>
245245
<string name="upload_problem_different_geolocation">This picture was taken at a different location.</string>
246+
<string name="upload_problem_fbmd">Please only upload pictures that you have taken by yourself. Don\'t upload pictures that you have found on other people\'s Facebook accounts.</string>
246247
<string name="upload_problem_do_you_continue">Do you still want to upload this picture?</string>
247248

248249
<string name="give_permission">Give permission</string>

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import org.mockito.Mockito.*
1919
import org.mockito.MockitoAnnotations
2020
import java.io.FileInputStream
2121

22-
class ImageProcessingServiceTest {
22+
class u {
2323
@Mock
2424
internal var fileUtilsWrapper: FileUtilsWrapper? = null
2525
@Mock
@@ -28,6 +28,8 @@ class ImageProcessingServiceTest {
2828
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
2929
@Mock
3030
internal var mwApi: MediaWikiApi? = null
31+
@Mock
32+
internal var readFBMD: ReadFBMD?=null
3133

3234
@InjectMocks
3335
var imageProcessingService: ImageProcessingService? = null
@@ -80,6 +82,8 @@ class ImageProcessingServiceTest {
8082
.thenReturn(false)
8183
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
8284
.thenReturn(false)
85+
`when`(readFBMD?.processMetadata(ArgumentMatchers.anyString()))
86+
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
8387
}
8488

8589
@Test

0 commit comments

Comments
 (0)