mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-11-04 00:33: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