mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-11-04 16:53:55 +01:00
Convert ImageProcessingService to kotlin
This commit is contained in:
parent
e68341efb2
commit
85c6cda3d8
2 changed files with 183 additions and 190 deletions
|
|
@ -1,190 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_DUPLICATE;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||
|
||||
import android.content.Context;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Methods for pre-processing images to be uploaded
|
||||
*/
|
||||
@Singleton
|
||||
public class ImageProcessingService {
|
||||
|
||||
private final FileUtilsWrapper fileUtilsWrapper;
|
||||
private final ImageUtilsWrapper imageUtilsWrapper;
|
||||
private final ReadFBMD readFBMD;
|
||||
private final EXIFReader EXIFReader;
|
||||
private final MediaClient mediaClient;
|
||||
|
||||
@Inject
|
||||
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
||||
ImageUtilsWrapper imageUtilsWrapper,
|
||||
ReadFBMD readFBMD, EXIFReader EXIFReader,
|
||||
MediaClient mediaClient, Context context) {
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||
this.readFBMD = readFBMD;
|
||||
this.EXIFReader = EXIFReader;
|
||||
this.mediaClient = mediaClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||
* geolocation for image
|
||||
*
|
||||
* @param uploadItem UploadItem whose quality is to be checked
|
||||
* @param inAppPictureLocation In app picture location (if any)
|
||||
* @return Quality of UploadItem
|
||||
*/
|
||||
Single<Integer> validateImage(UploadItem uploadItem, LatLng inAppPictureLocation) {
|
||||
int currentImageQuality = uploadItem.getImageQuality();
|
||||
Timber.d("Current image quality is %d", currentImageQuality);
|
||||
if (currentImageQuality == IMAGE_KEEP || currentImageQuality == IMAGE_OK) {
|
||||
return Single.just(IMAGE_OK);
|
||||
}
|
||||
Timber.d("Checking the validity of image");
|
||||
String filePath = uploadItem.getMediaUri().getPath();
|
||||
|
||||
return Single.zip(
|
||||
checkDuplicateImage(filePath),
|
||||
checkImageGeoLocation(uploadItem.getPlace(), filePath, inAppPictureLocation),
|
||||
checkDarkImage(filePath),
|
||||
checkFBMD(filePath),
|
||||
checkEXIF(filePath),
|
||||
(duplicateImage, wrongGeoLocation, darkImage, fbmd, exif) -> {
|
||||
Timber.d("duplicate: %d, geo: %d, dark: %d" + "fbmd:" + fbmd + "exif:"
|
||||
+ exif,
|
||||
duplicateImage, wrongGeoLocation, darkImage);
|
||||
return duplicateImage | wrongGeoLocation | darkImage | fbmd | exif;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks caption of the given UploadItem
|
||||
*
|
||||
* @param uploadItem UploadItem whose caption is to be verified
|
||||
* @return Quality of caption of the UploadItem
|
||||
*/
|
||||
Single<Integer> validateCaption(UploadItem uploadItem) {
|
||||
int currentImageQuality = uploadItem.getImageQuality();
|
||||
Timber.d("Current image quality is %d", currentImageQuality);
|
||||
if (currentImageQuality == IMAGE_KEEP) {
|
||||
return Single.just(IMAGE_OK);
|
||||
}
|
||||
Timber.d("Checking the validity of caption");
|
||||
|
||||
return validateItemTitle(uploadItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to discourage users from uploading images to Commons that were taken from Facebook.
|
||||
* This attempts to detect whether an image was downloaded from Facebook by heuristically
|
||||
* searching for metadata that is specific to images that come from Facebook.
|
||||
*/
|
||||
private Single<Integer> checkFBMD(String filepath) {
|
||||
return readFBMD.processMetadata(filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* We try to minimize uploads from the Commons app that might be copyright violations. If an
|
||||
* image does not have any Exif metadata, then it was likely downloaded from the internet, and
|
||||
* is probably not an original work by the user. We detect these kinds of images by looking for
|
||||
* the presence of some basic Exif metadata.
|
||||
*/
|
||||
private Single<Integer> checkEXIF(String filepath) {
|
||||
return EXIFReader.processMetadata(filepath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks item caption - empty caption - existing caption
|
||||
*
|
||||
* @param uploadItem
|
||||
* @return
|
||||
*/
|
||||
private Single<Integer> validateItemTitle(UploadItem uploadItem) {
|
||||
Timber.d("Checking for image title %s", uploadItem.getUploadMediaDetails());
|
||||
List<UploadMediaDetail> captions = uploadItem.getUploadMediaDetails();
|
||||
if (captions.isEmpty()) {
|
||||
return Single.just(EMPTY_CAPTION);
|
||||
}
|
||||
|
||||
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
|
||||
.map(doesFileExist -> {
|
||||
Timber.d("Result for valid title is %s", doesFileExist);
|
||||
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate image
|
||||
*
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_DUPLICATE or IMAGE_OK
|
||||
*/
|
||||
Single<Integer> checkDuplicateImage(String filePath) {
|
||||
Timber.d("Checking for duplicate image %s", filePath);
|
||||
return Single.fromCallable(() -> fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(fileUtilsWrapper::getSHA1)
|
||||
.flatMap(mediaClient::checkFileExistsUsingSha)
|
||||
.map(b -> {
|
||||
Timber.d("Result for duplicate image %s", b);
|
||||
return b ? IMAGE_DUPLICATE : IMAGE_OK;
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for dark image
|
||||
*
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_DARK or IMAGE_OK
|
||||
*/
|
||||
private Single<Integer> checkDarkImage(String filePath) {
|
||||
Timber.d("Checking for dark image %s", filePath);
|
||||
return imageUtilsWrapper.checkIfImageIsTooDark(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private Single<Integer> checkImageGeoLocation(Place place, String filePath, LatLng inAppPictureLocation) {
|
||||
Timber.d("Checking for image geolocation %s", filePath);
|
||||
if (place == null || StringUtils.isBlank(place.getWikiDataEntityId())) {
|
||||
return Single.just(IMAGE_OK);
|
||||
}
|
||||
return Single.fromCallable(() -> filePath)
|
||||
.flatMap(path -> Single.just(fileUtilsWrapper.getGeolocationOfFile(path, inAppPictureLocation)))
|
||||
.flatMap(geoLocation -> {
|
||||
if (StringUtils.isBlank(geoLocation)) {
|
||||
return Single.just(IMAGE_OK);
|
||||
}
|
||||
return imageUtilsWrapper
|
||||
.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION
|
||||
import fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS
|
||||
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_DUPLICATE
|
||||
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP
|
||||
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
|
||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import timber.log.Timber
|
||||
import java.io.FileInputStream
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Methods for pre-processing images to be uploaded
|
||||
*/
|
||||
@Singleton
|
||||
class ImageProcessingService @Inject constructor(
|
||||
private val fileUtilsWrapper: FileUtilsWrapper,
|
||||
private val imageUtilsWrapper: ImageUtilsWrapper,
|
||||
private val readFBMD: ReadFBMD,
|
||||
private val EXIFReader: EXIFReader,
|
||||
private val mediaClient: MediaClient
|
||||
) {
|
||||
/**
|
||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||
* geolocation for image
|
||||
*
|
||||
* @param uploadItem UploadItem whose quality is to be checked
|
||||
* @param inAppPictureLocation In app picture location (if any)
|
||||
* @return Quality of UploadItem
|
||||
*/
|
||||
fun validateImage(uploadItem: UploadItem, inAppPictureLocation: LatLng?): Single<Int> {
|
||||
val currentImageQuality = uploadItem.imageQuality
|
||||
Timber.d("Current image quality is %d", currentImageQuality)
|
||||
if (currentImageQuality == IMAGE_KEEP || currentImageQuality == IMAGE_OK) {
|
||||
return Single.just(IMAGE_OK)
|
||||
}
|
||||
|
||||
Timber.d("Checking the validity of image")
|
||||
val filePath = uploadItem.mediaUri.path
|
||||
|
||||
return Single.zip(
|
||||
checkDuplicateImage(filePath),
|
||||
checkImageGeoLocation(uploadItem.place, filePath, inAppPictureLocation),
|
||||
checkDarkImage(filePath!!),
|
||||
checkFBMD(filePath),
|
||||
checkEXIF(filePath)
|
||||
) { duplicateImage: Int, wrongGeoLocation: Int, darkImage: Int, fbmd: Int, exif: Int ->
|
||||
Timber.d(
|
||||
"duplicate: %d, geo: %d, dark: %d, fbmd: %d, exif: %d",
|
||||
duplicateImage, wrongGeoLocation, darkImage, fbmd, exif
|
||||
)
|
||||
return@zip duplicateImage or wrongGeoLocation or darkImage or fbmd or exif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks caption of the given UploadItem
|
||||
*
|
||||
* @param uploadItem UploadItem whose caption is to be verified
|
||||
* @return Quality of caption of the UploadItem
|
||||
*/
|
||||
fun validateCaption(uploadItem: UploadItem): Single<Int> {
|
||||
val currentImageQuality = uploadItem.imageQuality
|
||||
Timber.d("Current image quality is %d", currentImageQuality)
|
||||
if (currentImageQuality == IMAGE_KEEP) {
|
||||
return Single.just(IMAGE_OK)
|
||||
}
|
||||
Timber.d("Checking the validity of caption")
|
||||
|
||||
return validateItemTitle(uploadItem)
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to discourage users from uploading images to Commons that were taken from Facebook.
|
||||
* This attempts to detect whether an image was downloaded from Facebook by heuristically
|
||||
* searching for metadata that is specific to images that come from Facebook.
|
||||
*/
|
||||
private fun checkFBMD(filepath: String?): Single<Int> =
|
||||
readFBMD.processMetadata(filepath)
|
||||
|
||||
/**
|
||||
* We try to minimize uploads from the Commons app that might be copyright violations. If an
|
||||
* image does not have any Exif metadata, then it was likely downloaded from the internet, and
|
||||
* is probably not an original work by the user. We detect these kinds of images by looking for
|
||||
* the presence of some basic Exif metadata.
|
||||
*/
|
||||
private fun checkEXIF(filepath: String): Single<Int> =
|
||||
EXIFReader.processMetadata(filepath)
|
||||
|
||||
|
||||
/**
|
||||
* Checks item caption - empty caption - existing caption
|
||||
*/
|
||||
private fun validateItemTitle(uploadItem: UploadItem): Single<Int> {
|
||||
Timber.d("Checking for image title %s", uploadItem.uploadMediaDetails)
|
||||
val captions = uploadItem.uploadMediaDetails
|
||||
if (captions.isEmpty()) {
|
||||
return Single.just(EMPTY_CAPTION)
|
||||
}
|
||||
|
||||
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.fileName)
|
||||
.map { doesFileExist: Boolean ->
|
||||
Timber.d("Result for valid title is %s", doesFileExist)
|
||||
if (doesFileExist) FILE_NAME_EXISTS else IMAGE_OK
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for duplicate image
|
||||
*
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_DUPLICATE or IMAGE_OK
|
||||
*/
|
||||
fun checkDuplicateImage(filePath: String?): Single<Int> {
|
||||
Timber.d("Checking for duplicate image %s", filePath)
|
||||
return Single.fromCallable { fileUtilsWrapper.getFileInputStream(filePath) }
|
||||
.map { stream: FileInputStream? ->
|
||||
fileUtilsWrapper.getSHA1(stream)
|
||||
}
|
||||
.flatMap { fileSha: String? ->
|
||||
mediaClient.checkFileExistsUsingSha(fileSha)
|
||||
}
|
||||
.map {
|
||||
Timber.d("Result for duplicate image %s", it)
|
||||
if (it) IMAGE_DUPLICATE else IMAGE_OK
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for dark image
|
||||
*
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_DARK or IMAGE_OK
|
||||
*/
|
||||
private fun checkDarkImage(filePath: String): Single<Int> {
|
||||
Timber.d("Checking for dark image %s", filePath)
|
||||
return imageUtilsWrapper.checkIfImageIsTooDark(filePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private fun checkImageGeoLocation(
|
||||
place: Place?,
|
||||
filePath: String?,
|
||||
inAppPictureLocation: LatLng?
|
||||
): Single<Int> {
|
||||
Timber.d("Checking for image geolocation %s", filePath)
|
||||
if (place == null || StringUtils.isBlank(place.wikiDataEntityId)) {
|
||||
return Single.just(IMAGE_OK)
|
||||
}
|
||||
|
||||
return Single.fromCallable<String?> { filePath }
|
||||
.flatMap { path: String? ->
|
||||
Single.just<String?>(
|
||||
fileUtilsWrapper.getGeolocationOfFile(path!!, inAppPictureLocation)
|
||||
)
|
||||
}
|
||||
.flatMap { geoLocation: String? ->
|
||||
if (geoLocation.isNullOrBlank()) {
|
||||
return@flatMap Single.just<Int>(IMAGE_OK)
|
||||
}
|
||||
imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation())
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue