mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
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:
parent
00b95ea9cf
commit
3a822d3c30
8 changed files with 125 additions and 18 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
BIN
app/libs/icafe-1.1-SNAPSHOT.jar
Normal file
BIN
app/libs/icafe-1.1-SNAPSHOT.jar
Normal file
Binary file not shown.
|
|
@ -97,7 +97,6 @@ public interface MediaWikiApi {
|
|||
@NonNull
|
||||
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
|
||||
|
||||
|
||||
boolean isUserBlockedFromCommons();
|
||||
|
||||
void logout();
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
44
app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java
Normal file
44
app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue