mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +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
|
|
@ -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