Added IPTC reader (#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
This commit is contained in:
Vanshika Arora 2019-02-24 23:16:22 +05:30 committed by Vivek Maskara
parent 00b95ea9cf
commit 3a822d3c30
8 changed files with 125 additions and 18 deletions

View file

@ -12,6 +12,7 @@ if(isRunningOnTravisAndIsNotPRBuild) {
}
dependencies {
// Utils
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
implementation 'commons-codec:commons-codec:1.10'
@ -40,21 +41,20 @@ dependencies {
implementation files('libs/simplemagic-1.9.jar')
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
// Logging
implementation 'ch.acra:acra:4.9.2'
implementation 'com.jakewharton.timber:timber:4.4.0'
implementation 'org.slf4j:slf4j-api:1.7.25'
api ("com.github.tony19:logback-android-classic:1.1.1-6") {
api('com.github.tony19:logback-android-classic:1.1.1-6') {
exclude group: 'com.google.android', module: 'android'
}
// Dependency injector
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
// Unit testing
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
@ -70,8 +70,8 @@ dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation "org.mockito:mockito-core:2.10.0"
androidTestImplementation 'org.mockito:mockito-core:2.10.0'
// Debugging
implementation 'com.tspoon.traceur:traceur:1.0.1'
implementation 'com.facebook.stetho:stetho:1.5.0'
@ -86,9 +86,11 @@ dependencies {
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
//swipe_layout
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
implementation 'com.nineoldandroids:library:2.4.0'
implementation files('libs/icafe-1.1-SNAPSHOT.jar')
}
android {

Binary file not shown.

View file

@ -97,7 +97,6 @@ public interface MediaWikiApi {
@NonNull
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
boolean isUserBlockedFromCommons();
void logout();

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.upload;
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -25,16 +27,18 @@ public class ImageProcessingService {
private final BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper;
private final ImageUtilsWrapper imageUtilsWrapper;
private final MediaWikiApi mwApi;
private final ReadFBMD readFBMD;
@Inject
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
BitmapRegionDecoderWrapper bitmapRegionDecoderWrapper,
ImageUtilsWrapper imageUtilsWrapper,
MediaWikiApi mwApi) {
MediaWikiApi mwApi, ReadFBMD readFBMD) {
this.fileUtilsWrapper = fileUtilsWrapper;
this.bitmapRegionDecoderWrapper = bitmapRegionDecoderWrapper;
this.imageUtilsWrapper = imageUtilsWrapper;
this.mwApi = mwApi;
this.readFBMD = readFBMD;
}
/**
@ -56,18 +60,44 @@ public class ImageProcessingService {
Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath);
Single<Integer> darkImage = checkDarkImage(filePath);
Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK);
Single<Integer> checkFBMD = checkFBMD(filePath);
return Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
Single<Integer> zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
(duplicate, wrongGeo, dark, title) -> {
Timber.d("Result for duplicate: %d, geo: %d, dark: %d, title: %d", duplicate, wrongGeo, dark, title);
return duplicate | wrongGeo | dark | title;
});
return Single.zip(zipResult, checkFBMD, (zip, fbmd) -> {
Timber.d("zip:" + zip + "fbmd:" + fbmd);
return zip | fbmd;
});
}
/**
* Other than the Image quality we need to check that using this Image doesn't violate's facebook's copyright's.
* 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
* To know whether the Image is downloaded from facebook:
* -We read the metadata of any Image and check for FBMD
* -Facebook downloaded image's contains metadata of the type IPTC
* - From this IPTC metadata we extract a byte array that contains FBMD as it's initials. If the image was downloaded from facebook
* Thus we successfully protect common's from Facebook's copyright violation
*/
public Single<Integer> checkFBMD(String filePath) {
try {
return readFBMD.processMetadata(filePath);
} catch (IOException e) {
return Single.just(ImageUtils.FILE_FBMD);
}
}
/**
* Checks item title
* - empty title
* - existing title
*
* @param uploadItem
* @return
*/
@ -87,6 +117,7 @@ public class ImageProcessingService {
/**
* Checks for duplicate image
*
* @param filePath file to be checked
* @return IMAGE_DUPLICATE or IMAGE_OK
*/
@ -104,6 +135,7 @@ public class ImageProcessingService {
/**
* Checks for dark image
*
* @param filePath file to be checked
* @return IMAGE_DARK or IMAGE_OK
*/
@ -118,6 +150,7 @@ public class ImageProcessingService {
/**
* Checks for image geolocation
* returns IMAGE_OK if the place is null or if the file doesn't contain a geolocation
*
* @param filePath file to be checked
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
*/
@ -136,3 +169,4 @@ public class ImageProcessingService {
});
}
}

View file

@ -0,0 +1,44 @@
package fr.free.nrw.commons.upload;
import com.icafe4j.image.meta.Metadata;
import com.icafe4j.image.meta.MetadataType;
import com.icafe4j.image.meta.iptc.IPTC;
import java.io.IOException;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.utils.ImageUtils;
import io.reactivex.Single;
import timber.log.Timber;
@Singleton
public class ReadFBMD {
@Inject
public ReadFBMD(){
}
public Single<Integer> processMetadata(String path) throws IOException {
Map<MetadataType, Metadata> metadataMap = Metadata.readMetadata(path);
Metadata metadata = metadataMap.get(MetadataType.IPTC);
byte[] dataInBytes = new byte[0];
try {
dataInBytes = ((IPTC) metadata).getDataSets().get("SpecialInstructions").get(0).getData();
} catch (NullPointerException e) {
return Single.just(ImageUtils.IMAGE_OK);
}
/**
* The byte array so obtained is used is tested to contain FBMD data
* Note: Any image downloaded from Facebook contains the ASCII code of the letters 'FBMD' in this bytecode extracted from it's IPTC metadata
* */
if (dataInBytes[0] == 70 && dataInBytes[1] == 66 && dataInBytes[2] == 77 && dataInBytes[3] == 68) {
Timber.d("Contains FBMD");
return Single.just(ImageUtils.FILE_FBMD);
}
return Single.just(ImageUtils.IMAGE_OK);
}
}

View file

@ -34,10 +34,28 @@ import timber.log.Timber;
public class ImageUtils {
public static final int IMAGE_DARK = 1;
static final int IMAGE_BLURRY = 1 << 1;
/**
* Set 0th bit as 1 for dark image ie. 0001
*/
public static final int IMAGE_DARK = 1 << 0; // 1
/**
* Set 1st bit as 1 for blurry image ie. 0010
*/
static final int IMAGE_BLURRY = 1 << 1; // 2
/**
* Set 2nd bit as 1 for duplicate image ie. 0100
*/
public static final int IMAGE_DUPLICATE = 1 << 2; //4
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3;
/**
* Set 3rd bit as 1 for image with different geo location ie. 1000
*/
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3; //8
/**
* The parameter FILE_FBMD is returned from the class ReadFBMD if the uploaded image contains FBMD data else returns IMAGE_OK
* ie. 10000
*/
public static final int FILE_FBMD = 1 << 4;
public static final int IMAGE_OK = 0;
public static final int IMAGE_KEEP = -1;
public static final int IMAGE_WAIT = -2;
@ -214,11 +232,12 @@ public class ImageUtils {
}
}
/**
* Result variable is a result of an or operation of all possible problems. Ie. if result
* is 0001 means IMAGE_DARK
* if result is 1100 IMAGE_DUPLICATE and IMAGE_GEOLOCATION_DIFFERENT
*/
public static String getErrorMessageForResult(Context context, @Result int result) {
/**
* Result variable is a result of an or operation of all possible problems. Ie. if result
* is 0001 means IMAGE_DARK, if result is 1100 IMAGE_DUPLICATE and IMAGE_GEOLOCATION_DIFFERENT
*/
StringBuilder errorMessage = new StringBuilder();
if (result <= 0 ) {
Timber.d("No issues to warn user is found");
@ -243,6 +262,10 @@ public class ImageUtils {
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_different_geolocation));
}
if ((FILE_FBMD & result) != 0) {
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_fbmd));
}
errorMessage.append("\n\n").append(context.getResources().getString(R.string.upload_problem_do_you_continue));
}

View file

@ -238,11 +238,12 @@
<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>
<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>
<string name="upload_problem_exist">Potential problems with this image:</string>
<string name="upload_problem_exist">Potential problems with this image :\n\n</string>
<string name="upload_problem_image_dark">Image is too dark.</string>
<string name="upload_problem_image_blurry">Image is blurry.</string>
<string name="upload_problem_image_duplicate">Image is already on Commons.</string>
<string name="upload_problem_different_geolocation">This picture was taken at a different location.</string>
<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>
<string name="upload_problem_do_you_continue">Do you still want to upload this picture?</string>
<string name="give_permission">Give permission</string>

View file

@ -19,7 +19,7 @@ import org.mockito.Mockito.*
import org.mockito.MockitoAnnotations
import java.io.FileInputStream
class ImageProcessingServiceTest {
class u {
@Mock
internal var fileUtilsWrapper: FileUtilsWrapper? = null
@Mock
@ -28,6 +28,8 @@ class ImageProcessingServiceTest {
internal var imageUtilsWrapper: ImageUtilsWrapper? = null
@Mock
internal var mwApi: MediaWikiApi? = null
@Mock
internal var readFBMD: ReadFBMD?=null
@InjectMocks
var imageProcessingService: ImageProcessingService? = null
@ -80,6 +82,8 @@ class ImageProcessingServiceTest {
.thenReturn(false)
`when`(mwApi!!.fileExistsWithName(ArgumentMatchers.anyString()))
.thenReturn(false)
`when`(readFBMD?.processMetadata(ArgumentMatchers.anyString()))
.thenReturn(Single.just(ImageUtils.IMAGE_OK))
}
@Test