Full Code of Crazy-Marvin/MetadataRemover for AI

development b09bc5a97c92 cached
444 files
4.1 MB
1.1M tokens
1 requests
Download .txt
Showing preview only (4,653K chars total). Download the full file or copy to clipboard to get everything.
Repository: Crazy-Marvin/MetadataRemover
Branch: development
Commit: b09bc5a97c92
Files: 444
Total size: 4.1 MB

Directory structure:
gitextract_5a0us9tf/

├── .codecov.yml
├── .devcontainer/
│   └── devcontainer.json
├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── SECURITY.md
│   ├── SUPPORT.md
│   ├── dependabot.yaml
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml
├── .gitignore
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── lint.xml
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── rocks/
│       │           └── poopjournal/
│       │               └── metadataremover/
│       │                   └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── rocks/
│       │   │       └── poopjournal/
│       │   │           └── metadataremover/
│       │   │               ├── MetadataRemoverApp.kt
│       │   │               ├── di/
│       │   │               │   └── AppModule.kt
│       │   │               ├── metadata/
│       │   │               │   └── handlers/
│       │   │               │       ├── ApplyAllMetadataHandler.kt
│       │   │               │       ├── AudioVideoMetadataHandler.kt
│       │   │               │       ├── DocumentMetadataHandler.kt
│       │   │               │       ├── DrewMetadataReader.kt
│       │   │               │       ├── ExifMetadataHandler.kt
│       │   │               │       ├── FirstMatchMetadataHandler.kt
│       │   │               │       ├── NopMetadataHandler.kt
│       │   │               │       └── PngMetadataHandler.kt
│       │   │               ├── model/
│       │   │               │   ├── coordinates/
│       │   │               │   │   └── Coordinates.kt
│       │   │               │   ├── metadata/
│       │   │               │   │   ├── ClearedFile.kt
│       │   │               │   │   ├── Metadata.kt
│       │   │               │   │   ├── MetadataHandler.kt
│       │   │               │   │   ├── MetadataReader.kt
│       │   │               │   │   └── MetadataWriter.kt
│       │   │               │   ├── resources/
│       │   │               │   │   ├── Image.kt
│       │   │               │   │   ├── MediaType.kt
│       │   │               │   │   ├── MediaTypes.kt
│       │   │               │   │   ├── Resource.kt
│       │   │               │   │   └── Text.kt
│       │   │               │   └── util/
│       │   │               │       ├── MetadataHandlerExtensions.kt
│       │   │               │       └── SupportedTypes.kt
│       │   │               ├── ui/
│       │   │               │   ├── AboutActivity.kt
│       │   │               │   ├── LibrariesActivity.kt
│       │   │               │   ├── LicenseActivity.kt
│       │   │               │   ├── MainActivity.kt
│       │   │               │   ├── MetaAttributeAdapter.kt
│       │   │               │   ├── SettingsActivity.kt
│       │   │               │   └── adapter/
│       │   │               │       └── PageAdapter.kt
│       │   │               ├── util/
│       │   │               │   ├── ActivityLauncher.kt
│       │   │               │   ├── ActivityResultLauncher.kt
│       │   │               │   ├── AndroidViewDslScope.kt
│       │   │               │   ├── BindingAdapters.kt
│       │   │               │   ├── FileOpener.kt
│       │   │               │   ├── ImageFile.kt
│       │   │               │   ├── Logger.kt
│       │   │               │   ├── SharedPrefUtil.kt
│       │   │               │   ├── SingleLiveEvent.kt
│       │   │               │   ├── TimeZones.kt
│       │   │               │   └── extensions/
│       │   │               │       ├── ActivityLaunchers.kt
│       │   │               │       ├── Collections.kt
│       │   │               │       ├── Files.kt
│       │   │               │       ├── Locations.kt
│       │   │               │       ├── Numbers.kt
│       │   │               │       ├── Randoms.kt
│       │   │               │       ├── Strings.kt
│       │   │               │       ├── Times.kt
│       │   │               │       ├── android/
│       │   │               │       │   ├── Activities.kt
│       │   │               │       │   ├── Bundles.kt
│       │   │               │       │   ├── Colors.kt
│       │   │               │       │   ├── Contexts.kt
│       │   │               │       │   ├── Cursors.kt
│       │   │               │       │   ├── ExifInterfaces.kt
│       │   │               │       │   ├── FileDescriptors.kt
│       │   │               │       │   ├── ImageViews.kt
│       │   │               │       │   ├── Intents.kt
│       │   │               │       │   ├── Menus.kt
│       │   │               │       │   ├── TextViews.kt
│       │   │               │       │   ├── ViewGroups.kt
│       │   │               │       │   ├── Views.kt
│       │   │               │       │   └── architecture/
│       │   │               │       │       ├── LiveDatas.kt
│       │   │               │       │       ├── Uris.kt
│       │   │               │       │       └── ViewModels.kt
│       │   │               │       ├── glide/
│       │   │               │       │   └── RequestBuilders.kt
│       │   │               │       └── pngj/
│       │   │               │           └── PngJExtensions.kt
│       │   │               └── viewmodel/
│       │   │                   ├── ActivityLauncherViewModel.kt
│       │   │                   ├── ActivityResultLauncherViewModel.kt
│       │   │                   ├── MainViewModel.kt
│       │   │                   ├── MetadataViewModel.kt
│       │   │                   └── usecases/
│       │   │                       ├── GetDescriptor.kt
│       │   │                       ├── GetFileUri.kt
│       │   │                       ├── MetadataHandler.kt
│       │   │                       ├── SaveFiles.kt
│       │   │                       └── SharedFiles.kt
│       │   ├── play/
│       │   │   ├── contact-email.txt
│       │   │   ├── contact-website.txt
│       │   │   ├── default-language.txt
│       │   │   ├── listings/
│       │   │   │   ├── af/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ar/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── bg/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── bn/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ca/
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── de/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── el/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── en/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── en-US/
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── es-ES/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── et/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── fi-FI/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── fr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── hi/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── hr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── hu/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── id/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── it-IT/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ja-JP/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ko/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── mr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── my/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── nb-NO/
│       │   │   │   │   └── full-description.txt
│       │   │   │   ├── nl/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pa-IN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pl-PL/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pt-BR/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pt-PT/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ru/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── si-LK/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── sv-SE/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── sw/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── sw-KE/
│       │   │   │   │   └── full-description.txt
│       │   │   │   ├── ta-IN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── te-IN/
│       │   │   │   │   └── short-description.txt
│       │   │   │   ├── tr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── uk/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ur-IN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ur-PK/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── vi/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── xml_to_google_play.py
│       │   │   │   ├── zh-CN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   └── zh-TW/
│       │   │   │       ├── full-description.txt
│       │   │   │       ├── google_play.xml
│       │   │   │       ├── short-description.txt
│       │   │   │       └── title.txt
│       │   │   └── release-notes/
│       │   │       ├── de-DE/
│       │   │       │   ├── alpha.txt
│       │   │       │   └── internal.txt
│       │   │       └── en-US/
│       │   │           ├── alpha.txt
│       │   │           └── internal.txt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── add_circle.xml
│       │       │   ├── arrow_down.xml
│       │       │   ├── arrow_up.xml
│       │       │   ├── ic_add.xml
│       │       │   ├── ic_album.xml
│       │       │   ├── ic_apps.xml
│       │       │   ├── ic_arrow_back.xml
│       │       │   ├── ic_arrow_back_ltr.xml
│       │       │   ├── ic_arrow_back_rtl.xml
│       │       │   ├── ic_artist.xml
│       │       │   ├── ic_audio.xml
│       │       │   ├── ic_author.xml
│       │       │   ├── ic_bitrate.xml
│       │       │   ├── ic_build.xml
│       │       │   ├── ic_business.xml
│       │       │   ├── ic_calendar_today.xml
│       │       │   ├── ic_camera.xml
│       │       │   ├── ic_category.xml
│       │       │   ├── ic_close.xml
│       │       │   ├── ic_color.xml
│       │       │   ├── ic_comment.xml
│       │       │   ├── ic_compilation.xml
│       │       │   ├── ic_composer.xml
│       │       │   ├── ic_crop_free.xml
│       │       │   ├── ic_date.xml
│       │       │   ├── ic_description.xml
│       │       │   ├── ic_document.xml
│       │       │   ├── ic_duration.xml
│       │       │   ├── ic_edit.xml
│       │       │   ├── ic_error.xml
│       │       │   ├── ic_event.xml
│       │       │   ├── ic_exposure.xml
│       │       │   ├── ic_factory.xml
│       │       │   ├── ic_file.xml
│       │       │   ├── ic_file_type.xml
│       │       │   ├── ic_folder_special.xml
│       │       │   ├── ic_genre.xml
│       │       │   ├── ic_history.xml
│       │       │   ├── ic_image.xml
│       │       │   ├── ic_info.xml
│       │       │   ├── ic_info_outline.xml
│       │       │   ├── ic_label.xml
│       │       │   ├── ic_language.xml
│       │       │   ├── ic_launcher_monochrome.xml
│       │       │   ├── ic_lens.xml
│       │       │   ├── ic_light.xml
│       │       │   ├── ic_location.xml
│       │       │   ├── ic_loop.xml
│       │       │   ├── ic_person.xml
│       │       │   ├── ic_play.xml
│       │       │   ├── ic_print.xml
│       │       │   ├── ic_storage.xml
│       │       │   ├── ic_subject.xml
│       │       │   ├── ic_supervisor_account.xml
│       │       │   ├── ic_title.xml
│       │       │   ├── ic_tracks.xml
│       │       │   ├── ic_update.xml
│       │       │   ├── ic_video.xml
│       │       │   ├── ic_warning.xml
│       │       │   ├── ic_writer.xml
│       │       │   ├── ic_year.xml
│       │       │   ├── outline_settings_24.xml
│       │       │   ├── restart.xml
│       │       │   ├── rounded_bottom_sheet_shape.xml
│       │       │   ├── rounded_corner_background.xml
│       │       │   ├── rounded_dialog_background.xml
│       │       │   ├── rounded_dialog_bg.xml
│       │       │   └── shadow_navigation_bar.xml
│       │       ├── drawable-ldrtl/
│       │       │   └── ic_arrow_back.xml
│       │       ├── layout/
│       │       │   ├── activity_about.xml
│       │       │   ├── activity_about_card_contribute.xml
│       │       │   ├── activity_about_card_designer.xml
│       │       │   ├── activity_about_card_developer.xml
│       │       │   ├── activity_about_card_language.xml
│       │       │   ├── activity_about_card_owner.xml
│       │       │   ├── activity_about_card_source.xml
│       │       │   ├── activity_about_cards.xml
│       │       │   ├── activity_about_header.xml
│       │       │   ├── activity_license.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── activity_main_bottom_sheet.xml
│       │       │   ├── activity_main_preview.xml
│       │       │   ├── activity_settings.xml
│       │       │   ├── dialog_error_monitor.xml
│       │       │   ├── image_child.xml
│       │       │   ├── listitem_meta_data.xml
│       │       │   ├── listitem_opensourceaaaa.xml
│       │       │   └── type_selector_dialog.xml
│       │       ├── menu/
│       │       │   └── menu_main.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   └── ic_launcher.xml
│       │       ├── resources.properties
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   ├── strings_activity_about.xml
│       │       │   ├── strings_activity_libraries.xml
│       │       │   ├── strings_activity_license.xml
│       │       │   ├── strings_activity_main.xml
│       │       │   ├── strings_attributes.xml
│       │       │   ├── styles.xml
│       │       │   └── themes.xml
│       │       ├── values-af-rZA/
│       │       │   └── strings.xml
│       │       ├── values-ar/
│       │       │   └── strings.xml
│       │       ├── values-ar-rSA/
│       │       │   └── strings.xml
│       │       ├── values-bg-rBG/
│       │       │   └── strings.xml
│       │       ├── values-bn/
│       │       │   └── strings.xml
│       │       ├── values-bn-rBD/
│       │       │   └── strings.xml
│       │       ├── values-ca-rES/
│       │       │   └── strings.xml
│       │       ├── values-cs-rCZ/
│       │       │   └── strings.xml
│       │       ├── values-da-rDK/
│       │       │   └── strings.xml
│       │       ├── values-de-rDE/
│       │       │   └── strings.xml
│       │       ├── values-el-rGR/
│       │       │   └── strings.xml
│       │       ├── values-en-rUS/
│       │       │   └── strings.xml
│       │       ├── values-eo-rUY/
│       │       │   └── strings.xml
│       │       ├── values-es-rES/
│       │       │   └── strings.xml
│       │       ├── values-et/
│       │       │   └── strings.xml
│       │       ├── values-fi-rFI/
│       │       │   └── strings.xml
│       │       ├── values-fil-rPH/
│       │       │   └── strings.xml
│       │       ├── values-fo-rFO/
│       │       │   └── strings.xml
│       │       ├── values-fr-rFR/
│       │       │   └── strings.xml
│       │       ├── values-hi-rIN/
│       │       │   └── strings.xml
│       │       ├── values-hr-rHR/
│       │       │   └── strings.xml
│       │       ├── values-hu-rHU/
│       │       │   └── strings.xml
│       │       ├── values-ia/
│       │       │   └── strings.xml
│       │       ├── values-in-rID/
│       │       │   └── strings.xml
│       │       ├── values-it-rIT/
│       │       │   └── strings.xml
│       │       ├── values-iw-rIL/
│       │       │   └── strings.xml
│       │       ├── values-ja-rJP/
│       │       │   └── strings.xml
│       │       ├── values-ko-rKR/
│       │       │   └── strings.xml
│       │       ├── values-mr-rIN/
│       │       │   └── strings.xml
│       │       ├── values-my-rMM/
│       │       │   └── strings.xml
│       │       ├── values-nl-rNL/
│       │       │   └── strings.xml
│       │       ├── values-no-rNO/
│       │       │   └── strings.xml
│       │       ├── values-pa-rIN/
│       │       │   └── strings.xml
│       │       ├── values-pl-rPL/
│       │       │   └── strings.xml
│       │       ├── values-pt-rBR/
│       │       │   └── strings.xml
│       │       ├── values-pt-rPT/
│       │       │   └── strings.xml
│       │       ├── values-ro-rRO/
│       │       │   └── strings.xml
│       │       ├── values-ru-rRU/
│       │       │   └── strings.xml
│       │       ├── values-si-rLK/
│       │       │   └── strings.xml
│       │       ├── values-sv-rSE/
│       │       │   └── strings.xml
│       │       ├── values-sw/
│       │       │   └── strings.xml
│       │       ├── values-sw-rKE/
│       │       │   └── strings.xml
│       │       ├── values-ta-rIN/
│       │       │   └── strings.xml
│       │       ├── values-te-rIN/
│       │       │   └── strings.xml
│       │       ├── values-tr-rTR/
│       │       │   └── strings.xml
│       │       ├── values-uk-rUA/
│       │       │   └── strings.xml
│       │       ├── values-ur-rIN/
│       │       │   └── strings.xml
│       │       ├── values-ur-rPK/
│       │       │   └── strings.xml
│       │       ├── values-uz-rUZ/
│       │       │   └── strings.xml
│       │       ├── values-v21/
│       │       │   └── colors.xml
│       │       ├── values-v23/
│       │       │   ├── colors.xml
│       │       │   └── themes.xml
│       │       ├── values-v27/
│       │       │   ├── colors.xml
│       │       │   └── themes.xml
│       │       ├── values-v28/
│       │       │   ├── colors.xml
│       │       │   └── themes.xml
│       │       ├── values-val-rES/
│       │       │   └── strings.xml
│       │       ├── values-vi-rVN/
│       │       │   └── strings.xml
│       │       ├── values-zh-rCN/
│       │       │   └── strings.xml
│       │       ├── values-zh-rTW/
│       │       │   └── strings.xml
│       │       └── xml/
│       │           └── shared_file_paths.xml
│       └── test/
│           └── java/
│               └── rocks/
│                   └── poopjournal/
│                       └── metadataremover/
│                           └── ExampleUnitTest.kt
├── art/
│   ├── avatars/
│   │   └── art_profile_crazy_marvin.ai
│   ├── icons/
│   │   └── ic_launcher/
│   │       └── old/
│   │           └── ic_launcher.ai
│   └── mockups/
│       ├── README.md
│       └── medatada_remover_ui.ai
├── build.gradle.kts
├── buildSrc/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               ├── CiUtils.kt
│               ├── ProjectExtensions.kt
│               ├── Version.kt
│               └── Versions.kt
├── ci/
│   ├── .gitignore
│   ├── adbkey.pub
│   └── local.properties
├── fastlane/
│   └── metadata/
│       └── android/
│           └── en-US/
│               ├── changelogs/
│               │   ├── 20000.txt
│               │   ├── 20010.txt
│               │   ├── 20020.txt
│               │   ├── 20030.txt
│               │   ├── 30000.txt
│               │   └── 31000.txt
│               ├── full_description.txt
│               ├── short_description.txt
│               ├── title.txt
│               └── video.txt
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── libs/
│   └── ffmpeg-kit.aar
├── secret/
│   ├── .gitignore
│   └── secrets.tar.gpg
└── settings.gradle.kts

================================================
FILE CONTENTS
================================================

================================================
FILE: .codecov.yml
================================================
codecov:
  require_ci_to_pass: true

coverage:
  precision: 2
  round: down
  range: "70...100"

  status:
    project: true
    patch: true
    changes: false

parsers:
  gcov:
    branch_detection:
      conditional: true
      loop: true
      method: false
      macro: false

comment:
  layout: diff
  behavior: default
  require_changes: false


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
  "name": "Docker Android Build Box",
  "image": "mingc/android-build-box:latest",
  "extensions": ["vscode-icons-team.vscode-icons", "MS-vsliveshare.vsliveshare"]
}


================================================
FILE: .github/CODEOWNERS
================================================
# User @heinrichreimer manages CI scripts.
/ci/ @heinrichreimer

# User @heinrichreimer manages build configuration.
*.gradle @heinrichreimer
*.gradle.kts @heinrichreimer
*gradle.properties @heinrichreimer
*gradle-wrapper.properties @heinrichreimer

# User @CrazyMarvin manages artwork.
/art/ @CrazyMarvin


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing To Metadata Remover

👍🎉 First off, thanks for taking the time to contribute! 🎉👍

The following is a set of guidelines for contributing to [Metadata Remover](https://poopjournal.rocks/MetadataRemover/), which are hosted on [GitHub](https://github.com/Crazy-Marvin/MetadataRemover/).
These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.

## What should I know before I get started?

### Code of Conduct

This project adheres to the [Contributor Covenant code of conduct](https://contributor-covenant.org/version/1/4/).
By participating, you are expected to uphold this code. Please report unacceptable behavior to [marvin@poopjournal.rocks](mailto:marvin@poopjournal.rocks).

### Contact

If you have any questions or are unsure about something just drop a line to [marvin@poopjournal.rocks](mailto:marvin@poopjournal.rocks).

### Design Decisions

If you plan to make a significant decision in how to maintain the project and what it can or cannot support please send an email beforehand. 

## How Can I Contribute?

### Reporting Bugs

This section guides you through submitting a bug report for [Metadata Remover](https://poopjournal.rocks/MetadataRemover/) software. Following these guidelines helps maintainers and the community understand your report 📝, reproduce the behavior 📱💻🎮, and find related reports 🔎.

Before creating bug reports, please check this list as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible.

### Translating Strings

You can help to translate the app and marketing texts (e.g. Google Play description) via [Weblate](https://hosted.weblate.org/engage/metadata-remover/).

### Suggesting Enhancements

This section guides you through submitting an enhancement suggestion for [Metadata Remover](https://poopjournal.rocks/MetadataRemover/), including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion 📝 and find related suggestions 🔎.

Before creating enhancement suggestions, please check this list as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please include as many details as possible.

### Pull Requests

+ Include screenshots and animated GIFs in your pull request whenever possible.
+ Create a [branch](https://guides.github.com/introduction/flow/) for your edit
    
### Git Commit Messages

+ Use the present tense ("Add feature" not "Added feature")
+ Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
+ Limit the first line to 72 characters or less
+ Reference issues and pull requests liberally
+ When only changing documentation, include [ci skip] in the commit description
+ Consider starting the commit message with an applicable emoji:

        🎨 :art: when improving the format/structure of the code
        🐎 :racehorse: when improving performance
        🚱 :non-potable_water: when plugging memory leaks
        📝 :memo: when writing docs
        🐧 :penguin: when fixing something on Linux
        🍎 :apple: when fixing something on macOS
        🏁 :checkered_flag: when fixing something on Windows
        🐛 :bug: when fixing a bug
        🔥 :fire: when removing code or files
        💚 :green_heart: when fixing the CI build
        ✅ :white_check_mark: when adding tests
        🔒 :lock: when dealing with security
        ⬆️ :arrow_up: when upgrading dependencies
        ⬇️ :arrow_down: when downgrading dependencies
        👕 :shirt: when removing linter warnings
        
        
__Thank you so much! 😘__


================================================
FILE: .github/FUNDING.yml
================================================
custom: ["https://poopjournal.rocks/blog/donate/"]


================================================
FILE: .github/SECURITY.md
================================================
Please report (suspected) security vulnerabilities to [marvin@poopjournal.rocks](mailto:marvin@poopjournal.rocks). It would be great if you could prepare a patch too. Thanks!


================================================
FILE: .github/SUPPORT.md
================================================
Hi!  👋

We’re excited that you’re using **Metadata Remover** and we’d love to help.
To help us help you, please read through the following guidelines.

Please understand that people involved with this project often do so for fun,
next to their day job; you are not entitled to free customer service.

## Help us help you!

Spending time framing a question and adding support links or resources makes it
much easier for us to help.
It’s easy to fall into the trap of asking something too specific when you’re
close to a problem.
Then, those trying to help you out have to spend a lot of time asking additional
questions to understand what you are hoping to achieve.

Spending the extra time up front can help save everyone time in the long run.

*   Try to define what you need help with:
    *   Is there something in particular you want to do?
    *   What problem are you encountering and what steps have you taken to try
        and fix it?
    *   Is there a concept you’re not understanding?
*   Learn about the [rubber duck debugging method](https://rubberduckdebugging.com/)
*   Avoid falling for the [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem/66378#66378)
*   Search on GitHub to see if a similar question has been asked
*   If possible, provide sample code, a [CodeSandbox](https://codesandbox.io/), or a video/GIF
*   The more time you put into asking your question, the better we can help you

## Contributions

See [`contributing.md`](https://github.com/Crazy-Marvin/MetadataRemover/blob/trunk/.github/CONTRIBUTING.md) on how to contribute. Quality PRs are really appreaciated!



================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"
  - package-ecosystem: "gradle"
    directory: "/"
    schedule:
      interval: "monthly"


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on: push
jobs:
  build:
    runs-on: macos-latest
    steps:
      - name: "📥 Check-out"
        uses: actions/checkout@v6
      - name: "🧪 Gradle Wrapper Validation"
        uses: gradle/wrapper-validation-action@v3
      - name: "🧰 Install JDK"
        uses: actions/setup-java@v5
        with:
          java-version: 1.8
          java-package: jdk
      - name: "🧰 Install Android SDK"
        uses: android-actions/setup-android@v4
      - name: "📁 Copy local CI properties"
        run: cp ci/local.properties ./
      - name: "🏗 Build"
        run: ./gradlew assembleDebug
      - name: "🧪 Android LINT"
        run: ./gradlew lint
      - name: "🧪 Code coverage"
        run: ./gradlew jacocoTestReport
      - name: "🧪 Unit test"
        run: ./gradlew test
      - name: "🧪 Integration test"
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 25
          script: ./gradlew connectedAndroidTest
      - name: "📤 Upload code coverage"
        uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy
on:
  push:
    branches:
      - master
jobs:
  deploy:
    runs-on: macos-10.15
    steps:
      - name: "📥 Check-out"
        uses: actions/checkout@v6
      - name: "🧰 Install JDK"
        uses: actions/setup-java@v5
        with:
          java-version: 1.8
          java-package: jdk
      - name: "🧰 Install Android SDK"
        uses: android-actions/setup-android@v4
      - name: "🕶 Decrypt secret files"
        run: |
          cd secret
          gpg --quiet --batch --yes --decrypt --passphrase="${{ secrets.FILES_PASSPHRASE }}" --output secrets.tar secrets.tar.gpg
          tar -xf secrets.tar
      - name: "📁 Copy local CI properties"
        run: cp ci/local.properties ./
      - name: "🏗 Build"
        run: ./gradlew assembleDebug
      - name: "🚀 GitHub release"
        run: ./gradlew githubRelease
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: "🚀 Google Play publish"
        run: ./gradlew publishRelease


================================================
FILE: .gitignore
================================================

# Created by https://www.gitignore.io/api/kotlin,android

### Android ###
# Built application files
#*.apk

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle/
build/
release/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log

# Android Studio Navigation editor temp files
.navigation/

# Android Studio captures folder
captures/

# Intellij
*.iml
.idea/

# Keystore files
*.jks

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

# Google Services (e.g. APIs or Firebase)
google-services.json

# Freeline
freeline.py
freeline/
freeline_project_description.json

### Android Patch ###
gen-external-apklibs

### Kotlin ###
# Compiled class file

# Log file

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.war
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# End of https://www.gitignore.io/api/kotlin,android


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
[![Icon](art/icons/ic_launcher/legacy/ic_launcher_squircle_xxxhdpi.png)](art/icons/ic_launcher/ic_launcher_play_store.png)

# Metadata Remover App For Android
[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/Crazy-Marvin/MetadataRemover/CI/development)](https://github.com/Crazy-Marvin/MetadataRemover/actions)
[![License](https://img.shields.io/github/license/Crazy-Marvin/MetadataRemover.svg)](https://github.com/Crazy-Marvin/MetadataRemover/blob/trunk/LICENSE)
[![Figma Mockups](https://img.shields.io/badge/Figma-black?logo=figma)](https://www.figma.com/file/wD8JMCudIIPFW80mDP8QJ8/Metadata-Remover?type=design&mode=design&t=nuZ7zffrFlfe3AW5-1)
[![Last commit](https://img.shields.io/github/last-commit/Crazy-Marvin/MetadataRemover.svg?style=flat)](https://github.com/Crazy-Marvin/MetadataRemover/commits)
[![Releases](https://img.shields.io/github/downloads/Crazy-Marvin/MetadataRemover/total.svg?style=flat)](https://github.com/Crazy-Marvin/MetadataRemover/releases)
[![Latest tag](https://img.shields.io/github/tag/Crazy-Marvin/MetadataRemover.svg?style=flat)](https://github.com/Crazy-Marvin/MetadataRemover/tags)
[![Issues](https://img.shields.io/github/issues/Crazy-Marvin/MetadataRemover.svg?style=flat)](https://github.com/Crazy-Marvin/MetadataRemover/issues)
[![Pull requests](https://img.shields.io/github/issues-pr/Crazy-Marvin/MetadataRemover.svg?style=flat)](https://github.com/Crazy-Marvin/MetadataRemover/pulls)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/7dadc506c2df42a38c2ef733948f9492)](https://www.codacy.com/gh/Crazy-Marvin/MetadataRemover/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Crazy-Marvin/MetadataRemover&utm_campaign=Badge_Grade)
[![codecov](https://codecov.io/gh/Crazy-Marvin/MetadataRemover/branch/development/graph/badge.svg?token=ECQID61KGH)](https://codecov.io/gh/Crazy-Marvin/MetadataRemover)
[![Hosted Weblate](https://hosted.weblate.org/widgets/metadata-remover/-/svg-badge.svg)](https://hosted.weblate.org/engage/metadata-remover/)
[![Sentry](https://img.shields.io/badge/Sentry-%23362D59.svg?logo=sentry&style=flat)](https://sentry.com)
[![Known Vulnerabilities](https://snyk.io/test/github/Crazy-Marvin/MetadataRemover/badge.svg?targetFile=app%2Fbuild.gradle.kts)](https://snyk.io/test/github/Crazy-Marvin/MetadataRemover?targetFile=app%2Fbuild.gradle.kts)
[![API](https://img.shields.io/badge/API-26%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=26)
![Gradle Play Publisher](https://img.shields.io/badge/-Gradle_Play_Publisher-brightgreen?logo=gradle&link=https%3A%2F%2Fgithub.com%2FTriple-T%2Fgradle-play-publisher)
[![F-Droid](https://img.shields.io/f-droid/v/rocks.poopjournal.metadataremover.svg)](https://f-droid.org/en/packages/rocks.poopjournal.metadataremover/)
[![IzzyOnDroid](https://img.shields.io/endpoint?url=https://apt.izzysoft.de/fdroid/api/v1/shield/rocks.poopjournal.MetadataRemover&label=IzzyOnDroid&cacheSeconds=86400)](https://apt.izzysoft.de/fdroid/index/apk/rocks.poopjournal.metadataremover)
[![Google Play](https://badgen.net/badge/icon/googleplay?icon=googleplay&label)](https://play.google.com/store/apps/details?id=rocks.poopjournal.MetadataRemover)

_Remove any image's metadata fast and easily._

<a href="https://f-droid.org/packages/rocks.poopjournal.metadataremover/">
    <img alt="Get it on F-Droid"
        height="80"
        src="https://f-droid.org/badge/get-it-on.png" />
        </a>
<a href="https://apt.izzysoft.de/fdroid/index/apk/rocks.poopjournal.metadataremover">
    <img alt="Get it on IzzyOnDroid"
        height="80"
        src="https://github.com/Crazy-Marvin/MetadataRemover/assets/15004217/978819ff-a4ac-4656-ace7-a0607fca50b3.png" />
        </a>
<a href="https://play.google.com/store/apps/details?id=rocks.poopjournal.metadataremover">
    <img alt="Get it on Google Play"
        height="80"
        src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" />
        </a> 

_Protect your privacy by removing metadata from your photos, videos and documents, before sharing them on the internet!_

## Features

 ✔️ View metadata
 
 ✔️ Image preview
 
 ✔️ Remove metadata
 
 ✔️ Simple and intuitive interface
 
 ✔️ Share directly from the app
 
## Learn more

Whenever you take a picture, shoot a video or create a document additional metadata is saved in the image file.

Most smartphones do <i>not inform</i> you about this.

<b>Metadata can look like this:</b>

<ul>
<li> 🕑 On which day was the picture taken, and at what time? </li>
<li> 🗺️ And where exactly? </li>
<li> 📷 Which camera or smartphone was used? </li>
<li> 🔧 What camera settings were used? </li>
<li> 📝 Notes of the photographer or the camera? </li>
<li> 📌 More and more often, even exact GPS coordinates are saved in your photo. </li>
<li> 👥 Who created the document?</li>    
</ul>

Metadata is sometimes very useful — for instance when sorting holiday photos.

As soon as you share files with others via social media, all this info is visible <i>publicly</i>.

Data collectors and stalkers are more easily able to discover your <i>place of residence or workplace</i> from the metadata or draw conclusions about your <i>daily routine</i>.

Tracking services create more comprehensive advertising profiles and sell your data to other organizations.

Use the Metadata Remover to easily view all that data, <i>remove it entirely</i>, and then share the anonymized file directly!
That way you stay <i>anonymous</i> and <i>safe</i> on the Internet, while your friends can still admire your cute cat.

Those formats are supported at the moment:
<ul>
<li>📷 Image: PNG & JPEG</li>
<li>📹 Video: AVI, MP4, MPEG, OGG, QUICKTIME, WEBM & WMV</li>
<li>📝 Docs: DOCX, DOC, XLSX, XLS, ODT, ODS & PDF</li>
<li>🎵 Audio: MP4, MPEG (including AAC & MP3), OGG, WEBM & X-WAV</li>
</ul>

<i>Happy sharing! 😽</i>

## Deployment

### Encoding/decoding secrets

To decode the secrets, run:

```shell
cd secret
gpg --quiet --batch --yes --decrypt --output secrets.tar secrets.tar.gpg
tar -xf secrets.tar
cd ..
```

To encode the secrets, run:

```shell
cd secret
tar -cf secrets.tar --exclude=secrets.tar --exclude=secrets.tar.gpg --exclude=.gitignore .
gpg --symmetric --cipher-algo AES256 --output secrets.tar.gpg secrets.tar
cd ..
```

The same password needs to be saved as `FILES_PASSPHRASE` variable
to this repository's [secret](https://github.com/Crazy-Marvin/MetadataRemover/settings/secret).

## Contributing

The ```development``` or a feature branch is used while developing the code, and pushed into the master branch ```trunk``` afterwards for releases.
PRs to the ```trunk``` need at least one approving review before getting merged.

Help translate the app at [Hosted Weblate](https://hosted.weblate.org/engage/metadata-remover/).

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

Check out the [contribution guidelines](https://github.com/Crazy-Marvin/MetadataRemover/blob/trunk/.github/CONTRIBUTING.md) for details please.

## License

[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle.kts
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.dsl.TestOptions
import com.android.builder.core.DefaultApiVersion
import com.android.builder.core.DefaultProductFlavor
import org.gradle.internal.Cast.uncheckedCast
*/

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    id("com.google.devtools.ksp")
    id("com.google.dagger.hilt.android")
    id("com.mikepenz.aboutlibraries.plugin")
    id("io.sentry.kotlin.compiler.gradle") version "6.3.0"
}



val secretProperties = rootProject
        .file("secret/secret.properties")
        .asProperties()
        .asStringMap()


/*
override var version: Version
    set(value) {
        super.setVersion(value)
        field = value
    }


version = Versions.app
 */
android {
    compileSdk = 36

    defaultConfig {
        applicationId = "rocks.poopjournal.metadataremover"

        minSdk = 26
        targetSdk = 36

        versionCode = 30000
        versionName = "3.0.0"

    dependenciesInfo {
        // Disables dependency metadata when building APKs.
        includeInApk = false
        // Disables dependency metadata when building Android App Bundles.
        includeInBundle = false
    }

        // The default test runner for Android instrumentation tests.
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    val releaseSigning by signingConfigs.creating {
        storeFile = rootProject.file("secret/MetadataRemover.jks")
                .takeIf(File::exists)
        storePassword = secretProperties["keystore.password"]
        keyAlias = secretProperties["keystore.alias.googleplay.name"]
        keyPassword = secretProperties["keystore.alias.googleplay.password"]
    }

    buildTypes {

        // Debug builds
        val debug by existing {
            // Append "DEBUG" to all debug build versions
            versionNameSuffix = "-$latestCommitHash (debug)"
            isDebuggable = true
            isTestCoverageEnabled = true
        }

        // Production builds
        val release by existing {
            isMinifyEnabled = false
            isShrinkResources = false

            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )

            signingConfig = releaseSigning
            isCrunchPngs = false
        }
    }


    buildFeatures {
        viewBinding = true
        dataBinding = true
        buildConfig = true
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    kotlin {
        jvmToolchain(17)
    }

    packaging {
        resources {
            excludes.add("META-INF/INDEX.LIST")
            excludes.add("META-INF/DEPENDENCIES")
        }
    }

    androidResources{
        generateLocaleConfig = true
    }

    splits {
        abi {
            isEnable = true
            reset()
            include("x86", "x86_64", "armeabi-v7a", "arm64-v8a")
            isUniversalApk = false
        }
    }


    // Always show the result of every unit test, even if it passes.
    /*
    testOptions.unitTests.all {
        testLogging {
            events("passed", "skipped", "failed", "standardOut", "standardError")
        }
    }*/

    lintOptions {
        ignore("MissingTranslation")
    }
    namespace = "rocks.poopjournal.metadataremover"
}



//dependencies(Dependencies.app)

dependencies {

    implementation("androidx.core:core-ktx:1.17.0")
    implementation ("androidx.activity:activity-ktx:1.13.0")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("com.google.android.material:material:1.13.0")
    implementation("androidx.constraintlayout:constraintlayout:2.2.1")

    //Metadata Extractors
    implementation("com.drewnoakes:metadata-extractor:2.19.0")
    implementation("androidx.exifinterface:exifinterface:1.4.2")
    implementation("ar.com.hjg:pngj:2.1.0") {
        // Explicitly exclude the AWT library, as that is not available on Android.
        exclude(group = "java.awt.image")
    }

    //Glide
    implementation("com.github.bumptech.glide:glide:5.0.5")
    implementation(libs.androidx.activity)
    ksp("com.github.bumptech.glide:compiler:5.0.5")

    //CircleImageView
    implementation("de.hdodenhof:circleimageview:3.1.0")

    //Timber
    implementation("com.jakewharton.timber:timber:5.0.1")

    //About libraries
    implementation("com.mikepenz:aboutlibraries:13.1.0")

    //Dagger-hilt
    implementation ("com.google.dagger:hilt-android:2.57.1")
    ksp ("com.google.dagger:hilt-android-compiler:2.57.1")
    ksp ("androidx.hilt:hilt-compiler:1.2.0")

    //ffmpeg
    implementation ("com.arthenica:smart-exception-java:0.2.1")
    implementation( files("../libs/ffmpeg-kit.aar"))

    //Apache POI
    implementation ("org.apache.poi:poi:5.5.1")
    implementation ("org.apache.poi:poi-ooxml:5.5.1")
    implementation ("org.apache.poi:poi-scratchpad:5.5.1")
    implementation ("org.apache.odftoolkit:simple-odf:0.8.2-incubating")
    implementation ("com.tom-roush:pdfbox-android:2.0.27.0")

    //sentry
    implementation("io.sentry:sentry-android:8.32.0")

}


/*
play {
    serviceAccountCredentials = rootProject.file("secret/api-7281121051860956110-977812-57e7308358e6.json")
    track = "internal"
    releaseStatus = "draft"
    defaultToAppBundles = true
    resolutionStrategy = "fail"
}*/

/*
githubRelease {
    setToken(System.getenv("GITHUB_TOKEN") ?: secretProperties["github.credentials.token"])
    setOwner("Crazy-Marvin")
    setRepo("MetadataRemover")
    setTagName("v$version")
    setTargetCommitish("master")
    setReleaseName("Release $version")
    val commitHashLinePrefix = Regex("^(?<hash>[0-9a-f]{1,40}) (?<message>.*)$", RegexOption.IGNORE_CASE)
    setBody(provider {
        "## Full changelog\n${
        provider(changelog())
                .get()
                .lines()
                .joinToString(separator = "\n") { change ->
                    change.replace(commitHashLinePrefix, "- \${hash} \${message}")
                }
        }"
    })
    // Don't publish releases directly.
    // Instead create a draft and let maintainers approve it.
    setDraft(true)
    setPrerelease(version.build != 0)
    val releaseAssets = fileTree(buildDir)
            .apply {
                include("outputs/**/release/**.aab", "outputs/**/release/**.apk")
            }
    setReleaseAssets(releaseAssets)
    setOverwrite(true)

    afterEvaluate {
        val githubRelease by tasks.getting {
            val bundleRelease by tasks.getting
            val assembleRelease by tasks.getting {
                shouldRunAfter(bundleRelease)
            }
            dependsOn(bundleRelease, assembleRelease)
        }
    }
}
*/


// Lint F-Droid resources.
//tasks["lint"].dependsOn("fdroidLint")

val printVersionName by tasks.creating {
    doLast {
        println(version)
    }
}

jacoco {
    toolVersion = "0.8.3"
}

repositories(Repositories.app)
dependencies(Dependencies.app)


var BaseExtension.compileSdk: Int
    get() = compileSdkVersion.removePrefix("android-").toInt()
    set(value) = compileSdkVersion(value)

var DefaultProductFlavor.minSdk: Int
    get() = minSdkVersion!!.apiLevel
    set(value) {
        minSdkVersion = DefaultApiVersion(value)
    }

var DefaultProductFlavor.targetSdk: Int
    get() = targetSdkVersion!!.apiLevel
    set(value) {
        targetSdkVersion = DefaultApiVersion(value)
    }

fun TestOptions.UnitTestOptions.all(action: Test.() -> Unit) =
        all(uncheckedCast(closureOf(action)))


================================================
FILE: app/lint.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<lint>
    <issue id="IconLauncherShape">
        <ignore path="src/main/res/mipmap-xxxhdpi/ic_launcher_background.png"/>
        <ignore path="src/main/res/mipmap-xxxhdpi/ic_launcher_background.png"/>
        <ignore path="src/main/res/mipmap-xxxhdpi/ic_launcher_background.png"/>
        <ignore path="src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png"/>
    </issue>
    <issue id="InvalidPackage">
        <ignore path="*.pngj.*" />
    </issue>
</lint>

================================================
FILE: app/proguard-rules.pro
================================================
# MIT License
#
# Copyright (c) 2018 Jan Heinrich Reimer
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


================================================
FILE: app/src/androidTest/java/rocks/poopjournal/metadataremover/ExampleInstrumentedTest.kt
================================================
package rocks.poopjournal.metadataremover

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation()
                .targetContext

        appContext.packageName shouldBeEqualTo "rocks.poopjournal.metadataremover"
    }
}


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name=".MetadataRemoverApp"
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/Theme.App">
        <activity
            android:name=".ui.SettingsActivity"
            android:exported="false" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ui.MainActivity" />
        </activity>
        <activity
            android:name=".ui.MainActivity"
            android:exported="true">
            <tools:validation testUrl="https://poopjournal.rocks/MetadataRemover/app" />

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="poopjournal.rocks"
                    android:pathPrefix="/MetadataRemover/app"
                    android:scheme="https" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND" />

                <category android:name="android.intent.category.DEFAULT" />

                <data android:mimeType="image/*" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />

                <category android:name="android.intent.category.DEFAULT" />

                <data android:mimeType="image/*" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.AboutActivity"
            android:label="@string/title_activity_about"
            android:parentActivityName=".ui.MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ui.MainActivity" />
        </activity>
        <activity
            android:name=".ui.LibrariesActivity"
            android:label="@string/title_activity_libraries"
            android:parentActivityName=".ui.AboutActivity"
            android:theme="@style/Theme.App.Libraries">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ui.AboutActivity" />
        </activity>
        <activity
            android:name=".ui.LicenseActivity"
            android:label="@string/title_activity_license"
            android:parentActivityName=".ui.AboutActivity"
            android:theme="@style/Theme.App.License">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".ui.AboutActivity" />
        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.files"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/shared_file_paths" />
        </provider>

        <meta-data
            android:name="io.sentry.dsn"
            android:value="https://6b6ed508df1467be8f6218288bf38f78@o282785.ingest.us.sentry.io/4510885021483008" />
        <meta-data
            android:name="io.sentry.auto-init"
            android:value="false" /> <!-- Add data like request headers, user ip address and device name, see https://docs.sentry.io/platforms/android/data-management/data-collected/ for more info -->
        <meta-data
            android:name="io.sentry.send-default-pii"
            android:value="true" /> <!-- enable automatic breadcrumbs for user interactions (clicks, swipes, scrolls) -->
        <meta-data
            android:name="io.sentry.traces.user-interaction.enable"
            android:value="true" /> <!-- enable screenshot for crashes -->
        <meta-data
            android:name="io.sentry.attach-screenshot"
            android:value="true" /> <!-- enable view hierarchy for crashes -->
        <meta-data
            android:name="io.sentry.attach-view-hierarchy"
            android:value="true" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/MetadataRemoverApp.kt
================================================
package rocks.poopjournal.metadataremover

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import io.sentry.android.core.SentryAndroid
import rocks.poopjournal.metadataremover.util.SharedPrefUtil

@HiltAndroidApp
class MetadataRemoverApp: Application() {
    override fun onCreate() {
        super.onCreate()
        val pref = SharedPrefUtil(this)

        if (pref.isSentryMonitorEnabled()) {
            enableSentry()
        } else {
            disableSentry()
        }
    }
    private fun enableSentry() {
        SentryAndroid.init(this) { options ->
            // DSN will be read automatically from manifest
            options.isEnableUserInteractionTracing = true
            options.isAttachScreenshot = true
            options.isAttachViewHierarchy = true
        }
    }

    private fun disableSentry() {
        io.sentry.Sentry.close()
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/di/AppModule.kt
================================================
package rocks.poopjournal.metadataremover.di

import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import rocks.poopjournal.metadataremover.viewmodel.usecases.GetDescriptor
import rocks.poopjournal.metadataremover.viewmodel.usecases.GetFileUri
import rocks.poopjournal.metadataremover.viewmodel.usecases.MetadataHandler
import rocks.poopjournal.metadataremover.viewmodel.usecases.SaveFiles
import rocks.poopjournal.metadataremover.viewmodel.usecases.SharedFiles
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun providesDescriptor (@ApplicationContext context: Context) : GetDescriptor{
        return GetDescriptor(context = context)
    }

    @Provides
    @Singleton
    fun providesMetadataHandler (@ApplicationContext context: Context) : MetadataHandler{
        return MetadataHandler(context = context)
    }

    @Provides
    @Singleton
    fun providesSharedImages (@ApplicationContext context: Context) : SharedFiles {
        return SharedFiles(context = context)
    }

    @Provides
    @Singleton
    fun providesFileUri(@ApplicationContext context: Context) : GetFileUri {
        return GetFileUri(context = context)
    }

    @Provides
    @Singleton
    fun providesSaveImages(@ApplicationContext context: Context): SaveFiles{
        return SaveFiles(context = context)
    }

}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/ApplyAllMetadataHandler.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.metadata.handlers

import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.util.extensions.asDeque
import java.io.File
import java.util.*

/**
 * [MetadataHandler] that combines multiple [handlers] by reading all metadata
 * and removing metadata by applying all [handlers].
 */
class ApplyAllMetadataHandler(
        private val handlers: Queue<MetadataHandler>
) : MetadataHandler {

    constructor(vararg handlers: MetadataHandler) : this(handlers.asDeque())

    override val readableMimeTypes = handlers
            .flatMapTo(mutableSetOf()) { handler ->
                handler.readableMimeTypes
            }
    override val writableMimeTypes = handlers
            .flatMapTo(mutableSetOf()) { handler ->
                handler.writableMimeTypes
            }

    private fun filterMetadataHandlers(mediaType: MediaType): Queue<MetadataHandler> {
        return handlers
                .filterTo(LinkedList()) { fileHandler ->
                    mediaType in fileHandler.readableMimeTypes
                }
    }

    override suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata? {
        return filterMetadataHandlers(mediaType)
                .map { it.loadMetadata(mediaType, inputFile) }
                .reduce(::mergeMetadata)
    }

    private fun mergeMetadata(acc: Metadata?, next: Metadata?): Metadata? {
        return when {
            acc == null -> next
            next == null -> acc
            else -> Metadata(
                    title = acc.title ?: next.title,
                    thumbnail = acc.thumbnail,
                    attributes = acc.attributes + next.attributes
            )
        }
    }

    override suspend fun removeMetadata(
            mediaType: MediaType,
            inputFile: File,
            outputFile: File,
            attributes: List<Metadata.Attribute>
    ): Boolean {

        return  filterMetadataHandlers(mediaType)
                .any { it.removeMetadata(mediaType, inputFile, outputFile,attributes) }
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/AudioVideoMetadataHandler.kt
================================================
package rocks.poopjournal.metadataremover.metadata.handlers

import android.content.Context
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import androidx.core.net.toUri
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import rocks.poopjournal.metadataremover.R
import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.Image
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.model.resources.MediaTypes
import rocks.poopjournal.metadataremover.model.resources.Text
import java.io.File

class AudioVideoMetadataHandler(private val context: Context): MetadataHandler {

    override val readableMimeTypes =    MediaTypes[MediaTypes.AVI_VIDEO]    +
                                        MediaTypes[MediaTypes.MP4_VIDEO]    +
                                        MediaTypes[ MediaTypes.MPEG_VIDEO]  +
                                        MediaTypes[ MediaTypes.OGG_VIDEO]   +
                                        MediaTypes[ MediaTypes.QUICKTIME]   +
                                        MediaTypes[ MediaTypes.WEBM_VIDEO]  +
                                        MediaTypes[ MediaTypes.WMV]         +
                                        MediaTypes[ MediaTypes.MP4_AUDIO]   +
                                        MediaTypes[ MediaTypes.MPEG_AUDIO]  +
                                        MediaTypes[ MediaTypes.OGG_AUDIO]   +
                                        MediaTypes[ MediaTypes.WEBM_AUDIO]  +
                                        MediaTypes[ MediaTypes.WAV_AUDIO]

    override val writableMimeTypes =    MediaTypes[MediaTypes.AVI_VIDEO]    +
                                        MediaTypes[MediaTypes.MP4_VIDEO]    +
                                        MediaTypes[ MediaTypes.MPEG_VIDEO]  +
                                        MediaTypes[ MediaTypes.OGG_VIDEO]   +
                                        MediaTypes[ MediaTypes.QUICKTIME]   +
                                        MediaTypes[ MediaTypes.WEBM_VIDEO]  +
                                        MediaTypes[ MediaTypes.WMV]         +
                                        MediaTypes[ MediaTypes.MP4_AUDIO]   +
                                        MediaTypes[ MediaTypes.MPEG_AUDIO]  +
                                        MediaTypes[ MediaTypes.OGG_AUDIO]   +
                                        MediaTypes[ MediaTypes.WEBM_AUDIO]  +
                                        MediaTypes[ MediaTypes.WAV_AUDIO]

    override suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata? {
        check(mediaType in readableMimeTypes)

        return Metadata(
            thumbnail = Image(inputFile),
            attributes = mediaMetadataRetrieve(inputFile.toUri()).toSet()
        )
    }


    override suspend fun removeMetadata(
        mediaType: MediaType,
        inputFile: File,
        outputFile: File,
        attributes: List<Metadata.Attribute>
    ): Boolean {
        check(mediaType in writableMimeTypes)

        val selectedKeys = attributes
            .filter { it.removable && it.selected }
            .mapNotNull { it.tag }

        // If no attributes selected, remove all
        val removeAll = selectedKeys.isEmpty()

        // Build FFmpeg metadata removal commands
        val metadataCommands = if (removeAll) {
            "-map_metadata -1"
        } else {
            selectedKeys.joinToString(" ") { key -> "-metadata $key=" }
        }

        val command = "-y -i ${inputFile.absolutePath} -map 0 $metadataCommands -c copy ${outputFile.absolutePath}"
        val session = FFmpegKit.execute(command)
        return ReturnCode.isSuccess(session.returnCode)
    }

    private fun mediaMetadataRetrieve(uri: Uri): List<Metadata.Attribute>{
        val retriever = MediaMetadataRetriever()
        retriever.setDataSource(context, uri)

        val metadataList = mutableListOf<Metadata.Attribute>()

        fun addMetadata(key: Int, label: String, iconRes: Int,removable : Boolean , tag : String) {
            val value = retriever.extractMetadata(key)
            if (value != null) {
                metadataList.add(Metadata.Attribute(
                    label = Text(label),
                    icon = Image(iconRes),
                    primaryValue = Text(value),
                    removable = removable,
                    tag = tag
                ))
            }
        }

        // Duration (special case for formatting)
        val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull()
        val durationSec = durationMs?.div(1000)
        val durationValue = Text(durationSec?.let { "$it seconds" } ?: "Duration not available")
        metadataList.add(Metadata.Attribute(
            label = Text("Duration"),
            icon = Image(R.drawable.ic_duration),
            primaryValue = durationValue,
            removable = true,
            tag = "duration"
        ))

        // Add other metadata
        addMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM, "Album", R.drawable.ic_album,removable = true, tag = "album")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, "Album Artist", R.drawable.ic_artist,removable = true, tag = "album_artist")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST, "Artist", R.drawable.ic_artist,removable = true, tag = "artist")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR, "Author", R.drawable.ic_author,removable = true, tag = "author")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE, "Bitrate", R.drawable.ic_bitrate,removable = true, tag = "bitrate")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            addMetadata(MediaMetadataRetriever.METADATA_KEY_BITS_PER_SAMPLE, "Bits Per Sample", R.drawable.ic_audio,removable = true, tag = "bits_per_sample")
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            addMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE, "Capture Framerate", R.drawable.ic_video,removable = true, tag = "capture_framerate")
        }

        addMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, "CD Track Number", R.drawable.ic_tracks,removable = true, tag = "cd_track_number")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            addMetadata(MediaMetadataRetriever.METADATA_KEY_COLOR_RANGE, "Color Range", R.drawable.ic_color,removable = true, tag = "color_range")
            addMetadata(MediaMetadataRetriever.METADATA_KEY_COLOR_STANDARD, "Color Standard", R.drawable.ic_color, removable = true, tag = "color_standard")
            addMetadata(MediaMetadataRetriever.METADATA_KEY_COLOR_TRANSFER, "Color Transfer", R.drawable.ic_color,removable = true, tag = "color_transfer")
        }

        addMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION, "Compilation", R.drawable.ic_compilation,removable = true, tag = "compilation")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER, "Composer", R.drawable.ic_composer,removable = true, tag = "composer")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_DATE, "Date", R.drawable.ic_date,removable = true, tag = "date")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, "Disc Number", R.drawable.ic_album,removable = true, tag = "disc_number")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE, "Genre", R.drawable.ic_genre,removable = true, tag = "genre")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE, "MIME Type", R.drawable.ic_file,removable = true, tag = "mime_type")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS, "Number of Tracks", R.drawable.ic_tracks,removable = true, tag = "num_tracks")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE, "Title", R.drawable.ic_title,removable = true, tag = "title")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR, "Year", R.drawable.ic_year,removable = true, tag = "year")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER, "Writer", R.drawable.ic_writer,removable = true, tag = "writer")

        // Video-specific metadata
        addMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH, "Video Width", R.drawable.ic_video,removable = true, tag = "video_width")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT, "Video Height", R.drawable.ic_video,removable = true, tag = "video_height")
        addMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION, "Video Rotation", R.drawable.ic_video,removable = true, tag = "video_rotation")

        // Image-specific metadata
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            addMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH, "Image Width", R.drawable.ic_image,removable = true, tag = "image_width")
            addMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT, "Image Height", R.drawable.ic_image,removable = true, tag = "image_height")
            addMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION, "Image Rotation", R.drawable.ic_image,removable = true, tag = "image_rotation")
        }

        // Boolean metadata
        fun addBooleanMetadata(key: Int, label: String, iconRes: Int,removable : Boolean , tag : String) {
            val value = retriever.extractMetadata(key)
            if (value != null) {
                metadataList.add(Metadata.Attribute(
                    label = Text(label),
                    icon = Image(iconRes),
                    primaryValue = Text(if (value == "yes") "Yes" else "No"),
                    removable = removable,
                    tag = tag
                ))
            }
        }

        addBooleanMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO, "Has Audio", R.drawable.ic_audio,removable = true, tag = "has_audio")
        addBooleanMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO, "Has Video", R.drawable.ic_video,removable = true, tag = "has_video")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            addBooleanMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE, "Has Image", R.drawable.ic_image,removable = true, tag = "has_image")
        }

        retriever.release()
        return metadataList
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/DocumentMetadataHandler.kt
================================================
package rocks.poopjournal.metadataremover.metadata.handlers

import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.PDDocumentInformation
import org.apache.poi.hssf.usermodel.HSSFWorkbook
import org.apache.poi.hwpf.HWPFDocument
import org.apache.poi.poifs.filesystem.OfficeXmlFileException
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.xwpf.usermodel.XWPFDocument
import org.odftoolkit.simple.Document
import org.odftoolkit.simple.SpreadsheetDocument
import org.odftoolkit.simple.TextDocument
import rocks.poopjournal.metadataremover.R
import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.Image
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.model.resources.MediaTypes
import rocks.poopjournal.metadataremover.model.resources.Text
import rocks.poopjournal.metadataremover.util.extensions.toCalendar
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import java.util.Optional

class DocumentMetadataHandler(private val context: Context) : MetadataHandler {

    override val readableMimeTypes = MediaTypes[MediaTypes.MICROSOFT_WORD] +
            MediaTypes[MediaTypes.OOXML_DOCUMENT] +
            MediaTypes[MediaTypes.MICROSOFT_EXCEL] +
            MediaTypes[MediaTypes.OOXML_SHEET] +
            MediaTypes[MediaTypes.OPENDOCUMENT_TEXT] +
            MediaTypes[MediaTypes.OPENDOCUMENT_SPREADSHEET] +
            MediaTypes[MediaTypes.PDF]

    override val writableMimeTypes = MediaTypes[MediaTypes.MICROSOFT_WORD] +
            MediaTypes[MediaTypes.OOXML_DOCUMENT] +
            MediaTypes[MediaTypes.MICROSOFT_EXCEL] +
            MediaTypes[MediaTypes.OOXML_SHEET] +
            MediaTypes[MediaTypes.OPENDOCUMENT_TEXT] +
            MediaTypes[MediaTypes.OPENDOCUMENT_SPREADSHEET] +
            MediaTypes[MediaTypes.PDF]


    override suspend fun loadMetadata(
        mediaType: MediaType,
        inputFile: File
    ): Metadata? {

        check(mediaType in readableMimeTypes)

        return Metadata(
            thumbnail = Image(inputFile),
            attributes = readDocumentMetadata(
                context,
                mediaType,
                inputFile.toUri()
            ).toSet()
        )
    }

    override suspend fun removeMetadata(
        mediaType: MediaType,
        inputFile: File,
        outputFile: File,
        attributes: List<Metadata.Attribute>
    ): Boolean {

        check(mediaType in writableMimeTypes)

        val selectedTags = attributes
            .filter { it.removable && it.selected }
            .mapNotNull { it.tag }

        val tagsToRemove =
            if (selectedTags.isEmpty()) null else selectedTags

        when (mediaType) {

            MediaTypes.OOXML_DOCUMENT ->
                removeOOXMLMetadata(inputFile, outputFile, MediaTypes.OOXML_DOCUMENT, tagsToRemove)

            MediaTypes.OOXML_SHEET ->
                removeOOXMLMetadata(inputFile, outputFile, MediaTypes.OOXML_SHEET, tagsToRemove)

            MediaTypes.MICROSOFT_WORD ->
                removeHWPFMetadata(inputFile, outputFile, tagsToRemove)

            MediaTypes.MICROSOFT_EXCEL ->
                removeHSSFMetadata(inputFile, outputFile, tagsToRemove)

            MediaTypes.OPENDOCUMENT_TEXT ->
                removeODTMetadata(inputFile, outputFile, tagsToRemove)

            MediaTypes.OPENDOCUMENT_SPREADSHEET ->
                removeODSMetadata(inputFile, outputFile, tagsToRemove)

            MediaTypes.PDF ->
                removePDFMetadata(inputFile, outputFile, tagsToRemove)
        }

        return true
    }

    private fun readDocumentMetadata(
        context: Context,
        mediaType: MediaType,
        uri: Uri
    ): List<Metadata.Attribute> {
        val metadataList = mutableListOf<Metadata.Attribute>()

        when (mediaType) {
            MediaTypes.OOXML_DOCUMENT -> readOOXMLMetadata(
                context,
                uri,
                MediaTypes.OOXML_DOCUMENT,
                metadataList
            )

            MediaTypes.OOXML_SHEET -> readOOXMLMetadata(
                context,
                uri,
                MediaTypes.OOXML_SHEET,
                metadataList
            )

            MediaTypes.MICROSOFT_WORD -> readHWPFMetadata(context, uri, metadataList)
            MediaTypes.MICROSOFT_EXCEL -> readHSSFMetadata(context, uri, metadataList)
            MediaTypes.OPENDOCUMENT_TEXT, MediaTypes.OPENDOCUMENT_SPREADSHEET -> readODFMetadata(
                context,
                uri,
                metadataList
            )

            MediaTypes.PDF -> readPDFMetadata(context, uri, metadataList)
            else -> metadataList.add(
                Metadata.Attribute(
                    label = Text("Unsupported file type"),
                    icon = Image(R.drawable.ic_error),
                    primaryValue = Text(mediaType.type)
                )
            )
        }

        return metadataList
    }

    //docx, xlsx
    private fun readOOXMLMetadata(
        context: Context,
        uri: Uri,
        mediaType: MediaType,
        metadataList: MutableList<Metadata.Attribute>
    ) {
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            val document = when (mediaType) {
                MediaTypes.OOXML_DOCUMENT -> XWPFDocument(inputStream)
                MediaTypes.OOXML_SHEET, MediaTypes.MICROSOFT_EXCEL -> XSSFWorkbook(inputStream)
                else -> throw IllegalArgumentException("Unsupported file type")
            }

            val properties = document.properties
            val coreProperties = properties.coreProperties
            val extendedProperties = properties.extendedProperties

            coreProperties.creator?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Author"),
                        icon = Image(R.drawable.ic_author),
                        primaryValue = Text(coreProperties.creator),
                        removable = true,
                        tag = "author"
                    )
                )
            }

            coreProperties.lastModifiedByUser?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Last Modified By"),
                        icon = Image(R.drawable.ic_edit),
                        primaryValue = Text(coreProperties.lastModifiedByUser),
                        removable = true,
                        tag = "lastModifiedByUser"
                    )
                )
            }


            coreProperties.description?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Description"),
                        icon = Image(R.drawable.ic_description),
                        primaryValue = Text(coreProperties.description),
                        removable = true,
                        tag = "description"
                    )
                )
            }

            coreProperties.subject?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Subject"),
                        icon = Image(R.drawable.ic_subject),
                        primaryValue = Text(coreProperties.subject),
                        removable = true,
                        tag = "subject"
                    )
                )
            }

            coreProperties.created?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Created"),
                        icon = Image(R.drawable.ic_calendar_today),
                        primaryValue = Text(convertDate(coreProperties.created)),
                        removable = true,
                        tag = "created"
                    )
                )
            }

            coreProperties.modified?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Modified"),
                        icon = Image(R.drawable.ic_update),
                        primaryValue = Text(convertDate(coreProperties.modified)),
                        removable = true,
                        tag = "modified"
                    )
                )
            }

            coreProperties.lastPrinted?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Last Printed"),
                        icon = Image(R.drawable.ic_update),
                        primaryValue = Text(convertDate(coreProperties.lastPrinted)),
                        removable = true,
                        tag = "lastPrinted"
                    )
                )
            }

            coreProperties.revision?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Revision"),
                        icon = Image(R.drawable.ic_history),
                        primaryValue = Text(coreProperties.revision),
                        removable = true,
                        tag = "revision"
                    )
                )
            }

            coreProperties.keywords?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Keywords"),
                        icon = Image(R.drawable.ic_label),
                        primaryValue = Text(coreProperties.keywords),
                        removable = true,
                        tag = "keywords"
                    )
                )
            }

            coreProperties.category?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Category"),
                        icon = Image(R.drawable.ic_category),
                        primaryValue = Text(coreProperties.category),
                        removable = true,
                        tag = "category"
                    )
                )
            }

            coreProperties.contentStatus?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Content Status"),
                        icon = Image(R.drawable.ic_info),
                        primaryValue = Text(coreProperties.contentStatus),
                        removable = true,
                        tag = "contentStatus"
                    )
                )
            }

            coreProperties.contentType?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Content Type"),
                        icon = Image(R.drawable.ic_file_type),
                        primaryValue = Text(coreProperties.contentType),
                        removable = true,
                        tag = "contentType"
                    )
                )
            }

            extendedProperties.company?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Company"),
                        icon = Image(R.drawable.ic_business),
                        primaryValue = Text(extendedProperties.company),
                        removable = true,
                        tag = "company"
                    )
                )
            }

            extendedProperties.manager?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Manager"),
                        icon = Image(R.drawable.ic_supervisor_account),
                        primaryValue = Text(extendedProperties.manager),
                        removable = true,
                        tag = "manager"
                    )
                )
            }

            document.close()
        }
    }

    private fun removeOOXMLMetadata(
        inputFile: File,
        outputFile: File,
        mediaType: MediaType,
        tags: List<String>?
    ) {

        val document =
            if (mediaType == MediaTypes.OOXML_DOCUMENT)
                XWPFDocument(FileInputStream(inputFile))
            else
                XSSFWorkbook(FileInputStream(inputFile))

        val core = document.properties.coreProperties
        val ext = document.properties.extendedProperties
        val date = getStartOfTimeDate()

        val removeAll = tags == null

        if (removeAll || "author" in tags) core.creator = ""
        if (removeAll || "lastModifiedBy" in tags) core.lastModifiedByUser = ""
        if (removeAll || "description" in tags) core.description = ""
        if (removeAll || "subject" in tags) core.setSubjectProperty("")
        if (removeAll || "created" in tags) core.setCreated(Optional.of(date))
        if (removeAll || "modified" in tags) core.setModified(Optional.of(date))
        if (removeAll || "keywords" in tags) core.keywords = ""
        if (removeAll || "category" in tags) core.category = ""
        if (removeAll || "company" in tags) ext.company = ""
        if (removeAll || "manager" in tags) ext.manager = ""

        FileOutputStream(outputFile).use {
            document.write(it)
        }

        document.close()
    }

    //Doc
    private fun readHWPFMetadata(
        context: Context,
        uri: Uri,
        metadataList: MutableList<Metadata.Attribute>
    ) {
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            val document = HWPFDocument(inputStream)
            val summaryInformation = document.summaryInformation
            val documentSummaryInformation = document.documentSummaryInformation

            summaryInformation.author?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Author"),
                        icon = Image(R.drawable.ic_person),
                        primaryValue = Text(summaryInformation.author),
                        removable = true,
                        tag = "author"
                    )
                )
            }

            summaryInformation.subject?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Subject"),
                        icon = Image(R.drawable.ic_subject),
                        primaryValue = Text(summaryInformation.subject),
                        removable = true,
                        tag = "subject"
                    )
                )
            }

            summaryInformation.keywords?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Keywords"),
                        icon = Image(R.drawable.ic_label),
                        primaryValue = Text(summaryInformation.keywords),
                        removable = true,
                        tag = "keywords"
                    )
                )
            }

            summaryInformation.comments?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Comments"),
                        icon = Image(R.drawable.ic_comment),
                        primaryValue = Text(summaryInformation.comments),
                        removable = true,
                        tag = "comments"
                    )
                )
            }

            summaryInformation.template?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Template"),
                        icon = Image(R.drawable.ic_description),
                        primaryValue = Text(summaryInformation.template),
                        removable = true,
                        tag = "template"
                    )
                )
            }

            summaryInformation.revNumber?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Revision Number"),
                        icon = Image(R.drawable.ic_history),
                        primaryValue = Text(summaryInformation.revNumber),
                        removable = true,
                        tag = "revNumber"
                    )
                )
            }

            summaryInformation.lastAuthor?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Last Author"),
                        icon = Image(R.drawable.ic_edit),
                        primaryValue = Text(summaryInformation.lastAuthor),
                        removable = true,
                        tag = "lastAuthor"
                    )
                )
            }

            summaryInformation.applicationName?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Application Name"),
                        icon = Image(R.drawable.ic_apps),
                        primaryValue = Text(summaryInformation.applicationName),
                        removable = true,
                        tag = "applicationName"
                    )
                )
            }

            summaryInformation.createDateTime?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Created Date"),
                        icon = Image(R.drawable.ic_calendar_today),
                        primaryValue = Text(convertDate(summaryInformation.createDateTime)),
                        removable = true,
                        tag = "createDateTime"
                    )
                )
            }

            summaryInformation.lastSaveDateTime?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Last Saved Date"),
                        icon = Image(R.drawable.ic_update),
                        primaryValue = Text(convertDate(summaryInformation.lastSaveDateTime)),
                        removable = true,
                        tag = "lastSaveDateTime"
                    )
                )
            }

            documentSummaryInformation.company?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Company"),
                        icon = Image(R.drawable.ic_business),
                        primaryValue = Text(documentSummaryInformation.company),
                        removable = true,
                        tag = "company"
                    )
                )
            }

            documentSummaryInformation.category?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Category"),
                        icon = Image(R.drawable.ic_category),
                        primaryValue = Text(documentSummaryInformation.category),
                        removable = true,
                        tag = "category"
                    )
                )
            }

            documentSummaryInformation.manager?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Manager"),
                        icon = Image(R.drawable.ic_supervisor_account),
                        primaryValue = Text(documentSummaryInformation.manager),
                        removable = true,
                        tag = "manager"
                    )
                )
            }

            document.close()
        }
    }

    private fun removeHWPFMetadata(inputFile: File, outputFile: File, tags: List<String>?) {
        val document = HWPFDocument(FileInputStream(inputFile))
        val summaryInformation = document.summaryInformation
        val documentSummaryInformation = document.documentSummaryInformation
        val date = getStartOfTimeDate()
        val removeAll = tags == null

       summaryInformation.title = ""
        if (removeAll || "author" in tags) summaryInformation.author = ""
        if (removeAll || "subject" in tags) summaryInformation.subject = ""
        if (removeAll || "keywords" in tags) summaryInformation.keywords = ""
        if (removeAll || "comments" in tags) summaryInformation.comments = ""
        if (removeAll || "template" in tags) summaryInformation.template = ""
        if (removeAll || "revNumber" in tags) summaryInformation.revNumber = ""
        if (removeAll || "lastAuthor" in tags) summaryInformation.lastAuthor = ""
        if (removeAll || "applicationName" in tags) summaryInformation.applicationName = ""
        if (removeAll || "createDateTime" in tags) summaryInformation.createDateTime = date
        if (removeAll || "lastSaveDateTime" in tags) summaryInformation.lastSaveDateTime = date
        if (removeAll || "company" in tags) documentSummaryInformation.company = ""
        if (removeAll || "category" in tags) documentSummaryInformation.category = ""
        if (removeAll || "manager" in tags) documentSummaryInformation.manager = ""


        FileOutputStream(outputFile).use { out -> document.write(out) }
        document.close()
    }

    //xls
    private fun readHSSFMetadata(
        context: Context,
        uri: Uri,
        metadataList: MutableList<Metadata.Attribute>
    ) {
        try {
            context.contentResolver.openInputStream(uri)?.use { inputStream ->
                val workbook = HSSFWorkbook(inputStream)
                val summaryInformation = workbook.summaryInformation
                val documentSummaryInformation = workbook.documentSummaryInformation

                summaryInformation.author?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Author"),
                            icon = Image(R.drawable.ic_person),
                            primaryValue = Text(summaryInformation.author),
                            removable = true,
                            tag = "author"
                        )
                    )
                }

                summaryInformation.lastAuthor?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Last Author"),
                            icon = Image(R.drawable.ic_edit),
                            primaryValue = Text(summaryInformation.lastAuthor),
                            removable = true,
                            tag = "lastAuthor"
                        )
                    )
                }

                summaryInformation.subject?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Subject"),
                            icon = Image(R.drawable.ic_subject),
                            primaryValue = Text(summaryInformation.subject),
                            removable = true,
                            tag = "subject"
                        )
                    )
                }

                summaryInformation.keywords?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Keywords"),
                            icon = Image(R.drawable.ic_label),
                            primaryValue = Text(summaryInformation.keywords),
                            removable = true,
                            tag = "keywords"
                        )
                    )
                }

                documentSummaryInformation.category?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Category"),
                            icon = Image(R.drawable.ic_category),
                            primaryValue = Text(documentSummaryInformation.category),
                            removable = true,
                            tag = "category"
                        )
                    )
                }

                summaryInformation.comments?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Comments"),
                            icon = Image(R.drawable.ic_comment),
                            primaryValue = Text(summaryInformation.comments),
                            removable = true,
                            tag = "comments"
                        )
                    )
                }

                summaryInformation.template?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Template"),
                            icon = Image(R.drawable.ic_description),
                            primaryValue = Text(summaryInformation.template),
                            removable = true,
                            tag = "template"
                        )
                    )
                }

                summaryInformation.revNumber?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Revision Number"),
                            icon = Image(R.drawable.ic_history),
                            primaryValue = Text(summaryInformation.revNumber),
                            removable = true,
                            tag = "revNumber"
                        )
                    )
                }

                summaryInformation.applicationName?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Application Name"),
                            icon = Image(R.drawable.ic_apps),
                            primaryValue = Text(summaryInformation.applicationName),
                            removable = true,
                            tag = "applicationName"
                        )
                    )
                }

                summaryInformation.createDateTime?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Created Date"),
                            icon = Image(R.drawable.ic_calendar_today),
                            primaryValue = Text(convertDate(summaryInformation.createDateTime)),
                            removable = true,
                            tag = "createDateTime"
                        )
                    )
                }

                summaryInformation.lastSaveDateTime?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Last Saved Date"),
                            icon = Image(R.drawable.ic_update),
                            primaryValue = Text(convertDate(summaryInformation.lastSaveDateTime)),
                            removable = true,
                            tag = "lastSaveDateTime"
                        )
                    )
                }

                summaryInformation.lastPrinted?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Last Printed"),
                            icon = Image(R.drawable.ic_print),
                            primaryValue = Text(convertDate(summaryInformation.lastPrinted)),
                            removable = true,
                            tag = "lastPrinted"
                        )
                    )
                }

                documentSummaryInformation.company?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Company"),
                            icon = Image(R.drawable.ic_business),
                            primaryValue = Text(documentSummaryInformation.company),
                            removable = true,
                            tag = "company"
                        )
                    )
                }

                documentSummaryInformation.manager?.takeIf { it.isNotBlank() }?.let {
                    metadataList.add(
                        Metadata.Attribute(
                            label = Text("Manager"),
                            icon = Image(R.drawable.ic_supervisor_account),
                            primaryValue = Text(documentSummaryInformation.manager),
                            removable = true,
                            tag = "manager"
                        )
                    )
                }

                workbook.close()
            }
        } catch (e: OfficeXmlFileException) {
            readOOXMLMetadata(context, uri, MediaTypes.MICROSOFT_EXCEL, metadataList)
        }
    }

    private fun removeHSSFMetadata(inputFile: File, outputFile: File, tags: List<String>?) {
        try {
            val workbook = HSSFWorkbook(FileInputStream(inputFile))
            val summaryInformation = workbook.summaryInformation
            val documentSummaryInformation = workbook.documentSummaryInformation
            val date = getStartOfTimeDate()
            val removeAll = tags == null

             summaryInformation.title = ""
            if(removeAll || "author" in tags) summaryInformation.author = ""
            if(removeAll || "lastAuthor" in tags) summaryInformation.lastAuthor = ""
            if(removeAll || "subject" in tags) summaryInformation.subject = ""
            if(removeAll || "keywords" in tags) summaryInformation.keywords = ""
            if(removeAll || "category" in tags) documentSummaryInformation.category = ""
            if(removeAll || "comments" in tags) summaryInformation.comments = ""
            if(removeAll || "template" in tags) summaryInformation.template = ""
            if(removeAll || "revNumber" in tags) summaryInformation.revNumber = ""
            if(removeAll || "applicationName" in tags)summaryInformation.applicationName = ""
            if(removeAll || "createDateTime" in tags)summaryInformation.createDateTime = date
            if(removeAll || "lastSaveDateTime" in tags) summaryInformation.lastSaveDateTime = date
            if(removeAll || "lastPrinted" in tags)summaryInformation.lastPrinted = date
            if(removeAll || "company" in tags)documentSummaryInformation.company = ""
            if(removeAll || "manager" in tags)documentSummaryInformation.manager = ""

            FileOutputStream(outputFile).use { out -> workbook.write(out) }
            workbook.close()
        } catch (e: OfficeXmlFileException) {
            removeOOXMLMetadata(inputFile, outputFile, MediaTypes.MICROSOFT_EXCEL,tags)
        }
    }

    //Open office
    private fun readODFMetadata(
        context: Context,
        uri: Uri,
        metadataList: MutableList<Metadata.Attribute>
    ) {
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            val document = Document.loadDocument(inputStream)
            val metadata = document.officeMetadata

            metadata.initialCreator?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Initial Creator"),
                        icon = Image(R.drawable.ic_person),
                        primaryValue = Text(metadata.initialCreator),
                        removable = true,
                        tag = "initialCreator"
                    )
                )
            }

            metadata.creator?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Creator"),
                        icon = Image(R.drawable.ic_edit),
                        primaryValue = Text(metadata.creator),
                        removable = true,
                        tag = "creator"
                    )
                )
            }

            metadata.editingCycles.toString().takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Editing Cycles"),
                        icon = Image(R.drawable.ic_loop),
                        primaryValue = Text(metadata.editingCycles.toString()),
                        removable = true,
                        tag = "editingCycles"
                    )
                )
            }

            metadata.printedBy?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Printed By"),
                        icon = Image(R.drawable.ic_print),
                        primaryValue = Text(metadata.printedBy),
                        removable = true,
                        tag = "printedBy"
                    )
                )
            }

            metadata.printDate?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Print Date"),
                        icon = Image(R.drawable.ic_print),
                        primaryValue = Text(convertDate(metadata.printDate.time)),
                        removable = true,
                        tag = "printDate"
                    )
                )
            }

            metadata.dcdate?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("DC Date"),
                        icon = Image(R.drawable.ic_update),
                        primaryValue = Text(convertDate(metadata.dcdate.time)),
                        removable = true,
                        tag = "dcdate"
                    )
                )
            }

            metadata.creationDate?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Created Date"),
                        icon = Image(R.drawable.ic_calendar_today),
                        primaryValue = Text(convertDate(metadata.creationDate.time)),
                        removable = true,
                        tag = "creationDate"
                    )
                )
            }

            metadata.language?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Language"),
                        icon = Image(R.drawable.ic_language),
                        primaryValue = Text(metadata.language),
                        removable = true,
                        tag = "language"
                    )
                )
            }

            metadata.keywords.toString().takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Keywords"),
                        icon = Image(R.drawable.ic_label),
                        primaryValue = Text(metadata.keywords.toString()),
                        removable = true,
                        tag = "keywords"
                    )
                )
            }

            metadata.subject?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Subject"),
                        icon = Image(R.drawable.ic_subject),
                        primaryValue = Text(metadata.subject),
                        removable = true,
                        tag = "subject"
                    )
                )
            }

            metadata.description?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Description"),
                        icon = Image(R.drawable.ic_description),
                        primaryValue = Text(metadata.description),
                        removable = true,
                        tag = "description"
                    )
                )
            }

            metadata.generator?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Generator"),
                        icon = Image(R.drawable.ic_build),
                        primaryValue = Text(metadata.generator),
                        removable = true,
                        tag = "generator"
                    )
                )
            }

            document.close()
        }
    }

    private fun removeODSMetadata(inputFile: File, outputFile: File,tags: List<String>?) {
        val spreadsheet = SpreadsheetDocument.loadDocument(inputFile)
        val metadata = spreadsheet.officeMetadata
        val date = getStartOfTimeDate()
        val removeAll = tags == null
        metadata.title = ""
        if(removeAll || "initialCreator" in tags)metadata.initialCreator = ""
        if(removeAll || "creator" in tags)metadata.creator = ""
        if(removeAll || "editingCycles" in tags)metadata.editingCycles = 0
        if(removeAll || "printedBy" in tags)metadata.printedBy = ""
        if(removeAll || "printDate" in tags)metadata.printDate = date.toCalendar()
        if(removeAll || "dcdate" in tags)metadata.dcdate = date.toCalendar()
        if(removeAll || "creationDate" in tags)metadata.creationDate = date.toCalendar()
        if(removeAll || "language" in tags)metadata.language = ""
        if(removeAll || "keywords" in tags)metadata.keywords = emptyList()
        if(removeAll || "subject" in tags)metadata.subject = ""
        if(removeAll || "description" in tags)metadata.description = ""
        if(removeAll || "generator" in tags)metadata.generator = ""

        spreadsheet.save(outputFile)
    }

    private fun removeODTMetadata(inputFile: File, outputFile: File,tags: List<String>?) {
        val document = TextDocument.loadDocument(inputFile)
        val metadata = document.officeMetadata
        val date = getStartOfTimeDate()
        val removeAll = tags == null
       metadata.title = ""
        if(removeAll || "initialCreator" in tags)metadata.initialCreator = ""
        if(removeAll || "creator" in tags)metadata.creator = ""
        if(removeAll || "editingCycles" in tags)metadata.editingCycles = 0
        if(removeAll || "printedBy" in tags)metadata.printedBy = ""
        if(removeAll || "printDate" in tags)metadata.printDate = date.toCalendar()
        if(removeAll || "dcdate" in tags)metadata.dcdate = date.toCalendar()
        if(removeAll || "creationDate" in tags)metadata.creationDate = date.toCalendar()
        if(removeAll || "language" in tags)metadata.language = ""
        if(removeAll || "keywords" in tags)metadata.keywords = emptyList()
        if(removeAll || "subject" in tags)metadata.subject = ""
        if(removeAll || "description" in tags)metadata.description = ""
        if(removeAll || "generator" in tags)metadata.generator = ""

        document.save(outputFile)
    }


    //PDF
    private fun readPDFMetadata(
        context: Context,
        uri: Uri,
        metadataList: MutableList<Metadata.Attribute>
    ) {
        context.contentResolver.openInputStream(uri)?.use { inputStream ->
            val document = PDDocument.load(inputStream)
            val info = document.documentInformation

            info.author?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Author"),
                        icon = Image(R.drawable.ic_person),
                        primaryValue = Text(info.author),
                        removable = true,
                        tag = "author"
                    )
                )
            }

            info.subject?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Subject"),
                        icon = Image(R.drawable.ic_subject),
                        primaryValue = Text(info.subject),
                        removable = true,
                        tag = "subject"
                    )
                )
            }

            info.keywords?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Keywords"),
                        icon = Image(R.drawable.ic_label),
                        primaryValue = Text(info.keywords),
                        removable = true,
                        tag = "keywords"
                    )
                )
            }

            info.creator?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Creator"),
                        icon = Image(R.drawable.ic_build),
                        primaryValue = Text(info.creator),
                        removable = true,
                        tag = "creator"
                    )
                )
            }

            info.producer?.takeIf { it.isNotBlank() }?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Producer"),
                        icon = Image(R.drawable.ic_apps),
                        primaryValue = Text(info.producer),
                        removable = true,
                        tag = "producer"
                    )
                )
            }

            info.creationDate?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Created Date"),
                        icon = Image(R.drawable.ic_calendar_today),
                        primaryValue = Text(convertDate(info.creationDate.time)),
                        removable = true,
                        tag = "creationDate"
                    )
                )
            }

            info.modificationDate?.let {
                metadataList.add(
                    Metadata.Attribute(
                        label = Text("Modification Date"),
                        icon = Image(R.drawable.ic_update),
                        primaryValue = Text(convertDate(info.modificationDate.time)),
                        removable = true,
                        tag = "modificationDate"
                    )
                )
            }

            document.close()
        }
    }

    private fun removePDFMetadata(inputFile: File, outputFile: File,tags: List<String>?) {
        FileInputStream(inputFile).use { inputStream ->
            PDDocument.load(inputStream).use { document ->
                val info: PDDocumentInformation = document.documentInformation
                val removeAll = tags == null
                info.title = ""
                if (removeAll || "author" in tags) info.author = ""
                if (removeAll || "subject" in tags) info.subject = ""
                if (removeAll || "keywords" in tags) info.keywords = ""
                if (removeAll || "creator" in tags) info.creator = ""
                if (removeAll || "producer" in tags) info.producer = ""
                if (removeAll || "createdDate" in tags) info.creationDate = null
                if (removeAll || "modificationDate" in tags) info.modificationDate = null

                FileOutputStream(outputFile).use { outputStream ->
                    document.save(outputStream)
                }
            }
        }
    }

    private fun convertDate(date: Date): String {
        val formatter = SimpleDateFormat("dd MMM yyyy, HH:mm:ss", Locale.getDefault())
        return formatter.format(date)
    }

    private fun getStartOfTimeDate(): Date {
        val calendar = GregorianCalendar(1970, 0, 1)
        return calendar.time
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/DrewMetadataReader.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.metadata.handlers

import com.drew.metadata.Directory
import com.drew.metadata.adobe.AdobeJpegDirectory
import com.drew.metadata.avi.AviDirectory
import com.drew.metadata.bmp.BmpHeaderDirectory
import com.drew.metadata.eps.EpsDirectory
import com.drew.metadata.exif.*
import com.drew.metadata.file.FileSystemDirectory
import com.drew.metadata.file.FileTypeDirectory
import com.drew.metadata.gif.*
import com.drew.metadata.icc.IccDirectory
import com.drew.metadata.ico.IcoDirectory
import com.drew.metadata.iptc.IptcDirectory
import com.drew.metadata.jfif.JfifDirectory
import com.drew.metadata.jfxx.JfxxDirectory
import com.drew.metadata.jpeg.HuffmanTablesDirectory
import com.drew.metadata.jpeg.JpegCommentDirectory
import com.drew.metadata.jpeg.JpegDirectory
import com.drew.metadata.mov.QuickTimeDirectory
import com.drew.metadata.mp4.Mp4Directory
import com.drew.metadata.pcx.PcxDirectory
import com.drew.metadata.photoshop.PhotoshopDirectory
import com.drew.metadata.photoshop.PsdHeaderDirectory
import com.drew.metadata.png.PngDirectory
import com.drew.metadata.wav.WavDirectory
import com.drew.metadata.webp.WebpDirectory
import com.drew.metadata.xmp.XmpDirectory
import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataReader
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.model.resources.MediaTypes
import java.io.File
import com.drew.imaging.ImageMetadataReader as DrewImageMetadataReader
import com.drew.metadata.Metadata as DrewMetadata

object DrewMetadataReader : MetadataReader {

    override val readableMimeTypes = MediaTypes[MediaTypes.JPEG] +
            MediaTypes[MediaTypes.AVI_VIDEO] + MediaTypes[MediaTypes.BMP] +
            MediaTypes[MediaTypes.POSTSCRIPT] + MediaTypes[MediaTypes.JPEG] +
            MediaTypes[MediaTypes.DNG] + MediaTypes[MediaTypes.CR2] +
            MediaTypes[MediaTypes.NEF] + MediaTypes[MediaTypes.NRW] +
            MediaTypes[MediaTypes.ARW] + MediaTypes[MediaTypes.RW2] +
            MediaTypes[MediaTypes.ORF] + MediaTypes[MediaTypes.PEF] +
            MediaTypes[MediaTypes.SRW] + MediaTypes[MediaTypes.RAF] +
            MediaTypes[MediaTypes.GIF] + MediaTypes[MediaTypes.ICC] +
            MediaTypes[MediaTypes.ICO] + MediaTypes[MediaTypes.QUICKTIME] +
            MediaTypes[MediaTypes.MP4_VIDEO] + MediaTypes[MediaTypes.PCX] +
            MediaTypes[MediaTypes.PSD] + MediaTypes[MediaTypes.PNG] +
            MediaTypes[MediaTypes.WAV_AUDIO] + MediaTypes[MediaTypes.WEBP]

    override suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata? {
        check(mediaType in readableMimeTypes)

        val metadata = DrewImageMetadataReader.readMetadata(inputFile)

        val adobeJpegDirectory = metadata.getFirstDirectoryOfType<AdobeJpegDirectory>()
        adobeJpegDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val aviDirectory = metadata.getFirstDirectoryOfType<AviDirectory>()
        aviDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val bmpHeaderDirectory = metadata.getFirstDirectoryOfType<BmpHeaderDirectory>()
        bmpHeaderDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val epsDirectory = metadata.getFirstDirectoryOfType<EpsDirectory>()
        epsDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val exifIFD0Directory = metadata.getFirstDirectoryOfType<ExifIFD0Directory>()
        exifIFD0Directory?.let { directory ->
            TODO("Read directory $directory")
        }
        val exifImageDirectory = metadata.getFirstDirectoryOfType<ExifImageDirectory>()
        exifImageDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val exifInteropDirectory = metadata.getFirstDirectoryOfType<ExifInteropDirectory>()
        exifInteropDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val exifSubIFDDirectory = metadata.getFirstDirectoryOfType<ExifSubIFDDirectory>()
        exifSubIFDDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val exifThumbnailDirectory = metadata.getFirstDirectoryOfType<ExifThumbnailDirectory>()
        exifThumbnailDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val gpsDirectory = metadata.getFirstDirectoryOfType<GpsDirectory>()
        gpsDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val panasonicRawDistortionDirectory = metadata.getFirstDirectoryOfType<PanasonicRawDistortionDirectory>()
        panasonicRawDistortionDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val panasonicRawIFD0Directory = metadata.getFirstDirectoryOfType<PanasonicRawIFD0Directory>()
        panasonicRawIFD0Directory?.let { directory ->
            TODO("Read directory $directory")
        }
        val panasonicRawWbInfo2Directory = metadata.getFirstDirectoryOfType<PanasonicRawWbInfo2Directory>()
        panasonicRawWbInfo2Directory?.let { directory ->
            TODO("Read directory $directory")
        }
        val panasonicRawWbInfoDirectory = metadata.getFirstDirectoryOfType<PanasonicRawWbInfoDirectory>()
        panasonicRawWbInfoDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val printIMDirectory = metadata.getFirstDirectoryOfType<PrintIMDirectory>()
        printIMDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val fileSystemDirectory = metadata.getFirstDirectoryOfType<FileSystemDirectory>()
        fileSystemDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val fileTypeDirectory = metadata.getFirstDirectoryOfType<FileTypeDirectory>()
        fileTypeDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val gifAnimationDirectory = metadata.getFirstDirectoryOfType<GifAnimationDirectory>()
        gifAnimationDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val gifCommentDirectory = metadata.getFirstDirectoryOfType<GifCommentDirectory>()
        gifCommentDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val gifControlDirectory = metadata.getFirstDirectoryOfType<GifControlDirectory>()
        gifControlDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val gifHeaderDirectory = metadata.getFirstDirectoryOfType<GifHeaderDirectory>()
        gifHeaderDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val gifImageDirectory = metadata.getFirstDirectoryOfType<GifImageDirectory>()
        gifImageDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val iccDirectory = metadata.getFirstDirectoryOfType<IccDirectory>()
        iccDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val icoDirectory = metadata.getFirstDirectoryOfType<IcoDirectory>()
        icoDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val iptcDirectory = metadata.getFirstDirectoryOfType<IptcDirectory>()
        iptcDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val jfifDirectory = metadata.getFirstDirectoryOfType<JfifDirectory>()
        jfifDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val jfxxDirectory = metadata.getFirstDirectoryOfType<JfxxDirectory>()
        jfxxDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val huffmanTablesDirectory = metadata.getFirstDirectoryOfType<HuffmanTablesDirectory>()
        huffmanTablesDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val jpegCommentDirectory = metadata.getFirstDirectoryOfType<JpegCommentDirectory>()
        jpegCommentDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val jpegDirectory = metadata.getFirstDirectoryOfType<JpegDirectory>()
        jpegDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val quickTimeDirectory = metadata.getFirstDirectoryOfType<QuickTimeDirectory>()
        quickTimeDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val mp4Directory = metadata.getFirstDirectoryOfType<Mp4Directory>()
        mp4Directory?.let { directory ->
            TODO("Read directory $directory")
        }
        val pcxDirectory = metadata.getFirstDirectoryOfType<PcxDirectory>()
        pcxDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val photoshopDirectory = metadata.getFirstDirectoryOfType<PhotoshopDirectory>()
        photoshopDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val psdHeaderDirectory = metadata.getFirstDirectoryOfType<PsdHeaderDirectory>()
        psdHeaderDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val pngDirectory = metadata.getFirstDirectoryOfType<PngDirectory>()
        pngDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val wavDirectory = metadata.getFirstDirectoryOfType<WavDirectory>()
        wavDirectory?.let { directory ->
            TODO("Read directory $directory")
        }
        val webpDirectory = metadata.getFirstDirectoryOfType<WebpDirectory>()
        webpDirectory?.let { directory ->
            directory.getBoolean(WebpDirectory.TAG_HAS_ALPHA)
            directory.getInt(WebpDirectory.TAG_IMAGE_HEIGHT)
            directory.getInt(WebpDirectory.TAG_IMAGE_WIDTH)
            directory.getBoolean(WebpDirectory.TAG_IS_ANIMATION)
            TODO("Read directory $directory")
        }
        val xmpDirectory = metadata.getFirstDirectoryOfType<XmpDirectory>()
        xmpDirectory?.let { directory ->
            directory.xmpMeta
            directory.xmpProperties
            TODO("Read directory $directory")
        }

        return null
    }

    private inline fun <reified T : Directory> DrewMetadata.getFirstDirectoryOfType(): T? =
            getFirstDirectoryOfType(T::class.java)

    private inline fun <reified T : Directory> DrewMetadata.getDirectoriesOfType(): Collection<T> =
            getDirectoriesOfType(T::class.java)
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/ExifMetadataHandler.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.metadata.handlers

import android.content.Context
import android.location.Geocoder
import androidx.exifinterface.media.ExifInterface
import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.Image
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.model.resources.MediaTypes
import rocks.poopjournal.metadataremover.util.ImageFile
import rocks.poopjournal.metadataremover.util.extensions.android.*
import rocks.poopjournal.metadataremover.util.extensions.deleteIfExists
import java.io.File
import java.util.*

class ExifMetadataHandler(context: Context? = null) : MetadataHandler {

    private val geocoder = context?.let {
        Geocoder(context, context.defaultLocale ?: Locale.getDefault())
    }

    override val writableMimeTypes = MediaTypes[MediaTypes.JPEG]

    override val readableMimeTypes =
        MediaTypes[MediaTypes.JPEG] +
                MediaTypes[MediaTypes.DNG] + MediaTypes[MediaTypes.CR2] +
                MediaTypes[MediaTypes.NEF] + MediaTypes[MediaTypes.NRW] +
                MediaTypes[MediaTypes.ARW] + MediaTypes[MediaTypes.RW2] +
                MediaTypes[MediaTypes.ORF] + MediaTypes[MediaTypes.PEF] +
                MediaTypes[MediaTypes.SRW] + MediaTypes[MediaTypes.RAF]

    override suspend fun loadMetadata(
        mediaType: MediaType,
        inputFile: File
    ): Metadata? {

        check(mediaType in readableMimeTypes)

        val exif = ExifInterface(inputFile.inputStream())

        val thumbnail =
            exif.thumbnailBitmap
                ?.takeIf { it.width * it.height > 1_000_000 }
                ?.let { Image(it) }
                ?: Image(inputFile)

        val imageFile = ImageFile(inputFile)

        val attributes = mutableListOf<Metadata.Attribute>()

        exif.creationAttribute?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_DATETIME
                )
            )
        }



        imageFile.resolutionAttribute?.let {
            attributes.add(
                it.copy(removable = true, tag = ExifInterface.TAG_LENS_MODEL)
            )
        }

        exif.exposureAttribute?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_EXPOSURE_TIME
                )
            )
        }


        exif.getLocationAttribute(geocoder)?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_GPS_LATITUDE
                )
            )
        }

        exif.cameraAttribute?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_MODEL
                )
            )
        }

        exif.lensAttribute?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_LENS_MODEL
                )
            )
        }

        exif.lightAttribute?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_FLASH
                )
            )
        }

        exif.ownerAttribute?.let {
            attributes.add(
                it.copy(
                    removable = true,
                    tag = ExifInterface.TAG_ARTIST
                )
            )
        }

        return Metadata(
            thumbnail = thumbnail,
            attributes = attributes.toSet()
        )
    }

    override suspend fun removeMetadata(
        mediaType: MediaType,
        inputFile: File,
        outputFile: File,
        attributes: List<Metadata.Attribute>
    ): Boolean {

        check(mediaType in writableMimeTypes)

        // Delete previous output
        outputFile.deleteIfExists()

        // Copy original to output
        inputFile.copyTo(outputFile)

        val exif = ExifInterface(outputFile.path)

        // Collect selected removable tags
        val tagsToRemove = attributes
            .filter { it.removable && it.selected }
            .mapNotNull { it.tag }

        if (tagsToRemove.isEmpty()) {
            // If nothing selected → remove everything
            exif.clearAllAttributes()
        } else {

            tagsToRemove.forEach { tag ->
                exif.setAttribute(tag, null)
            }
        }

        exif.saveAttributes()

        return true
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/FirstMatchMetadataHandler.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.metadata.handlers

import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.util.extensions.asDeque
import java.io.File
import java.util.*

class FirstMatchMetadataHandler(
        private val handlers: Queue<MetadataHandler>
) : MetadataHandler {

    constructor(vararg handlers: MetadataHandler) : this(handlers.asDeque())

    override val readableMimeTypes: Set<MediaType> = handlers
            .flatMapTo(mutableSetOf()) { handler ->
                handler.readableMimeTypes
            }
    override val writableMimeTypes: Set<MediaType> = handlers
            .flatMapTo(mutableSetOf()) { handler ->
                handler.writableMimeTypes
            }

    private fun findMetadataHandler(mediaType: MediaType): MetadataHandler? {
        return handlers
                .find { fileHandler ->
                    mediaType in fileHandler.readableMimeTypes
                }
    }

    override suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata? {
        return findMetadataHandler(mediaType)
                ?.loadMetadata(mediaType, inputFile)
    }

    override suspend fun removeMetadata(
        mediaType: MediaType,
        inputFile: File,
        outputFile: File,
        attributes: List<Metadata.Attribute>
    ): Boolean {

        val handler = findMetadataHandler(mediaType) ?: return false

        return handler.removeMetadata(
            mediaType,
            inputFile,
            outputFile,
            attributes
        )
    }


}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/NopMetadataHandler.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.metadata.handlers

import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.model.resources.MediaTypes
import java.io.File

object NopMetadataHandler : MetadataHandler {

    override val readableMimeTypes = MediaTypes[MediaTypes.ANY]
    override val writableMimeTypes = MediaTypes[MediaTypes.ANY]

    override suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata? = null

    override suspend fun removeMetadata(
            mediaType: MediaType,
            inputFile: File,
            outputFile: File,
            attributes: List<Metadata.Attribute>
    ) = false
}



================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/PngMetadataHandler.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.metadata.handlers

import ar.com.hjg.pngj.PngReader
import ar.com.hjg.pngj.PngWriter
import ar.com.hjg.pngj.chunks.ChunkCopyBehaviour
import ar.com.hjg.pngj.chunks.PngChunkTextVar
import rocks.poopjournal.metadataremover.model.metadata.Metadata
import rocks.poopjournal.metadataremover.model.metadata.MetadataHandler
import rocks.poopjournal.metadataremover.model.resources.Image
import rocks.poopjournal.metadataremover.model.resources.MediaType
import rocks.poopjournal.metadataremover.model.resources.MediaTypes
import rocks.poopjournal.metadataremover.model.resources.Text
import rocks.poopjournal.metadataremover.util.ImageFile
import rocks.poopjournal.metadataremover.util.extensions.pngj.*
import java.io.File

object PngMetadataHandler : MetadataHandler {
    override val readableMimeTypes = MediaTypes[MediaTypes.PNG]
    override val writableMimeTypes = MediaTypes[MediaTypes.PNG]

    override suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata? {
        check(mediaType in readableMimeTypes)

        val metadata = PngReader(inputFile).metadata
        val imageFile = ImageFile(inputFile)

        val attributes = listOfNotNull(
            metadata.creationAttribute,
            metadata.lastModifiedAttribute,
            imageFile.resolutionAttribute,
            metadata.authorAttribute,
            metadata.sourceAttribute,
            metadata.commentAttribute,
            metadata.warningAttribute
        )

        return Metadata(
            title = metadata
                .getTxtForKey(PngChunkTextVar.KEY_Title)
                ?.takeIf(String::isNotBlank)
                ?.let { Text(it) },
            thumbnail = Image(inputFile),
            attributes = attributes.toSet()
        )
    }

    override suspend fun removeMetadata(
        mediaType: MediaType,
        inputFile: File,
        outputFile: File,
        attributes: List<Metadata.Attribute>
    ): Boolean {
        check(mediaType in writableMimeTypes)

        val selectedKeys = attributes
            .filter { it.removable && it.selected }
            .mapNotNull { it.tag }

        val removeAll = selectedKeys.isEmpty()

        usePngTransforming(inputFile, outputFile, true) { reader, writer ->

            writer.copyChunksFrom(reader.chunksList, ChunkCopyBehaviour.COPY_ALL_SAFE)

            writer.metadata.apply {
                if (removeAll) {
                    // Reset all metadata
                    setTimeYMDHMS(0, 0, 0, 0, 0, 0)
                    setText(PngChunkTextVar.KEY_Title, "")
                    setText(PngChunkTextVar.KEY_Author, "")
                    setText(PngChunkTextVar.KEY_Description, "")
                    setText(PngChunkTextVar.KEY_Copyright, "")
                    setText(PngChunkTextVar.KEY_Creation_Time, "")
                    setText(PngChunkTextVar.KEY_Software, "")
                    setText(PngChunkTextVar.KEY_Disclaimer, "")
                    setText(PngChunkTextVar.KEY_Warning, "")
                    setText(PngChunkTextVar.KEY_Source, "")
                    setText(PngChunkTextVar.KEY_Comment, "")
                } else {
                    // Reset only selected metadata
                    selectedKeys.forEach { key ->
                        when (key) {
                            "title" -> setText(PngChunkTextVar.KEY_Title, "")
                            "author" -> setText(PngChunkTextVar.KEY_Author, "")
                            "description" -> setText(PngChunkTextVar.KEY_Description, "")
                            "copyright" -> setText(PngChunkTextVar.KEY_Copyright, "")
                            "creation_time" -> setText(PngChunkTextVar.KEY_Creation_Time, "")
                            "software" -> setText(PngChunkTextVar.KEY_Software, "")
                            "disclaimer" -> setText(PngChunkTextVar.KEY_Disclaimer, "")
                            "warning" -> setText(PngChunkTextVar.KEY_Warning, "")
                            "source" -> setText(PngChunkTextVar.KEY_Source, "")
                            "comment" -> setText(PngChunkTextVar.KEY_Comment, "")
                        }
                    }
                }
            }

            reader.copyRowsTo(writer)
        }

        return true
    }

    private fun <R> usePngTransforming(
        inputFile: File,
        outputFile: File,
        allowOverwrite: Boolean = true,
        block: (PngReader, PngWriter) -> R
    ): R {
        val reader = PngReader(inputFile)
        val writer = PngWriter(outputFile, reader.imgInfo, allowOverwrite)

        var exception: Throwable? = null
        try {
            return block(reader, writer)
        } catch (throwable: Throwable) {
            exception = throwable
            throw throwable
        } finally {
            when (exception) {
                null -> {
                    reader.end()
                    writer.end()
                }
                else -> {
                    try {
                        reader.end()
                        writer.end()
                    } catch (closeException: Throwable) {
                        exception.addSuppressed(closeException)
                    }
                }
            }
        }
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/coordinates/Coordinates.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.coordinates

import androidx.annotation.StringRes
import rocks.poopjournal.metadataremover.R
import rocks.poopjournal.metadataremover.model.resources.Text
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min

sealed class Coordinate(value: Double, range: ClosedRange<Double>) {

    private val min: Double = min(range.start, range.endInclusive)
    private val max: Double = max(range.start, range.endInclusive)
    private val interval: Double = (max - min).absoluteValue

    val value: Double = value.normalize()
    private val absoluteValue: Double = this.value.absoluteValue

    @get:StringRes
    protected abstract val directionRes: Int
    val direction get() = Text(directionRes)

    val hours: Long = absoluteValue.toLong()
    val minutes: Long = ((absoluteValue - hours) * 60).toLong()
    val seconds: Double = ((absoluteValue - hours) * 60 - minutes) * 60

    private fun Double.normalize(): Double {
        return when {
            this < min -> (this + interval).normalize()
            this > max -> (this - interval).normalize()
            else -> this
        }
    }
}

class Latitude(value: Double) : Coordinate(value, -180.0..180.0) {

    constructor(
            hours: Int,
            minutes: Int,
            seconds: Double
    ) : this((seconds / 60 + minutes) / 60 + hours)

    @StringRes
    override val directionRes =
            if (this.value < 0) R.string.description_attribute_file_creation_location_direction_west
            else R.string.description_attribute_file_creation_location_direction_east

    override fun toString(): String {
        val direction = if (value < 0) "West" else "East"
        return "${hours.absoluteValue}  $minutes' $seconds\" $direction"
    }
}

class Longitude(value: Double) : Coordinate(value, -90.0..90.0) {

    constructor(
            hours: Int,
            minutes: Int,
            seconds: Double
    ) : this((seconds / 60 + minutes) / 60 + hours)

    @StringRes
    override val directionRes =
            if (this.value < 0) R.string.description_attribute_file_creation_location_direction_south
            else R.string.description_attribute_file_creation_location_direction_north

    override fun toString(): String {
        val direction = if (value < 0) "South" else "North"
        return "${hours.absoluteValue}  $minutes' $seconds\" $direction"
    }
}

data class Coordinates(
        val latitude: Latitude,
        val longitude: Longitude
)


================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/ClearedFile.kt
================================================
package rocks.poopjournal.metadataremover.model.metadata

import android.net.Uri

data class ClearedFile (
    val uri: Uri,
    val name: String
)

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/Metadata.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.metadata

import rocks.poopjournal.metadataremover.model.resources.Image
import rocks.poopjournal.metadataremover.model.resources.Text

/**
 * Wrapper around a file with metadata.
 */
data class Metadata(

        /**
         * A title different from the file name.
         * This could be the document title for files that aren't necessarily named after their
         * title.
         *
         * Note: This property should only be used for files that have a dedicated title,
         * files that don't have a title other than the filename should not override this property.
         */
        val title: Text? = null,

        /**
         * An image thumbnail of this file.
         */
        val thumbnail: Image,

        /**
         * The metadata stored in this file.
         */
        val attributes: Set<Attribute>

) {
    /**
     * A single metadata attribute (e.g. file creation date).
     */
    data class Attribute(
            /**
             * Label describing this attribute's category (e.g. "Date").
             */
            val label: Text,

            /**
             * Icon describing this attribute's category.
             */
            val icon: Image,

            /**
             * The attribute's primary value (e.g. "October 12, 2017").
             */
            val primaryValue: Text,

            /**
             * The attribute's optional secondary value (e.g. "11:21 AM").
             */
            val secondaryValue: Text? = null,

            /**
             * Whether this metadata attribute can be removed individually.
             */
            val removable: Boolean = false,

            /**
             * Underlying metadata tag (EXIF tag etc.)
             */
            val tag: String? = null,

            /**
             * UI checkbox state
             */
            var selected: Boolean = false
    )
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/MetadataHandler.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.metadata

interface  MetadataHandler : MetadataReader, MetadataWriter

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/MetadataReader.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.metadata

import rocks.poopjournal.metadataremover.model.resources.MediaType
import java.io.File

interface MetadataReader {
    val readableMimeTypes: Set<MediaType>

    /**
     * Load the metadata from the [input file][inputFile].
     *
     * Note: The metadata's attributes should explicitly not contain any attribute
     * that is obvious to the file system, e.g. the file name, last modified date, or file size.
     * Instead a [MetadataHandler] should be as specific to it's [readableMimeTypes] as it can.
     */
    suspend fun loadMetadata(mediaType: MediaType, inputFile: File): Metadata?
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/MetadataWriter.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.metadata

import rocks.poopjournal.metadataremover.model.resources.MediaType
import java.io.File

interface MetadataWriter {
    val writableMimeTypes: Set<MediaType>

    /**
     * Remove the metadata from the [input file][inputFile] and save a copy
     * without any contained metadata to the [output file][outputFile].
     *
     * Note: Depending on the file type this process might cause quality loss (e.g. for JPEG's).
     *
     * @return `true` if the removal was successful, `false` otherwise.
     */
    suspend fun removeMetadata(
        mediaType: MediaType,
        inputFile: File,
        outputFile: File,
        attributes: List<Metadata.Attribute>
    ): Boolean
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/resources/Image.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.resources

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.annotation.IntRange
import androidx.core.net.toUri
import com.bumptech.glide.Glide
import rocks.poopjournal.metadataremover.model.resources.Image.Type.*
import rocks.poopjournal.metadataremover.util.extensions.parseUri
import java.io.File

/**
 * An umbrella container for several graphics representations, including [Bitmap]s,
 * compressed [Bitmap] images (e.g. JPG or PNG)
 * and [drawable resources][DrawableRes] (including vectors).
 *
 * This class is a simplified copy of [android.graphics.drawable.Icon] which is released under
 * the Apache License 2.0.
 */
class Image private constructor(
        /**
         * This image's type as in [Text.Type].
         */
        private val type: Type,

        /**
         * [bitmap], [bytes], [uri] or `null`, depending on the [type].
         */
        private val object1: Any? = null,

        /**
         * [resId], [bytesStartOffset] or `null`, depending on the [type].
         */
        private val int1: Int? = null,

        /**
         * [bytesLength] or `null`, depending on the [type].
         */
        private val int2: Int? = null
) {
    /**
     * Image source type.
     */
    enum class Type {
        BITMAP,
        RESOURCE,
        BYTES,
        URI,
        ADAPTIVE_BITMAP
    }

    /**
     * Throws an exception if this image's type is not [allowed][allowedTypes].
     */
    private fun checkType(vararg allowedTypes: Type) {
        if (type !in allowedTypes) {
            throw IllegalStateException("The image type was expected to be " +
                    "one of ${allowedTypes.joinToString()} but was $type instead.")
        }
    }

    /**
     * The image's [Bitmap] source.
     *
     * Note: This property is only available to [BITMAP] and [ADAPTIVE_BITMAP] images.
     */
    private val bitmap: Bitmap
        get() {
            checkType(BITMAP, ADAPTIVE_BITMAP)
            return object1 as Bitmap
        }

    /**
     * The image's [DrawableRes] ID source.
     *
     * Note: This property is only available to [RESOURCE] images.
     */
    private val resId: Int
        @DrawableRes get() {
            checkType(RESOURCE)
            return int1 as Int
        }

    /**
     * The image's [ByteArray] source.
     *
     * Note: This property is only available to [BYTES] images.
     */
    private val bytes: ByteArray
        get() {
            checkType(BYTES)
            return object1 as ByteArray
        }
    /**
     * The [bytes]'s start offset.
     *
     * Note: This property is only available to [BYTES] images.
     */
    private val bytesStartOffset: Int
        @IntRange(from = 0) get() {
            checkType(BYTES)
            return int1 ?: 0
        }
    /**
     * The [bytes]'s length.
     *
     * Note: This property is only available to [BYTES] images.
     */
    private val bytesLength: Int
        @IntRange(from = 0) get() {
            checkType(BYTES)
            return int2 ?: (bytes.size - bytesStartOffset)
        }

    /**
     * The image's [Uri] source.
     *
     * Note: This property is only available to [URI] images.
     */
    private val uri: Uri
        get() {
            checkType(URI)
            return object1 as Uri
        }


    /**
     * Create an [Image] pointing to a bitmap in memory.
     *
     * @param bitmap A valid [Bitmap] object.
     * @param adaptive Whether or not the bitmap follows the icon design guideline defined
     * by [AdaptiveIconDrawable].
     */
    constructor(bitmap: Bitmap, adaptive: Boolean = false) : this(
            type = if (adaptive) ADAPTIVE_BITMAP else BITMAP,
            object1 = bitmap
    )

    /**
     * Create an [Image] pointing to a drawable resource.
     *
     * @param resId ID of the drawable resource.
     */
    constructor(@DrawableRes resId: Int) : this(
            type = RESOURCE,
            int1 = resId
    )

    /**
     * Create an [Image] pointing to a compressed bitmap stored in a byte array.
     *
     * @param bytes Byte array storing compressed bitmap data of a type that [BitmapFactory]
     * can decode (see [Bitmap.CompressFormat]).
     * @param startOffset Offset into [bytes] at which the bitmap data starts
     * or `null` if no offset is specified aka. the bitmap data starts at the first position.
     * @param length Length of the bitmap data or `null` if no length is specified aka. the
     * bitmap data continues to the end of the array.
     */
    constructor(bytes: ByteArray, @IntRange(from = 0) startOffset: Int? = null, @IntRange(from = 0) length: Int? = null) : this(
            type = BYTES,
            object1 = bytes,
            int1 = startOffset,
            int2 = length
    )

    /**
     * Create an [Image] pointing to an image file specified by [Uri].
     *
     * @param uri A URI referring to local `content://` or `file://` image data.
     */
    constructor(uri: Uri) : this(
            type = URI,
            object1 = uri
    )

    /**
     * Create an [Image] pointing to an image file specified by [Uri].
     *
     * @param uri A URI referring to local `content://` or `file://` image data.
     */
    constructor(uri: String) : this(uri.parseUri())

    /**
     * Create an [Image] pointing to an image [File].
     *
     * @param file A [File] containing image data.
     */
    constructor(file: File) : this(file.toUri())

    /**
     * Load a [Drawable] containing this image.
     *
     * @return The [Drawable] containing this image or `null` if this image could not be loaded.
     */
    fun load(view: ImageView) {
        when (type) {
            BITMAP -> {
                Glide.with(view)
                        .load(bitmap)
                        .into(view)
            }
            ADAPTIVE_BITMAP -> {
                if (VERSION.SDK_INT >= VERSION_CODES.O) {
                    val drawable = AdaptiveIconDrawable(
                            null,
                            BitmapDrawable(view.resources, bitmap)
                    )
                    Glide.with(view)
                            .load(drawable)
                            .into(view)
                }
            }
            RESOURCE -> {
                Glide.with(view)
                        .load(resId)
                        .into(view)
            }
            BYTES -> {
                val drawable = BitmapDrawable(
                        view.resources,
                        BitmapFactory.decodeByteArray(bytes, bytesStartOffset, bytesLength)
                )
                Glide.with(view)
                        .load(drawable)
                        .into(view)
            }
            URI -> {
                Glide.with(view)
                        .load(uri)
                        .into(view)
            }
        }
    }

    operator fun invoke(view: ImageView) = load(view)

    companion object {
        private val EMPTY_IMAGE = Image(android.R.color.transparent)
        fun emptyImage() = EMPTY_IMAGE
    }
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/resources/MediaType.kt
================================================
/*
 * Copyright (C) 2011 The Guava Authors, 2018 Jan Heinrich Reimer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package rocks.poopjournal.metadataremover.model.resources

import rocks.poopjournal.metadataremover.util.Logger
import java.nio.charset.Charset
import java.util.Locale
import java.util.Locale.getDefault

/**
 * Represents an [Internet Media Type](http://en.wikipedia.org/wiki/Internet_media_type)
 * (also known as a MIME Type or Content Type). This class also supports the concept of media ranges
 * [defined by HTTP/1.1](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1).
 * As such, the `*` character is treated as a wildcard and is used to represent any acceptable
 * type or subtype value. A media type may not have wildcard type with a declared subtype. The
 * `*` character has no special meaning as part of a parameter. All values for type, subtype,
 * parameter attributes or parameter values must be valid according
 * to RFCs [2045](http://www.ietf.org/rfc/rfc2045.txt)
 * and [2046](http://www.ietf.org/rfc/rfc2046.txt).
 *
 *
 * All portions of the media type that are case-insensitive (type, subtype, parameter attributes)
 * are normalized to lowercase. The value of the `charset` parameter is normalized to
 * lowercase, but all others are left as-is.
 *
 *
 * Note that this specifically does **not** represent the value of the MIME `Content-Type` header
 * and as such has no support for header-specific considerations such as line
 * folding and comments.
 *
 *
 * For media types that take a charset the predefined constants default to UTF-8 and have a
 * "_UTF_8" suffix. To get a version without a character set, use [.withoutParameters].
 *
 * This file is a modified copy of Guava's
 * [MediaType](https://github.com/google/guava/blob/master/guava/src/com/google/common/net/MediaType.java)
 * which is released under the Apache License v.2.0.
 */
class MediaType(
        /** The top-level media type. For example, "text" in "text/plain". */
        type: String,
        /** The media subtype. For example, "plain" in "text/plain". */
        subtype: String,
        /** A map containing the parameters of this media type. */
        parameters: Map<String, String> = emptyMap(),
        charset: Charset? = null
) {

    init {
        check(WILDCARD != type || WILDCARD == subtype) {
            "A wildcard type cannot be used with a non-wildcard subtype"
        }
    }

    /** The top-level media type. For example, "text" in "text/plain". */
    val type: String = type.normalizeToken().apply { checkType() }

    /** The media subtype. For example, "plain" in "text/plain". */
    val subtype: String = subtype.normalizeToken().apply { checkType() }

    /** A map containing the parameters of this media type. */
    val parameters: Map<String, String> = parameters
            .run {
                // Add the charset to the parameter list.
                if (charset != null) {
                    this + (CHARSET_ATTRIBUTE to charset.name().lowercase(getDefault()))
                } else this
            }
            .normalizeParameters()
            .apply { checkParameters() }

    private val charsetAttribute = parameters[CHARSET_ATTRIBUTE]
    val charset = charsetAttribute
            ?.let(Charset::forName)

    private val hasWildcardType = WILDCARD == type
    private val hasWildcardSubtype = WILDCARD == subtype
    /** Returns `true` if either the type or subtype is the wildcard.  */
    val hasWildcard = hasWildcardType || hasWildcardSubtype

    constructor(
            /** The top-level media type. For example, "text" in "text/plain". */
            knownType: KnownType,
            /** The media subtype. For example, "plain" in "text/plain". */
            subtype: String,
            /** A map containing the parameters of this media type. */
            parameters: Map<String, String> = emptyMap(),
            charset: Charset? = null
    ) : this(
            knownType.type,
            subtype,
            parameters,
            charset
    )

    /**
     * Returns `true` if the [element] falls within this types range (as defined by
     * [the HTTP Accept header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)) given
     * by the argument according to three criteria:
     *
     *  1. The type of the argument is the wildcard or equal to the type of this instance.
     *  2. The subtype of the argument is the wildcard or equal to the subtype of this instance.
     *  3. All of the parameters present in the argument are present in this instance.
     *
     * For example:
     *
     * ```kotlin
     * PLAIN_TEXT_UTF_8 in PLAIN_TEXT_UTF_8 // true
     * HTML_UTF_8 in PLAIN_TEXT_UTF_8 // false
     * ANY in PLAIN_TEXT_UTF_8 // false
     * ANY_TEXT in PLAIN_TEXT_UTF_8 // false
     * ANY_IMAGE in PLAIN_TEXT_UTF_8 // false
     * ```
     */
    operator fun contains(element: MediaType) =
            ((hasWildcardType || type == element.type)
                    && (hasWildcardSubtype || subtype == element.subtype)
                    && element.parameters.entries.containsAll(parameters.entries))

    /**
     * Joins two media types and generates wildcards if necessary.
     *
     * Note: Only parameters in both media types will be kept.
     */
    operator fun times(other: MediaType): MediaType {
        val newParameters = parameters.filter { (key, value) ->
            other.parameters[key] == value
        }
        return if (type == other.type) {
            if (subtype == other.subtype) {
                MediaType(type, subtype, newParameters)
            } else MediaType(type, WILDCARD, newParameters)
        } else MediaType(WILDCARD, WILDCARD, newParameters)
    }

    /**
     * Returns the string representation of this media type in the format described in [RFC 2045](http://www.ietf.org/rfc/rfc2045.txt).
     */
    override fun toString(): String {
        val builder = StringBuilder()
                .append(type)
                .append(SUBTYPE_DELIMITER)
                .append(subtype)
        parameters.entries
                .takeIf { it.isNotEmpty() }
                ?.joinTo(
                        builder,
                        prefix = "$PARAMETER_DELIMITER",
                        separator = "$PARAMETER_DELIMITER"
                ) { (key, value) ->
                    key + PARAMETER_VALUE_DELIMITER + value
                }
        return builder.toString()
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is MediaType) return false

        if (type != other.type) return false
        if (subtype != other.subtype) return false
        if (parameters != other.parameters) return false

        return true
    }

    override fun hashCode(): Int {
        var result = type.hashCode()
        result = 31 * result + subtype.hashCode()
        result = 31 * result + parameters.hashCode()
        return result
    }

    companion object {

        private const val WILDCARD_SYMBOL = '*'
        private const val SUBTYPE_DELIMITER = '/'
        private const val PARAMETER_DELIMITER = ';'
        private const val PARAMETER_VALUE_DELIMITER = '='

        const val WILDCARD = "$WILDCARD_SYMBOL"

        private const val CHARSET_ATTRIBUTE = "charset"

        /**
         * Parse a [MediaType] from its [String] representation.
         *
         * @throws IllegalStateException if the input is not parsable.
         */
        fun parse(input: String): MediaType? {
            Logger.w("Parsing mime type of '$input'.")
            val typeParameterStrings = input
                    .split(PARAMETER_DELIMITER, limit = 2)
                    .takeIf { it.isNotEmpty() }
                    ?: return null
            val typeString = typeParameterStrings[0]
            val parametersString = typeParameterStrings.getOrNull(1)
            Logger.w("typeString: $typeString")
            Logger.w("parametersString: $parametersString")
            val (type, subtype) = typeString
                    .split(SUBTYPE_DELIMITER, limit = 2)
                    .takeIf { it.size == 2 }
                    ?.map(String::trim)
                    ?: return null
            if (type.isEmpty() || subtype.isEmpty()) return null
            val parameters = parametersString
                    ?.split(PARAMETER_DELIMITER)
                    ?.filter { it.isNotBlank() }
                    ?.mapNotNull { parameterString ->
                        parameterString
                                .split(PARAMETER_VALUE_DELIMITER, limit = 2)
                                .takeIf { it.size == 2 }
                                ?.map(String::trim)
                                ?.takeUnless { (key, value) -> key.isEmpty() || value.isEmpty() }
                                ?.let { (key, value) -> key to value }
                    }
                    ?.takeIf { it.isNotEmpty() }
                    ?.toMap()
            if (parameters != null) return MediaType(type, subtype, parameters)
            return MediaType(type, subtype)
        }
    }

    private fun String.normalizeToken() = lowercase(getDefault())

    private fun String.checkToken() {
        check(none {
            it == PARAMETER_DELIMITER || it == SUBTYPE_DELIMITER || it == PARAMETER_VALUE_DELIMITER
        })
    }

    private fun String.checkType() {
        checkToken()
        check(WILDCARD_SYMBOL !in this || this == WILDCARD)
    }

    private fun Map<String, String>.normalizeParameters() =
            map { (key, value) ->
                key.normalizeToken() to value.normalizeToken()
            }.toMap()

    private fun Map<String, String>.checkParameters() =
            forEach { (key, value) ->
                key.checkToken() to value.checkToken()
            }

    enum class KnownType(val type: String) {
        APPLICATION("application"),
        AUDIO("audio"),
        IMAGE("image"),
        TEXT("text"),
        VIDEO("video")


    }
}


================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/resources/MediaTypes.kt
================================================
/*
 * Copyright (C) 2011 The Guava Authors, 2018 Jan Heinrich Reimer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package rocks.poopjournal.metadataremover.model.resources

import rocks.poopjournal.metadataremover.model.resources.MediaType.KnownType.*
import kotlin.text.Charsets.UTF_8


/**
 * The following constants are grouped by their type and ordered alphabetically by the constant
 * name within that type. The constant name should be a sensible identifier that is closest to the
 * "common name" of the media. This is often, but not necessarily the same as the subtype.
 *
 * Be sure to declare all constants with the type and subtype in all lowercase. For types that
 * take a charset (e.g. all text types), default to UTF-8 and suffix the constant name with
 * `_UTF_8`.
 */
@Suppress("SpellCheckingInspection", "UNUSED")
object MediaTypes {

    private val ALL = mutableMapOf<MediaType, Set<MediaType>>()

    operator fun get(type: MediaType): Set<MediaType> = ALL.getOrElse(type) { setOf(type) }

    private operator fun MediaType.plus(type: MediaType) = plus(setOf(type))

    private operator fun MediaType.plus(types: Collection<MediaType>): MediaType {
        ALL[this] = get(this) + types
        return this
    }

    fun wildcardOf(): MediaType = wildcardOf(MediaType.WILDCARD)

    fun wildcardOf(type: String): MediaType = MediaType(type, MediaType.WILDCARD)

    fun wildcardOf(knownType: MediaType.KnownType): MediaType = wildcardOf(knownType.type)

    /* Wildcard types: */

    val ANY = wildcardOf()

    val ANY_TEXT = wildcardOf(TEXT)

    val ANY_IMAGE = wildcardOf(IMAGE)

    val ANY_AUDIO = wildcardOf(AUDIO)

    val ANY_VIDEO = wildcardOf(VIDEO)

    val ANY_APPLICATION = wildcardOf(APPLICATION)


    /* Text types: */

    val CACHE_MANIFEST_UTF_8 = MediaType(TEXT, "cache-manifest", charset = UTF_8)

    val CSS_UTF_8 = MediaType(TEXT, "css", charset = UTF_8)

    val CSV_UTF_8 = MediaType(TEXT, "csv", charset = UTF_8)

    val HTML_UTF_8 = MediaType(TEXT, "html", charset = UTF_8)

    val I_CALENDAR_UTF_8 = MediaType(TEXT, "calendar", charset = UTF_8)

    val PLAIN_TEXT_UTF_8 = MediaType(TEXT, "plain", charset = UTF_8)

    /**
     * [RFC 4329](http://www.rfc-editor.org/rfc/rfc4329.txt) declares [JAVASCRIPT_UTF_8]
     * to be the correct media type for JavaScript, but this
     * may be necessary in certain situations for compatibility.
     */
    val TEXT_JAVASCRIPT_UTF_8 = MediaType(TEXT, "javascript", charset = UTF_8)

    /**
     * [Tab separated values](http://www.iana.org/assignments/media-types/text/tab-separated-values).
     */
    val TSV_UTF_8 = MediaType(TEXT, "tab-separated-values", charset = UTF_8)

    val V_CARD_UTF_8 = MediaType(TEXT, "vcard", charset = UTF_8)

    val WML_UTF_8 = MediaType(TEXT, "vnd.wap.wml", charset = UTF_8)

    /**
     * As described in [RFC 3023](http://www.ietf.org/rfc/rfc3023.txt), this type is used
     * for XML documents that are "readable by casual users."
     * [APPLICATION_XML_UTF_8] is provided for documents that are intended for applications.
     */
    val XML_UTF_8 = MediaType(TEXT, "xml", charset = UTF_8)

    /**
     * As described in [the VTT spec](https://w3c.github.io/webvtt/#iana-text-vtt), this is
     * used for Web Video Text Tracks (WebVTT) files, used with the HTML5 track element.
     */
    val VTT_UTF_8 = MediaType(TEXT, "vtt", charset = UTF_8)


    /* Image types: */

    val ARW = MediaType(IMAGE, "arw") +
            MediaType(IMAGE, "x-sony-arw")

    val BMP = MediaType(IMAGE, "bmp") +
            MediaType(IMAGE, "x-bmp") +
            MediaType(IMAGE, "x-bitmap") +
            MediaType(IMAGE, "x-xbitmap") +
            MediaType(IMAGE, "x-win-bitmap") +
            MediaType(IMAGE, "x-windows-bmp") +
            MediaType(IMAGE, "ms-bmp") +
            MediaType(IMAGE, "x-ms-bmp") +
            MediaType(APPLICATION, "bmp") +
            MediaType(APPLICATION, "x-bmp") +
            MediaType(APPLICATION, "x-win-bitmap")

    val CR2 = MediaType(IMAGE, "cr2") +
            MediaType(IMAGE, "x-canon-cr2") +
            MediaType(IMAGE, "x-dcraw")

    /**
     * The media type for the [Canon Image File Format](http://en.wikipedia.org/wiki/Camera_Image_File_Format) (`crw` files),
     * a widely-used "raw image" format for cameras. It is found in `/etc/mime.types`,
     * e.g. in [Debian 3.48-1](http://anonscm.debian.org/gitweb/?p=collab-maint/mime-support.git;a=blob;f=mime.types;hb=HEAD).
     */
    val CRW = MediaType(IMAGE, "x-canon-crw")

    val DCX = MediaType(IMAGE, "dcx") +
            MediaType(IMAGE, "x-dcx")

    val DNG = MediaType(IMAGE, "dng") +
            MediaType(IMAGE, "x-adobe-dng")

    val GIF = MediaType(IMAGE, "gif")

    val ICO = MediaType(IMAGE, "vnd.microsoft.icon") +
            MediaType(IMAGE, "ico") +
            MediaType(IMAGE, "x-icon") +
            MediaType(APPLICATION, "ico") +
            MediaType(APPLICATION, "x-icon")

    val JPEG = MediaType(IMAGE, "jpeg") +
            MediaType(IMAGE, "jpg") +
            MediaType(APPLICATION, "jpg") +
            MediaType(APPLICATION, "x-jpg") +
            MediaType(IMAGE, "pjpeg") +
            MediaType(IMAGE, "pipeg") +
            MediaType(IMAGE, "vnd.swiftview-jpeg")

    val NEF = MediaType(IMAGE, "nef") +
            MediaType(IMAGE, "x-nikon-nef")

    val NRW = MediaType(IMAGE, "nrw") +
            MediaType(IMAGE, "x-nikon-nrw") +
            get(NEF)

    val ORF = MediaType(IMAGE, "orf") +
            MediaType(IMAGE, "x-olympus-orf")

    val PCX = MediaType(IMAGE, "pcx") +
            MediaType(IMAGE, "x-pcx") +
            MediaType(IMAGE, "x-pc-paintbrush") +
            MediaType(IMAGE, "vnd.swiftview-pcx") +
            MediaType(APPLICATION, "pcx") +
            MediaType(APPLICATION, "x-pcx") +
            MediaType("zz-application", "zz-winassoc-pcx")

    val PEF = MediaType(IMAGE, "pef") +
            MediaType(IMAGE, "x-pentax-pef")

    val PNG = MediaType(IMAGE, "png") +
            MediaType(APPLICATION, "png") +
            MediaType(APPLICATION, "x-png")

    val PNM = MediaType(IMAGE, "x-portable-anymap") +
            MediaType(IMAGE, "pbm")

    /**
     * The media type for the Photoshop File Format (`psd` files) as defined
     * by [IANA](http://www.iana.org/assignments/media-types/image/vnd.adobe.photoshop), and
     * found in [`/etc/mime.types`](http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types)
     * of the Apache [HTTPD project](http://httpd.apache.org/); for the specification,
     * see [Adobe Photoshop Document Format](http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm)
     * and [Wikipedia](http://en.wikipedia.org/wiki/Adobe_Photoshop#File_format); this is the
     * regular output/input of Photoshop (which can also export to various image formats;
     * note that files with extension "PSB" are in a distinct but related format).
     *
     * This is a more recent replacement for the older, experimental type
     * `x-photoshop`: [RFC-2046.6](http://tools.ietf.org/html/rfc2046#section-6).
     */
    val PSD = MediaType(IMAGE, "vnd.adobe.photoshop") +
            MediaType(IMAGE, "photoshop") +
            MediaType(IMAGE, "x-photoshop") +
            MediaType(IMAGE, "psd") +
            MediaType(APPLICATION, "photoshop") +
            MediaType(APPLICATION, "psd") +
            MediaType("zz-application", "zz-winassoc-psd")

    val RAF = MediaType(IMAGE, "raf") +
            MediaType(IMAGE, "x-fuji-raf")

    val RW2 = MediaType(IMAGE, "rw2") +
            MediaType(IMAGE, "x-panasonic-rw2") +
            MediaType(IMAGE, "x-panasonic-raw")

    val SRW = MediaType(IMAGE, "srw") +
            MediaType(IMAGE, "x-samsung-srw") +
            MediaType(APPLICATION, "octet-stream")

    val SVG_UTF_8 = MediaType(IMAGE, "svg+xml", charset = UTF_8)

    val TIFF = MediaType(IMAGE, "tiff")

    val WBMP = MediaType(IMAGE, "vnd.wap.wbmp")

    val WEBP = MediaType(IMAGE, "webp")

    val XBM = MediaType(IMAGE, "x-xbitmap")

    val XPM = MediaType(IMAGE, "x-xpixmap") +
            MediaType(IMAGE, "x-xbitmap") +
            MediaType(IMAGE, "xpm") +
            MediaType(IMAGE, "x-xpm")


    /* Audio types: */

    val MP4_AUDIO = MediaType(AUDIO, "mp4")

    val MPEG_AUDIO = MediaType(AUDIO, "mpeg")

    val OGG_AUDIO = MediaType(AUDIO, "ogg")

    val WEBM_AUDIO = MediaType(AUDIO, "webm")

    /**
     * Media type for L16 audio, as defined by [RFC 2586](https://tools.ietf.org/html/rfc2586).
     */
    val L16_AUDIO = MediaType(AUDIO, "l16")

    /**
     * Media type for L24 audio, as defined by [RFC 3190](https://tools.ietf.org/html/rfc3190).
     */
    val L24_AUDIO = MediaType(AUDIO, "l24")

    /**
     * Media type for Basic Audio, as defined by [RFC 2046](http://tools.ietf.org/html/rfc2046#section-4.3).
     */
    val BASIC_AUDIO = MediaType(AUDIO, "basic")

    /**
     * Media type for Advanced Audio Coding. For more information,
     * see [Advanced Audio Coding](https://en.wikipedia.org/wiki/Advanced_Audio_Coding).
     */
    val AAC_AUDIO = MediaType(AUDIO, "aac")

    /**
     * Media type for Vorbis Audio, as defined by [RFC 5215](http://tools.ietf.org/html/rfc5215).
     */
    val VORBIS_AUDIO = MediaType(AUDIO, "vorbis")

    /**
     * Media type for Windows Media Audio. For more information,
     * see [file name extensions for Windows Media metafiles](https://msdn.microsoft.com/en-us/library/windows/desktop/dd562994(v=vs.85).aspx).
     */
    val WMA_AUDIO = MediaType(AUDIO, "x-ms-wma")

    /**
     * Media type for Windows Media metafiles. For more information,
     * see [file name extensions for Windows Media metafiles](https://msdn.microsoft.com/en-us/library/windows/desktop/dd562994(v=vs.85).aspx).
     */
    val WAX_AUDIO = MediaType(AUDIO, "x-ms-wax")

    val WAV_AUDIO = MediaType(AUDIO, "x-wav")

    /**
     * Media type for Real Audio. For more information,
     * see [this link](http://service.real.com/help/faq/rp8/configrp8win.html).
     */
    val VND_REAL_AUDIO = MediaType(AUDIO, "vnd.rn-realaudio")

    /**
     * Media type for WAVE format, as defined by [RFC 2361](https://tools.ietf.org/html/rfc2361).
     */
    val VND_WAVE_AUDIO = MediaType(AUDIO, "vnd.wave")


    /* Video types: */

    val AVI_VIDEO = MediaType(VIDEO, "x-msvideo")

    val MP4_VIDEO = MediaType(VIDEO, "mp4")

    val MPEG_VIDEO = MediaType(VIDEO, "mpeg")

    val OGG_VIDEO = MediaType(VIDEO, "ogg")

    val QUICKTIME = MediaType(VIDEO, "quicktime")

    val WEBM_VIDEO = MediaType(VIDEO, "webm")

    val WMV = MediaType(VIDEO, "x-ms-wmv")

    /**
     * Media type for Flash video. For more information,
     * see [this link](http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7d48.html).
     */
    val FLV_VIDEO = MediaType(VIDEO, "x-flv")

    /**
     * Media type for the 3GP multimedia container format. For more information,
     * see [3GPP TS 26.244](ftp://www.3gpp.org/tsg_sa/TSG_SA/TSGS_23/Docs/PDF/SP-040065.pdf#page=10).
     */
    val THREE_GPP_VIDEO = MediaType(VIDEO, "3gpp")

    /**
     * Media type for the 3G2 multimedia container format. For more information,
     * see [3GPP2 C.S0050-B](http://www.3gpp2.org/Public_html/specs/C.S0050-B_v1.0_070521.pdf#page=16).
     */
    val THREE_GPP2_VIDEO = MediaType(VIDEO, "3gpp2")


    /* Application types: */

    /**
     * As described in [RFC 3023](http://www.ietf.org/rfc/rfc3023.txt), this type is used
     * for XML documents that are "unreadable by casual users."
     * [XML_UTF_8] is provided for documents that may be read by users.
     */
    val APPLICATION_XML_UTF_8 = MediaType(APPLICATION, "xml", charset = UTF_8)

    val ATOM_UTF_8 = MediaType(APPLICATION, "atom+xml", charset = UTF_8)

    val BZIP2 = MediaType(APPLICATION, "x-bzip2")

    /**
     * Media type for [dart files](https://www.dartlang.org/articles/embedding-in-html/).
     */
    val DART_UTF_8 = MediaType(APPLICATION, "dart", charset = UTF_8)

    /**
     * Media type for [Apple Passbook](https://goo.gl/2QoMvg).
     */
    val APPLE_PASSBOOK = MediaType(APPLICATION, "vnd.apple.pkpass")

    /**
     * Media type for [Embedded OpenType](http://en.wikipedia.org/wiki/Embedded_OpenType)
     * fonts. This is [registered](http://www.iana.org/assignments/media-types/application/vnd.ms-fontobject)
     * with the IANA.
     */
    val EOT = MediaType(APPLICATION, "vnd.ms-fontobject")

    /**
     * As described in the [International Digital Publishing Forum](http://idpf.org/epub)
     * EPUB is the distribution and interchange format standard for digital publications and
     * documents. This media type is defined in the [EPUB Open Container Format](http://www.idpf.org/epub/30/spec/epub30-ocf.html)
     * specification.
     */
    val EPUB = MediaType(APPLICATION, "epub+zip")

    val FORM_DATA = MediaType(APPLICATION, "x-www-form-urlencoded")

    /**
     * As described in [PKCS #12: Personal Information Exchange Syntax Standard](https://www.rsa.com/rsalabs/node.asp?id=2138),
     * PKCS #12 defines an archive file format for storing
     * many cryptography objects as a single file.
     */
    val KEY_ARCHIVE = MediaType(APPLICATION, "pkcs12")

    /**
     * This is a non-standard media type, but is commonly used in serving hosted binary files
     * as it is [known not to trigger content sniffing in current browsers](http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors).
     * It *should not* be used in other situations as it is not specified by any RFC
     * and does not appear in the [/IANA MIME Media Types](http://www.iana.org/assignments/media-types) list.
     * Consider [OCTET_STREAM] for binary data that is not being served to a browser.
     */
    val APPLICATION_BINARY = MediaType(APPLICATION, "binary")

    val GZIP = MediaType(APPLICATION, "x-gzip")

    /**
     * Media type for the [JSON Hypertext Application Language (HAL) documents](https://tools.ietf.org/html/draft-kelly-json-hal-08#section-3).
     */
    val HAL_JSON = MediaType(APPLICATION, "hal+json")

    val ICC = MediaType(APPLICATION, "vnd.iccprofile")

    /**
     * [RFC 4329](http://www.rfc-editor.org/rfc/rfc4329.txt) declares this to be the
     * correct media type for JavaScript, but [text/javascript][TEXT_JAVASCRIPT_UTF_8] may be
     * necessary in certain situations for compatibility.
     */
    val JAVASCRIPT_UTF_8 = MediaType(APPLICATION, "javascript", charset = UTF_8)

    val JSON_UTF_8 = MediaType(APPLICATION, "json", charset = UTF_8)

    /**
     * Media type for the [Manifest for a web application](http://www.w3.org/TR/appmanifest/).
     */
    val MANIFEST_JSON_UTF_8 = MediaType(APPLICATION, "manifest+json", charset = UTF_8)

    /**
     * Media type for [OGC KML (Keyhole Markup Language)](http://www.opengeospatial.org/standards/kml/).
     */
    val KML = MediaType(APPLICATION, "vnd.google-earth.kml+xml")

    /**
     * Media type for [OGC KML (Keyhole Markup Language)](http://www.opengeospatial.org/standards/kml/),
     * compressed using the ZIP format into KMZ archives.
     */
    val KMZ = MediaType(APPLICATION, "vnd.google-earth.kmz")

    /**
     * Media type for the [mbox database format](https://tools.ietf.org/html/rfc4155).
     */
    val MBOX = MediaType(APPLICATION, "mbox")

    /**
     * Media type for [Apple over-the-air mobile configuration profiles](http://goo.gl/1pGBFm).
     */
    val APPLE_MOBILE_CONFIG = MediaType(APPLICATION, "x-apple-aspen-config")

    val MICROSOFT_EXCEL = MediaType(APPLICATION, "vnd.ms-excel")

    val MICROSOFT_POWERPOINT = MediaType(APPLICATION, "vnd.ms-powerpoint")

    val MICROSOFT_WORD = MediaType(APPLICATION, "msword")

    /**
     * Media type for WASM applications. For more information see [the Web Assembly overview](https://webassembly.org/).
     */
    val WASM_APPLICATION = MediaType(APPLICATION, "wasm")

    /**
     * Media type for NaCl applications. For more information see [the Developer Guide for Native Client Application Structure](https://developer.chrome.com/native-client/devguide/coding/application-structure).
     */
    val NACL_APPLICATION = MediaType(APPLICATION, "x-nacl")

    /**
     * Media type for NaCl portable applications. For more information
     * see [the Developer Guide for Native Client Application Structure](https://developer.chrome.com/native-client/devguide/coding/application-structure).
     */
    val NACL_PORTABLE_APPLICATION = MediaType(APPLICATION, "x-pnacl")

    val OCTET_STREAM = MediaType(APPLICATION, "octet-stream")

    val OGG_CONTAINER = MediaType(APPLICATION, "ogg")

    val OOXML_DOCUMENT = MediaType(
            APPLICATION,
            "vnd.openxmlformats-officedocument.wordprocessingml.document"
    )

    val OOXML_PRESENTATION = MediaType(
            APPLICATION,
            "vnd.openxmlformats-officedocument.presentationml.presentation"
    )

    val OOXML_SHEET = MediaType(
            APPLICATION,
            "vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )

    val OPENDOCUMENT_GRAPHICS = MediaType(
            APPLICATION,
            "vnd.oasis.opendocument.graphics"
    )

    val OPENDOCUMENT_PRESENTATION = MediaType(
            APPLICATION,
            "vnd.oasis.opendocument.presentation"
    )

    val OPENDOCUMENT_SPREADSHEET = MediaType(
            APPLICATION,
            "vnd.oasis.opendocument.spreadsheet"
    )

    val OPENDOCUMENT_TEXT = MediaType(APPLICATION, "vnd.oasis.opendocument.text")

    val PDF = MediaType(APPLICATION, "pdf")

    val POSTSCRIPT = MediaType(APPLICATION, "postscript")

    /**
     * [Protocol buffers](http://tools.ietf.org/html/draft-rfernando-protocol-buffers-00)
     */
    val PROTOBUF = MediaType(APPLICATION, "protobuf")

    val RDF_XML_UTF_8 = MediaType(APPLICATION, "rdf+xml", charset = UTF_8)

    val RTF_UTF_8 = MediaType(APPLICATION, "rtf", charset = UTF_8)

    /**
     * Media type for SFNT fonts (which includes [TrueType](http://en.wikipedia.org/wiki/TrueType/)
     * and [OpenType](http://en.wikipedia.org/wiki/OpenType/) fonts).
     * This is [registered](http://www.iana.org/assignments/media-types/application/font-sfnt)
     * with the IANA.
     */
    val SFNT = MediaType(APPLICATION, "font-sfnt")

    val SHOCKWAVE_FLASH = MediaType(APPLICATION, "x-shockwave-flash")

    val SKETCHUP = MediaType(APPLICATION, "vnd.sketchup.skp")

    /**
     * As described in [RFC 3902](http://www.ietf.org/rfc/rfc3902.txt), this constant
     * (`application/soap+xml`) is used to identify SOAP 1.2 message envelopes that have been
     * serialized with XML 1.0.
     *
     * For SOAP 1.1 messages, see `XML_UTF_8`
     * per [W3C Note on Simple Object Access Protocol (SOAP) 1.1](http://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
     */
    val SOAP_XML_UTF_8 = MediaType(APPLICATION, "soap+xml", charset = UTF_8)

    val TAR = MediaType(APPLICATION, "x-tar")

    /**
     * Media type for the [Web Open Font Format](http://en.wikipedia.org/wiki/Web_Open_Font_Format) (WOFF)
     * [defined](http://www.w3.org/TR/WOFF/) by the W3C.
     * This is [registered](http://www.iana.org/assignments/media-types/application/font-woff) with
     * the IANA.
     */
    val WOFF = MediaType(APPLICATION, "font-woff")

    /**
     * Media type for the [Web Open Font Format](http://en.wikipedia.org/wiki/Web_Open_Font_Format) (WOFF)
     * version 2 [defined](https://www.w3.org/TR/WOFF2/) by the W3C.
     */
    val WOFF2 = MediaType(APPLICATION, "font-woff2")

    val XHTML_UTF_8 = MediaType(APPLICATION, "xhtml+xml", charset = UTF_8)

    /**
     * Media type for Extensible Resource Descriptors. This is not yet registered
     * with the IANA, but it is specified by OASIS
     * in the [XRD definition](http://docs.oasis-open.org/xri/xrd/v1.0/cd02/xrd-1.0-cd02.html)
     * and implemented in projects such as [WebFinger](http://code.google.com/p/webfinger/).
     */
    val XRD_UTF_8 = MediaType(APPLICATION, "xrd+xml", charset = UTF_8)

    val ZIP = MediaType(APPLICATION, "zip")
}


================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/resources/Resource.kt
================================================
package rocks.poopjournal.metadataremover.model.resources

@Suppress("UNUSED")
sealed class Resource<in T : Any> {
    class Empty<in T : Any> : Resource<T>()
    class Loading<in T : Any> : Resource<T>()
    data class Success<T : Any>(val value: T) : Resource<T>()
}

================================================
FILE: app/src/main/java/rocks/poopjournal/metadataremover/model/resources/Text.kt
================================================
/*
 * MIT License
 *
 * Copyright (c) 2018 Jan Heinrich Reimer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package rocks.poopjournal.metadataremover.model.resources

import android.content.Context
import android.text.SpannableStringBuilder
import androidx.annotation.IntRange
import androidx.annotation.StringRes
import rocks.poopjournal.metadataremover.model.resources.Text.Type.*
import java.util.*

/**
 * An umbrella container for several text representations, including [CharSequence]s,
 * [CharArray]s, and [string resources][StringRes].
 *
 * This class is a simplified copy of [android.graphics.drawable.Icon] which is released under
 * the Apache License 2.0, but customized for texts.
 */
class Text private constructor(
        /**
         * This text's type as in [Text.Type].
         */
        private val type: Type,

        /**
         * [CharSequence], [CharArray] or [Text] prefix, depending on the [type].
         */
        private val object1: Any? = null,

        /**
         * [Text] suffix, depending on the [type].
         */
        private val object2: Any? = null,

        /**
         * [StringRes] ID or the [CharArray]'s start offset, depending on the [type].
         */
        private val int1: Int? = null,

        /**
         * The [CharArray]'s length, depending on the [type].
         */
        private val int2: Int? = null,

        /**
         * The resource's format arguments.
         */
        private val formatArguments: Array<out Any> = emptyArray()
) {
    /**
     * Text source type.
     */
    enum class Type {
        SEQUENCE,
        RESOURCE,
        CHARACTERS,
        GLUE
    }

    /**
     * Throws an exception if this text's type is not [allowed][allowedTypes].
     */
    private fun checkType(vararg allowedTypes: Type) {
        if (type !in allowedTypes) {
            throw IllegalStateException("The text type was expected to be " +
                    "one of ${allowedTypes.joinToString()} but was $type instead.")
        }
    }

    /**
     * The text's [CharSequence] source.
     *
     * Note: This property is only available to [SEQUENCE] images.
     */
    private val sequence: CharSequence
        get() {
            checkT
Download .txt
gitextract_5a0us9tf/

├── .codecov.yml
├── .devcontainer/
│   └── devcontainer.json
├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── SECURITY.md
│   ├── SUPPORT.md
│   ├── dependabot.yaml
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml
├── .gitignore
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── lint.xml
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── rocks/
│       │           └── poopjournal/
│       │               └── metadataremover/
│       │                   └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── rocks/
│       │   │       └── poopjournal/
│       │   │           └── metadataremover/
│       │   │               ├── MetadataRemoverApp.kt
│       │   │               ├── di/
│       │   │               │   └── AppModule.kt
│       │   │               ├── metadata/
│       │   │               │   └── handlers/
│       │   │               │       ├── ApplyAllMetadataHandler.kt
│       │   │               │       ├── AudioVideoMetadataHandler.kt
│       │   │               │       ├── DocumentMetadataHandler.kt
│       │   │               │       ├── DrewMetadataReader.kt
│       │   │               │       ├── ExifMetadataHandler.kt
│       │   │               │       ├── FirstMatchMetadataHandler.kt
│       │   │               │       ├── NopMetadataHandler.kt
│       │   │               │       └── PngMetadataHandler.kt
│       │   │               ├── model/
│       │   │               │   ├── coordinates/
│       │   │               │   │   └── Coordinates.kt
│       │   │               │   ├── metadata/
│       │   │               │   │   ├── ClearedFile.kt
│       │   │               │   │   ├── Metadata.kt
│       │   │               │   │   ├── MetadataHandler.kt
│       │   │               │   │   ├── MetadataReader.kt
│       │   │               │   │   └── MetadataWriter.kt
│       │   │               │   ├── resources/
│       │   │               │   │   ├── Image.kt
│       │   │               │   │   ├── MediaType.kt
│       │   │               │   │   ├── MediaTypes.kt
│       │   │               │   │   ├── Resource.kt
│       │   │               │   │   └── Text.kt
│       │   │               │   └── util/
│       │   │               │       ├── MetadataHandlerExtensions.kt
│       │   │               │       └── SupportedTypes.kt
│       │   │               ├── ui/
│       │   │               │   ├── AboutActivity.kt
│       │   │               │   ├── LibrariesActivity.kt
│       │   │               │   ├── LicenseActivity.kt
│       │   │               │   ├── MainActivity.kt
│       │   │               │   ├── MetaAttributeAdapter.kt
│       │   │               │   ├── SettingsActivity.kt
│       │   │               │   └── adapter/
│       │   │               │       └── PageAdapter.kt
│       │   │               ├── util/
│       │   │               │   ├── ActivityLauncher.kt
│       │   │               │   ├── ActivityResultLauncher.kt
│       │   │               │   ├── AndroidViewDslScope.kt
│       │   │               │   ├── BindingAdapters.kt
│       │   │               │   ├── FileOpener.kt
│       │   │               │   ├── ImageFile.kt
│       │   │               │   ├── Logger.kt
│       │   │               │   ├── SharedPrefUtil.kt
│       │   │               │   ├── SingleLiveEvent.kt
│       │   │               │   ├── TimeZones.kt
│       │   │               │   └── extensions/
│       │   │               │       ├── ActivityLaunchers.kt
│       │   │               │       ├── Collections.kt
│       │   │               │       ├── Files.kt
│       │   │               │       ├── Locations.kt
│       │   │               │       ├── Numbers.kt
│       │   │               │       ├── Randoms.kt
│       │   │               │       ├── Strings.kt
│       │   │               │       ├── Times.kt
│       │   │               │       ├── android/
│       │   │               │       │   ├── Activities.kt
│       │   │               │       │   ├── Bundles.kt
│       │   │               │       │   ├── Colors.kt
│       │   │               │       │   ├── Contexts.kt
│       │   │               │       │   ├── Cursors.kt
│       │   │               │       │   ├── ExifInterfaces.kt
│       │   │               │       │   ├── FileDescriptors.kt
│       │   │               │       │   ├── ImageViews.kt
│       │   │               │       │   ├── Intents.kt
│       │   │               │       │   ├── Menus.kt
│       │   │               │       │   ├── TextViews.kt
│       │   │               │       │   ├── ViewGroups.kt
│       │   │               │       │   ├── Views.kt
│       │   │               │       │   └── architecture/
│       │   │               │       │       ├── LiveDatas.kt
│       │   │               │       │       ├── Uris.kt
│       │   │               │       │       └── ViewModels.kt
│       │   │               │       ├── glide/
│       │   │               │       │   └── RequestBuilders.kt
│       │   │               │       └── pngj/
│       │   │               │           └── PngJExtensions.kt
│       │   │               └── viewmodel/
│       │   │                   ├── ActivityLauncherViewModel.kt
│       │   │                   ├── ActivityResultLauncherViewModel.kt
│       │   │                   ├── MainViewModel.kt
│       │   │                   ├── MetadataViewModel.kt
│       │   │                   └── usecases/
│       │   │                       ├── GetDescriptor.kt
│       │   │                       ├── GetFileUri.kt
│       │   │                       ├── MetadataHandler.kt
│       │   │                       ├── SaveFiles.kt
│       │   │                       └── SharedFiles.kt
│       │   ├── play/
│       │   │   ├── contact-email.txt
│       │   │   ├── contact-website.txt
│       │   │   ├── default-language.txt
│       │   │   ├── listings/
│       │   │   │   ├── af/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ar/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── bg/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── bn/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ca/
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── de/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── el/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── en/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── en-US/
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── es-ES/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── et/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── fi-FI/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── fr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── hi/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── hr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── hu/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── id/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── it-IT/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ja-JP/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ko/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── mr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── my/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── nb-NO/
│       │   │   │   │   └── full-description.txt
│       │   │   │   ├── nl/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pa-IN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pl-PL/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pt-BR/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── pt-PT/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ru/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── si-LK/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── sv-SE/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── sw/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── sw-KE/
│       │   │   │   │   └── full-description.txt
│       │   │   │   ├── ta-IN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── te-IN/
│       │   │   │   │   └── short-description.txt
│       │   │   │   ├── tr/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── uk/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ur-IN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── ur-PK/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── vi/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   ├── xml_to_google_play.py
│       │   │   │   ├── zh-CN/
│       │   │   │   │   ├── full-description.txt
│       │   │   │   │   ├── google_play.xml
│       │   │   │   │   ├── short-description.txt
│       │   │   │   │   └── title.txt
│       │   │   │   └── zh-TW/
│       │   │   │       ├── full-description.txt
│       │   │   │       ├── google_play.xml
│       │   │   │       ├── short-description.txt
│       │   │   │       └── title.txt
│       │   │   └── release-notes/
│       │   │       ├── de-DE/
│       │   │       │   ├── alpha.txt
│       │   │       │   └── internal.txt
│       │   │       └── en-US/
│       │   │           ├── alpha.txt
│       │   │           └── internal.txt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── add_circle.xml
│       │       │   ├── arrow_down.xml
│       │       │   ├── arrow_up.xml
│       │       │   ├── ic_add.xml
│       │       │   ├── ic_album.xml
│       │       │   ├── ic_apps.xml
│       │       │   ├── ic_arrow_back.xml
│       │       │   ├── ic_arrow_back_ltr.xml
│       │       │   ├── ic_arrow_back_rtl.xml
│       │       │   ├── ic_artist.xml
│       │       │   ├── ic_audio.xml
│       │       │   ├── ic_author.xml
│       │       │   ├── ic_bitrate.xml
│       │       │   ├── ic_build.xml
│       │       │   ├── ic_business.xml
│       │       │   ├── ic_calendar_today.xml
│       │       │   ├── ic_camera.xml
│       │       │   ├── ic_category.xml
│       │       │   ├── ic_close.xml
│       │       │   ├── ic_color.xml
│       │       │   ├── ic_comment.xml
│       │       │   ├── ic_compilation.xml
│       │       │   ├── ic_composer.xml
│       │       │   ├── ic_crop_free.xml
│       │       │   ├── ic_date.xml
│       │       │   ├── ic_description.xml
│       │       │   ├── ic_document.xml
│       │       │   ├── ic_duration.xml
│       │       │   ├── ic_edit.xml
│       │       │   ├── ic_error.xml
│       │       │   ├── ic_event.xml
│       │       │   ├── ic_exposure.xml
│       │       │   ├── ic_factory.xml
│       │       │   ├── ic_file.xml
│       │       │   ├── ic_file_type.xml
│       │       │   ├── ic_folder_special.xml
│       │       │   ├── ic_genre.xml
│       │       │   ├── ic_history.xml
│       │       │   ├── ic_image.xml
│       │       │   ├── ic_info.xml
│       │       │   ├── ic_info_outline.xml
│       │       │   ├── ic_label.xml
│       │       │   ├── ic_language.xml
│       │       │   ├── ic_launcher_monochrome.xml
│       │       │   ├── ic_lens.xml
│       │       │   ├── ic_light.xml
│       │       │   ├── ic_location.xml
│       │       │   ├── ic_loop.xml
│       │       │   ├── ic_person.xml
│       │       │   ├── ic_play.xml
│       │       │   ├── ic_print.xml
│       │       │   ├── ic_storage.xml
│       │       │   ├── ic_subject.xml
│       │       │   ├── ic_supervisor_account.xml
│       │       │   ├── ic_title.xml
│       │       │   ├── ic_tracks.xml
│       │       │   ├── ic_update.xml
│       │       │   ├── ic_video.xml
│       │       │   ├── ic_warning.xml
│       │       │   ├── ic_writer.xml
│       │       │   ├── ic_year.xml
│       │       │   ├── outline_settings_24.xml
│       │       │   ├── restart.xml
│       │       │   ├── rounded_bottom_sheet_shape.xml
│       │       │   ├── rounded_corner_background.xml
│       │       │   ├── rounded_dialog_background.xml
│       │       │   ├── rounded_dialog_bg.xml
│       │       │   └── shadow_navigation_bar.xml
│       │       ├── drawable-ldrtl/
│       │       │   └── ic_arrow_back.xml
│       │       ├── layout/
│       │       │   ├── activity_about.xml
│       │       │   ├── activity_about_card_contribute.xml
│       │       │   ├── activity_about_card_designer.xml
│       │       │   ├── activity_about_card_developer.xml
│       │       │   ├── activity_about_card_language.xml
│       │       │   ├── activity_about_card_owner.xml
│       │       │   ├── activity_about_card_source.xml
│       │       │   ├── activity_about_cards.xml
│       │       │   ├── activity_about_header.xml
│       │       │   ├── activity_license.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── activity_main_bottom_sheet.xml
│       │       │   ├── activity_main_preview.xml
│       │       │   ├── activity_settings.xml
│       │       │   ├── dialog_error_monitor.xml
│       │       │   ├── image_child.xml
│       │       │   ├── listitem_meta_data.xml
│       │       │   ├── listitem_opensourceaaaa.xml
│       │       │   └── type_selector_dialog.xml
│       │       ├── menu/
│       │       │   └── menu_main.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   └── ic_launcher.xml
│       │       ├── resources.properties
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── strings.xml
│       │       │   ├── strings_activity_about.xml
│       │       │   ├── strings_activity_libraries.xml
│       │       │   ├── strings_activity_license.xml
│       │       │   ├── strings_activity_main.xml
│       │       │   ├── strings_attributes.xml
│       │       │   ├── styles.xml
│       │       │   └── themes.xml
│       │       ├── values-af-rZA/
│       │       │   └── strings.xml
│       │       ├── values-ar/
│       │       │   └── strings.xml
│       │       ├── values-ar-rSA/
│       │       │   └── strings.xml
│       │       ├── values-bg-rBG/
│       │       │   └── strings.xml
│       │       ├── values-bn/
│       │       │   └── strings.xml
│       │       ├── values-bn-rBD/
│       │       │   └── strings.xml
│       │       ├── values-ca-rES/
│       │       │   └── strings.xml
│       │       ├── values-cs-rCZ/
│       │       │   └── strings.xml
│       │       ├── values-da-rDK/
│       │       │   └── strings.xml
│       │       ├── values-de-rDE/
│       │       │   └── strings.xml
│       │       ├── values-el-rGR/
│       │       │   └── strings.xml
│       │       ├── values-en-rUS/
│       │       │   └── strings.xml
│       │       ├── values-eo-rUY/
│       │       │   └── strings.xml
│       │       ├── values-es-rES/
│       │       │   └── strings.xml
│       │       ├── values-et/
│       │       │   └── strings.xml
│       │       ├── values-fi-rFI/
│       │       │   └── strings.xml
│       │       ├── values-fil-rPH/
│       │       │   └── strings.xml
│       │       ├── values-fo-rFO/
│       │       │   └── strings.xml
│       │       ├── values-fr-rFR/
│       │       │   └── strings.xml
│       │       ├── values-hi-rIN/
│       │       │   └── strings.xml
│       │       ├── values-hr-rHR/
│       │       │   └── strings.xml
│       │       ├── values-hu-rHU/
│       │       │   └── strings.xml
│       │       ├── values-ia/
│       │       │   └── strings.xml
│       │       ├── values-in-rID/
│       │       │   └── strings.xml
│       │       ├── values-it-rIT/
│       │       │   └── strings.xml
│       │       ├── values-iw-rIL/
│       │       │   └── strings.xml
│       │       ├── values-ja-rJP/
│       │       │   └── strings.xml
│       │       ├── values-ko-rKR/
│       │       │   └── strings.xml
│       │       ├── values-mr-rIN/
│       │       │   └── strings.xml
│       │       ├── values-my-rMM/
│       │       │   └── strings.xml
│       │       ├── values-nl-rNL/
│       │       │   └── strings.xml
│       │       ├── values-no-rNO/
│       │       │   └── strings.xml
│       │       ├── values-pa-rIN/
│       │       │   └── strings.xml
│       │       ├── values-pl-rPL/
│       │       │   └── strings.xml
│       │       ├── values-pt-rBR/
│       │       │   └── strings.xml
│       │       ├── values-pt-rPT/
│       │       │   └── strings.xml
│       │       ├── values-ro-rRO/
│       │       │   └── strings.xml
│       │       ├── values-ru-rRU/
│       │       │   └── strings.xml
│       │       ├── values-si-rLK/
│       │       │   └── strings.xml
│       │       ├── values-sv-rSE/
│       │       │   └── strings.xml
│       │       ├── values-sw/
│       │       │   └── strings.xml
│       │       ├── values-sw-rKE/
│       │       │   └── strings.xml
│       │       ├── values-ta-rIN/
│       │       │   └── strings.xml
│       │       ├── values-te-rIN/
│       │       │   └── strings.xml
│       │       ├── values-tr-rTR/
│       │       │   └── strings.xml
│       │       ├── values-uk-rUA/
│       │       │   └── strings.xml
│       │       ├── values-ur-rIN/
│       │       │   └── strings.xml
│       │       ├── values-ur-rPK/
│       │       │   └── strings.xml
│       │       ├── values-uz-rUZ/
│       │       │   └── strings.xml
│       │       ├── values-v21/
│       │       │   └── colors.xml
│       │       ├── values-v23/
│       │       │   ├── colors.xml
│       │       │   └── themes.xml
│       │       ├── values-v27/
│       │       │   ├── colors.xml
│       │       │   └── themes.xml
│       │       ├── values-v28/
│       │       │   ├── colors.xml
│       │       │   └── themes.xml
│       │       ├── values-val-rES/
│       │       │   └── strings.xml
│       │       ├── values-vi-rVN/
│       │       │   └── strings.xml
│       │       ├── values-zh-rCN/
│       │       │   └── strings.xml
│       │       ├── values-zh-rTW/
│       │       │   └── strings.xml
│       │       └── xml/
│       │           └── shared_file_paths.xml
│       └── test/
│           └── java/
│               └── rocks/
│                   └── poopjournal/
│                       └── metadataremover/
│                           └── ExampleUnitTest.kt
├── art/
│   ├── avatars/
│   │   └── art_profile_crazy_marvin.ai
│   ├── icons/
│   │   └── ic_launcher/
│   │       └── old/
│   │           └── ic_launcher.ai
│   └── mockups/
│       ├── README.md
│       └── medatada_remover_ui.ai
├── build.gradle.kts
├── buildSrc/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               ├── CiUtils.kt
│               ├── ProjectExtensions.kt
│               ├── Version.kt
│               └── Versions.kt
├── ci/
│   ├── .gitignore
│   ├── adbkey.pub
│   └── local.properties
├── fastlane/
│   └── metadata/
│       └── android/
│           └── en-US/
│               ├── changelogs/
│               │   ├── 20000.txt
│               │   ├── 20010.txt
│               │   ├── 20020.txt
│               │   ├── 20030.txt
│               │   ├── 30000.txt
│               │   └── 31000.txt
│               ├── full_description.txt
│               ├── short_description.txt
│               ├── title.txt
│               └── video.txt
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── libs/
│   └── ffmpeg-kit.aar
├── secret/
│   ├── .gitignore
│   └── secrets.tar.gpg
└── settings.gradle.kts
Condensed preview — 444 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (7,205K chars).
[
  {
    "path": ".codecov.yml",
    "chars": 375,
    "preview": "codecov:\r\n  require_ci_to_pass: true\r\n\r\ncoverage:\r\n  precision: 2\r\n  round: down\r\n  range: \"70...100\"\r\n\r\n  status:\r\n    "
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 168,
    "preview": "{\n  \"name\": \"Docker Android Build Box\",\n  \"image\": \"mingc/android-build-box:latest\",\n  \"extensions\": [\"vscode-icons-team"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 306,
    "preview": "# User @heinrichreimer manages CI scripts.\n/ci/ @heinrichreimer\n\n# User @heinrichreimer manages build configuration.\n*.g"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 3729,
    "preview": "# Contributing To Metadata Remover\n\n👍🎉 First off, thanks for taking the time to contribute! 🎉👍\n\nThe following is a set o"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 51,
    "preview": "custom: [\"https://poopjournal.rocks/blog/donate/\"]\n"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 175,
    "preview": "Please report (suspected) security vulnerabilities to [marvin@poopjournal.rocks](mailto:marvin@poopjournal.rocks). It wo"
  },
  {
    "path": ".github/SUPPORT.md",
    "chars": 1631,
    "preview": "Hi!  👋\n\nWe’re excited that you’re using **Metadata Remover** and we’d love to help.\nTo help us help you, please read thr"
  },
  {
    "path": ".github/dependabot.yaml",
    "chars": 210,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n "
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1094,
    "preview": "name: CI\non: push\njobs:\n  build:\n    runs-on: macos-latest\n    steps:\n      - name: \"📥 Check-out\"\n        uses: actions/"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 977,
    "preview": "name: Deploy\non:\n  push:\n    branches:\n      - master\njobs:\n  deploy:\n    runs-on: macos-10.15\n    steps:\n      - name: "
  },
  {
    "path": ".gitignore",
    "chars": 1124,
    "preview": "\n# Created by https://www.gitignore.io/api/kotlin,android\n\n### Android ###\n# Built application files\n#*.apk\n\n# Files for"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 7219,
    "preview": "[![Icon](art/icons/ic_launcher/legacy/ic_launcher_squircle_xxxhdpi.png)](art/icons/ic_launcher/ic_launcher_play_store.pn"
  },
  {
    "path": "app/.gitignore",
    "chars": 8,
    "preview": "/build\r\n"
  },
  {
    "path": "app/build.gradle.kts",
    "chars": 8825,
    "preview": "/*\n * MIT License\n *\n * Copyright (c) 2018 Jan Heinrich Reimer\n *\n * Permission is hereby granted, free of charge, to an"
  },
  {
    "path": "app/lint.xml",
    "chars": 512,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<lint>\r\n    <issue id=\"IconLauncherShape\">\r\n        <ignore path=\"src/main/res/m"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 1913,
    "preview": "# MIT License\r\n#\r\n# Copyright (c) 2018 Jan Heinrich Reimer\r\n#\r\n# Permission is hereby granted, free of charge, to any pe"
  },
  {
    "path": "app/src/androidTest/java/rocks/poopjournal/metadataremover/ExampleInstrumentedTest.kt",
    "chars": 751,
    "preview": "package rocks.poopjournal.metadataremover\r\n\r\nimport androidx.test.ext.junit.runners.AndroidJUnit4\r\nimport androidx.test."
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 4797,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/MetadataRemoverApp.kt",
    "chars": 892,
    "preview": "package rocks.poopjournal.metadataremover\n\nimport android.app.Application\nimport dagger.hilt.android.HiltAndroidApp\nimpo"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/di/AppModule.kt",
    "chars": 1541,
    "preview": "package rocks.poopjournal.metadataremover.di\n\nimport android.content.Context\nimport dagger.Module\nimport dagger.Provides"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/ApplyAllMetadataHandler.kt",
    "chars": 3475,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/AudioVideoMetadataHandler.kt",
    "chars": 10544,
    "preview": "package rocks.poopjournal.metadataremover.metadata.handlers\n\nimport android.content.Context\nimport android.media.MediaMe"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/DocumentMetadataHandler.kt",
    "chars": 45510,
    "preview": "package rocks.poopjournal.metadataremover.metadata.handlers\n\nimport android.content.Context\nimport android.net.Uri\nimpor"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/DrewMetadataReader.kt",
    "chars": 12203,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/ExifMetadataHandler.kt",
    "chars": 6094,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/FirstMatchMetadataHandler.kt",
    "chars": 2950,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/NopMetadataHandler.kt",
    "chars": 2016,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/metadata/handlers/PngMetadataHandler.kt",
    "chars": 6442,
    "preview": "/*\n * MIT License\n *\n * Copyright (c) 2018 Jan Heinrich Reimer\n *\n * Permission is hereby granted, free of charge, to an"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/coordinates/Coordinates.kt",
    "chars": 3768,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/ClearedFile.kt",
    "chars": 147,
    "preview": "package rocks.poopjournal.metadataremover.model.metadata\n\nimport android.net.Uri\n\ndata class ClearedFile (\n    val uri: "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/Metadata.kt",
    "chars": 3171,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/MetadataHandler.kt",
    "chars": 1286,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/MetadataReader.kt",
    "chars": 1837,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/metadata/MetadataWriter.kt",
    "chars": 1926,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/resources/Image.kt",
    "chars": 8858,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/resources/MediaType.kt",
    "chars": 10805,
    "preview": "/*\r\n * Copyright (C) 2011 The Guava Authors, 2018 Jan Heinrich Reimer\r\n *\r\n * Licensed under the Apache License, Version"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/resources/MediaTypes.kt",
    "chars": 21194,
    "preview": "/*\r\n * Copyright (C) 2011 The Guava Authors, 2018 Jan Heinrich Reimer\r\n *\r\n * Licensed under the Apache License, Version"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/resources/Resource.kt",
    "chars": 275,
    "preview": "package rocks.poopjournal.metadataremover.model.resources\r\n\r\n@Suppress(\"UNUSED\")\r\nsealed class Resource<in T : Any> {\r\n "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/resources/Text.kt",
    "chars": 10152,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/util/MetadataHandlerExtensions.kt",
    "chars": 1196,
    "preview": "package rocks.poopjournal.metadataremover.model.util\r\n\r\nimport rocks.poopjournal.metadataremover.metadata.handlers.NopMe"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/model/util/SupportedTypes.kt",
    "chars": 119,
    "preview": "package rocks.poopjournal.metadataremover.model.util\n\nenum class SupportedTypes {\n    IMAGE,\n    VIDEO,\n    DOCUMENTS\n}"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/AboutActivity.kt",
    "chars": 7175,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/LibrariesActivity.kt",
    "chars": 1589,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/LicenseActivity.kt",
    "chars": 1548,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/MainActivity.kt",
    "chars": 16632,
    "preview": "/*\n * MIT License\n *\n * Copyright (c) 2018 Jan Heinrich Reimer\n *\n * Permission is hereby granted, free of charge, to an"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/MetaAttributeAdapter.kt",
    "chars": 4351,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/SettingsActivity.kt",
    "chars": 2274,
    "preview": "package rocks.poopjournal.metadataremover.ui\n\nimport android.graphics.Color\nimport android.graphics.drawable.ColorDrawab"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/ui/adapter/PageAdapter.kt",
    "chars": 5379,
    "preview": "package rocks.poopjournal.metadataremover.ui.adapter\n\nimport android.annotation.SuppressLint\nimport android.content.Cont"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/ActivityLauncher.kt",
    "chars": 2066,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/ActivityResultLauncher.kt",
    "chars": 2578,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/AndroidViewDslScope.kt",
    "chars": 1599,
    "preview": "package rocks.poopjournal.metadataremover.util\r\n\r\nimport android.view.Menu\r\nimport android.view.MenuItem\r\nimport android"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/BindingAdapters.kt",
    "chars": 2522,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/FileOpener.kt",
    "chars": 6671,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/ImageFile.kt",
    "chars": 1617,
    "preview": "package rocks.poopjournal.metadataremover.util\r\n\r\nimport android.graphics.BitmapFactory\r\nimport rocks.poopjournal.metada"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/Logger.kt",
    "chars": 5762,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/SharedPrefUtil.kt",
    "chars": 711,
    "preview": "package rocks.poopjournal.metadataremover.util\n\nimport android.content.Context\nimport android.content.SharedPreferences\n"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/SingleLiveEvent.kt",
    "chars": 3514,
    "preview": "/*\r\n *  Copyright 2017 Google Inc.\r\n *\r\n *  Licensed under the Apache License, Version 2.0 (the \"License\");\r\n *  you may"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/TimeZones.kt",
    "chars": 1108,
    "preview": "package rocks.poopjournal.metadataremover.util\n\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport ja"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/ActivityLaunchers.kt",
    "chars": 3163,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Collections.kt",
    "chars": 2968,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Files.kt",
    "chars": 3584,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Locations.kt",
    "chars": 1462,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Numbers.kt",
    "chars": 4053,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Randoms.kt",
    "chars": 361,
    "preview": "package rocks.poopjournal.metadataremover.util.extensions\n\nimport java.util.*\n\nfun Random.nextLong(n: Long): Long {\n    "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Strings.kt",
    "chars": 1357,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/Times.kt",
    "chars": 1954,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Activities.kt",
    "chars": 1399,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Bundles.kt",
    "chars": 4185,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Colors.kt",
    "chars": 2041,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Contexts.kt",
    "chars": 2447,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Cursors.kt",
    "chars": 1767,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/ExifInterfaces.kt",
    "chars": 20906,
    "preview": "package rocks.poopjournal.metadataremover.util.extensions.android\r\n\r\nimport android.location.Address\r\nimport android.loc"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/FileDescriptors.kt",
    "chars": 890,
    "preview": "package rocks.poopjournal.metadataremover.util.extensions.android\r\n\r\nimport android.content.Context\r\nimport android.cont"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/ImageViews.kt",
    "chars": 1458,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Intents.kt",
    "chars": 4224,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Menus.kt",
    "chars": 1850,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/TextViews.kt",
    "chars": 1837,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/ViewGroups.kt",
    "chars": 2675,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/Views.kt",
    "chars": 2088,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/architecture/LiveDatas.kt",
    "chars": 5170,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/architecture/Uris.kt",
    "chars": 2765,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/android/architecture/ViewModels.kt",
    "chars": 1859,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2017 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/glide/RequestBuilders.kt",
    "chars": 1783,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/util/extensions/pngj/PngJExtensions.kt",
    "chars": 4840,
    "preview": "package rocks.poopjournal.metadataremover.util.extensions.pngj\n\nimport ar.com.hjg.pngj.PngReader\nimport ar.com.hjg.pngj."
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/ActivityLauncherViewModel.kt",
    "chars": 1431,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/ActivityResultLauncherViewModel.kt",
    "chars": 1562,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/MainViewModel.kt",
    "chars": 7199,
    "preview": "package rocks.poopjournal.metadataremover.viewmodel\n\n\nimport android.content.res.AssetFileDescriptor\nimport android.net."
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/MetadataViewModel.kt",
    "chars": 1473,
    "preview": "/*\r\n * MIT License\r\n *\r\n * Copyright (c) 2018 Jan Heinrich Reimer\r\n *\r\n * Permission is hereby granted, free of charge, "
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/usecases/GetDescriptor.kt",
    "chars": 1337,
    "preview": "package rocks.poopjournal.metadataremover.viewmodel.usecases\n\nimport android.content.Context\nimport android.content.res."
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/usecases/GetFileUri.kt",
    "chars": 790,
    "preview": "package rocks.poopjournal.metadataremover.viewmodel.usecases\n\nimport android.content.Context\nimport android.net.Uri\nimpo"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/usecases/MetadataHandler.kt",
    "chars": 1319,
    "preview": "package rocks.poopjournal.metadataremover.viewmodel.usecases\n\nimport android.content.Context\nimport rocks.poopjournal.me"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/usecases/SaveFiles.kt",
    "chars": 2006,
    "preview": "package rocks.poopjournal.metadataremover.viewmodel.usecases\n\nimport android.content.ContentValues\nimport android.conten"
  },
  {
    "path": "app/src/main/java/rocks/poopjournal/metadataremover/viewmodel/usecases/SharedFiles.kt",
    "chars": 331,
    "preview": "package rocks.poopjournal.metadataremover.viewmodel.usecases\n\nimport android.content.Context\nimport javax.inject.Inject\n"
  },
  {
    "path": "app/src/main/play/contact-email.txt",
    "chars": 24,
    "preview": "marvin@poopjournal.rocks"
  },
  {
    "path": "app/src/main/play/contact-website.txt",
    "chars": 41,
    "preview": "https://poopjournal.rocks/MetadataRemover"
  },
  {
    "path": "app/src/main/play/default-language.txt",
    "chars": 5,
    "preview": "en-US"
  },
  {
    "path": "app/src/main/play/listings/af/full-description.txt",
    "chars": 1618,
    "preview": "<div><i>Beskerm jou privaatheid deur metadata te verwyder van jou fotos voor jy dit op die internet deel!</i><br><br><h2"
  },
  {
    "path": "app/src/main/play/listings/af/google_play.xml",
    "chars": 1891,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Metadata Verwyderaar]]></title>\n  <fullDescription><!"
  },
  {
    "path": "app/src/main/play/listings/af/short-description.txt",
    "chars": 61,
    "preview": "Beskerm u privaatheid deur metadata van u foto's te verwyder!"
  },
  {
    "path": "app/src/main/play/listings/af/title.txt",
    "chars": 20,
    "preview": "Metadata Verwyderaar"
  },
  {
    "path": "app/src/main/play/listings/ar/full-description.txt",
    "chars": 1345,
    "preview": "<i>احمِ خصوصيتك عن طريق إزالة البيانات الوصفية من صورك، قبل مشاركتها على الإنترنت.</i>\n\n<h2><b>الميزات:</b></h2>\n\n ✔️ عر"
  },
  {
    "path": "app/src/main/play/listings/ar/google_play.xml",
    "chars": 1699,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[ماسح البيانات الوصفية]]></title>\n  <fullDescription><"
  },
  {
    "path": "app/src/main/play/listings/ar/short-description.txt",
    "chars": 53,
    "preview": "احمِ خصوصيتك عن طريق إزالة البيانات الوصفية من صورك!\n"
  },
  {
    "path": "app/src/main/play/listings/ar/title.txt",
    "chars": 23,
    "preview": "مُزيل البيانات الوصفية\n"
  },
  {
    "path": "app/src/main/play/listings/bg/full-description.txt",
    "chars": 1671,
    "preview": "<i>Защитете поверителността си, като премахнете метаданните от вашите снимки, преди да ги споделите в интернет.</i>\n\n <h"
  },
  {
    "path": "app/src/main/play/listings/bg/short-description.txt",
    "chars": 75,
    "preview": "Защитете поверителността си, като премахнете метаданните от вашите снимки!\n"
  },
  {
    "path": "app/src/main/play/listings/bg/title.txt",
    "chars": 17,
    "preview": "Метаданни Чистач\n"
  },
  {
    "path": "app/src/main/play/listings/bn/full-description.txt",
    "chars": 1624,
    "preview": "<div><i>আপনার প্রাইভেসি রক্ষা করুন ইনটেরনেট এ ফোটো শেয়ার করার আগে, ফোটো থেকে মেটাডাটা রিমুভ করার মাধ্যমে!</i><br><br><h"
  },
  {
    "path": "app/src/main/play/listings/bn/google_play.xml",
    "chars": 1897,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[মেটাডাটা রিমুভার]]></title>\n  <fullDescription><![CDA"
  },
  {
    "path": "app/src/main/play/listings/bn/short-description.txt",
    "chars": 65,
    "preview": "ফোটো থেকে মেটাডাটা রিমুভ করার মাধ্যমে আপনার প্রাইভেসি রক্ষা করুন!"
  },
  {
    "path": "app/src/main/play/listings/bn/title.txt",
    "chars": 16,
    "preview": "মেটাডাটা রিমুভার"
  },
  {
    "path": "app/src/main/play/listings/ca/short-description.txt",
    "chars": 74,
    "preview": "Protegiu la vostra privadesa eliminant les metadades de les vostres fotos\n"
  },
  {
    "path": "app/src/main/play/listings/ca/title.txt",
    "chars": 24,
    "preview": "Eliminador de metadades\n"
  },
  {
    "path": "app/src/main/play/listings/de/full-description.txt",
    "chars": 1518,
    "preview": "<i>Schützen Sie Ihre Privatsphäre, indem Sie Metadaten aus Ihren Fotos entfernen, bevor Sie sie im Internet teilen!</i>\n"
  },
  {
    "path": "app/src/main/play/listings/de/google_play.xml",
    "chars": 1914,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Metadaten Entferner]]></title>\n  <fullDescription><!["
  },
  {
    "path": "app/src/main/play/listings/de/short-description.txt",
    "chars": 79,
    "preview": "Schützen Sie Ihre Privatsphäre, indem Sie Metadaten von Ihren Fotos entfernen!\n"
  },
  {
    "path": "app/src/main/play/listings/de/title.txt",
    "chars": 19,
    "preview": "Metadatenentferner\n"
  },
  {
    "path": "app/src/main/play/listings/el/full-description.txt",
    "chars": 1708,
    "preview": "<i>Προτάξτε την ιδιωτικότητά σας αφαιρώντας τα μεταδεδομένα από τις φωτογραφίες σας, πριν τα μοιραστείτε στο Internet.</"
  },
  {
    "path": "app/src/main/play/listings/el/google_play.xml",
    "chars": 2200,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Αφαίρεση Μεταδεδομένων]]></title>\n  <fullDescription>"
  },
  {
    "path": "app/src/main/play/listings/el/short-description.txt",
    "chars": 80,
    "preview": "Προστατέψτε το απόρρητό σας αφαιρώντας τα μεταδεδομένα από τις φωτογραφίες σας!\n"
  },
  {
    "path": "app/src/main/play/listings/el/title.txt",
    "chars": 23,
    "preview": "Αφαίρεση μεταδεδομένων\n"
  },
  {
    "path": "app/src/main/play/listings/en/full-description.txt",
    "chars": 1464,
    "preview": "<i>Protect your privacy by removing metadata from your photos, before sharing them on the Internet.</i>\n\n<h2><b>Features"
  },
  {
    "path": "app/src/main/play/listings/en/google_play.xml",
    "chars": 275,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[]]></title>\n  <fullDescription><![CDATA[<div><i></i><"
  },
  {
    "path": "app/src/main/play/listings/en/short-description.txt",
    "chars": 60,
    "preview": "Protect your privacy by removing metadata from your photos!\n"
  },
  {
    "path": "app/src/main/play/listings/en/title.txt",
    "chars": 17,
    "preview": "Metadata Remover\n"
  },
  {
    "path": "app/src/main/play/listings/en-US/short-description.txt",
    "chars": 60,
    "preview": "Protect your privacy by removing metadata from your photos!\n"
  },
  {
    "path": "app/src/main/play/listings/en-US/title.txt",
    "chars": 17,
    "preview": "Metadata Remover\n"
  },
  {
    "path": "app/src/main/play/listings/es-ES/full-description.txt",
    "chars": 1690,
    "preview": "<i>Protege tu privacidad eliminando los metadatos de tus fotos antes de compartirlas en Internet.</i>\n\n<h2><b>Caracterís"
  },
  {
    "path": "app/src/main/play/listings/es-ES/google_play.xml",
    "chars": 1945,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Remover metadatos]]></title>\n  <fullDescription><![CD"
  },
  {
    "path": "app/src/main/play/listings/es-ES/short-description.txt",
    "chars": 62,
    "preview": "¡Protege tu privacidad eliminando los metadatos de tus fotos!\n"
  },
  {
    "path": "app/src/main/play/listings/es-ES/title.txt",
    "chars": 24,
    "preview": "Eliminador de metadatos\n"
  },
  {
    "path": "app/src/main/play/listings/et/full-description.txt",
    "chars": 1611,
    "preview": "<i>Kaitse oma privaatsust ja eemalda fotodest enne internetis jagamist kõik metateave.</i>\n\n<h2><b>Mida see rakendus osk"
  },
  {
    "path": "app/src/main/play/listings/et/short-description.txt",
    "chars": 67,
    "preview": "Eemalda oma fotodest metaandmed ja kaitse sellega oma privaatsust!\n"
  },
  {
    "path": "app/src/main/play/listings/et/title.txt",
    "chars": 22,
    "preview": "Metaandmete eemaldaja\n"
  },
  {
    "path": "app/src/main/play/listings/fi-FI/full-description.txt",
    "chars": 1510,
    "preview": "<i>Suojaa yksityisyyttäsi poistamalla metatiedot valokuvistasi ennen niiden jakamista Internetissä.</i>\n\n<h2><b>Ominaisu"
  },
  {
    "path": "app/src/main/play/listings/fi-FI/short-description.txt",
    "chars": 61,
    "preview": "Suojaa yksityisyyttäsi poistamalla metatiedot valokuvistasi!\n"
  },
  {
    "path": "app/src/main/play/listings/fi-FI/title.txt",
    "chars": 17,
    "preview": "Metadata Remover\n"
  },
  {
    "path": "app/src/main/play/listings/fr/full-description.txt",
    "chars": 1822,
    "preview": "<i>Protégez votre vie privée en supprimant les métadonnées de vos photos, avant de les partager sur Internet !</i>\n\n<h2>"
  },
  {
    "path": "app/src/main/play/listings/fr/google_play.xml",
    "chars": 2208,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Effaceur de métadonnées]]></title>\n  <fullDescription"
  },
  {
    "path": "app/src/main/play/listings/fr/short-description.txt",
    "chars": 72,
    "preview": "Protégez votre vie privée en supprimant les métadonnées de vos photos !\n"
  },
  {
    "path": "app/src/main/play/listings/fr/title.txt",
    "chars": 24,
    "preview": "Effaceur de métadonnées\n"
  },
  {
    "path": "app/src/main/play/listings/hi/full-description.txt",
    "chars": 1498,
    "preview": "<i>इंटरनेट पर साझा करने से पहले, अपनी तस्वीरों से मेटाडेटा हटाकर अपनी गोपनीयता सुरक्षित रखें।</i>\n\n<h2><b>विशेषताएं:</b>"
  },
  {
    "path": "app/src/main/play/listings/hi/google_play.xml",
    "chars": 1915,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[मेटाडेटा रिमुवर]]></title>\n  <fullDescription><![CDAT"
  },
  {
    "path": "app/src/main/play/listings/hi/short-description.txt",
    "chars": 61,
    "preview": "अपनी तस्वीरों से मेटाडेटा हटाकर अपनी गोपनीयता सुरक्षित रखें!\n"
  },
  {
    "path": "app/src/main/play/listings/hi/title.txt",
    "chars": 17,
    "preview": "Metadata Remover\n"
  },
  {
    "path": "app/src/main/play/listings/hr/full-description.txt",
    "chars": 1467,
    "preview": "<i>Zaštiti svoju privatnost uklanjanjem metapodataka iz tvojih fotografija, prije nego što ih dijeliš na internetu.</i>\n"
  },
  {
    "path": "app/src/main/play/listings/hr/short-description.txt",
    "chars": 66,
    "preview": "Zaštiti svoju privatnost uklanjanjem metapodataka iz fotografija!\n"
  },
  {
    "path": "app/src/main/play/listings/hr/title.txt",
    "chars": 24,
    "preview": "Uklanjanje metapodataka\n"
  },
  {
    "path": "app/src/main/play/listings/hu/full-description.txt",
    "chars": 1449,
    "preview": "<i>Védd meg a magánéleted: távolítsd el a metaadatokat a fotóidról, mielőtt megosztanád őket az interneten!</i>\n\n<h2><b>"
  },
  {
    "path": "app/src/main/play/listings/hu/google_play.xml",
    "chars": 1936,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Metaadat eltávolító]]></title>\n  <fullDescription><!["
  },
  {
    "path": "app/src/main/play/listings/hu/short-description.txt",
    "chars": 65,
    "preview": "Védd meg a magánéleted: távolítsd el a metaadatokat a fotóidról!\n"
  },
  {
    "path": "app/src/main/play/listings/hu/title.txt",
    "chars": 20,
    "preview": "Metaadat Eltávolító\n"
  },
  {
    "path": "app/src/main/play/listings/id/full-description.txt",
    "chars": 1601,
    "preview": "<i>Lindungi privasi Anda dengan menghapus metadata dari foto Anda, sebelum membagikannya di Internet.</i>\n\n<h2><b>Fitur:"
  },
  {
    "path": "app/src/main/play/listings/id/google_play.xml",
    "chars": 1985,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Penghapus Metadata]]></title>\n  <fullDescription><![C"
  },
  {
    "path": "app/src/main/play/listings/id/short-description.txt",
    "chars": 69,
    "preview": "Lindungi privasi Anda dengan menghapus metadata dari foto-foto Anda!\n"
  },
  {
    "path": "app/src/main/play/listings/id/title.txt",
    "chars": 19,
    "preview": "Penghapus Metadata\n"
  },
  {
    "path": "app/src/main/play/listings/it-IT/full-description.txt",
    "chars": 1672,
    "preview": "<i>Proteggi la tua privacy rimuovendo i metadati dalle tue foto prima di condividerle su Internet.</i>\n\n<h2><b>Funzional"
  },
  {
    "path": "app/src/main/play/listings/it-IT/short-description.txt",
    "chars": 62,
    "preview": "Proteggi la tua privacy rimuovendo i metadati dalle tue foto!\n"
  },
  {
    "path": "app/src/main/play/listings/it-IT/title.txt",
    "chars": 17,
    "preview": "Metadata Remover\n"
  },
  {
    "path": "app/src/main/play/listings/ja-JP/full-description.txt",
    "chars": 800,
    "preview": "<i>写真をインターネット上で共有する前に、写真からメタデータを削除してプライバシーを保護します。</i>\n\n<h2><b>特徴:</b></h2>\n\n ✔️ メタデータの表示\n ✔️ 画像のプレビュー\n ✔️ メタデータの削除\n ✔️ シ"
  },
  {
    "path": "app/src/main/play/listings/ja-JP/short-description.txt",
    "chars": 30,
    "preview": "写真からメタデータを削除してプライバシーを保護しましょう!\n"
  },
  {
    "path": "app/src/main/play/listings/ja-JP/title.txt",
    "chars": 17,
    "preview": "Metadata Remover\n"
  },
  {
    "path": "app/src/main/play/listings/ko/full-description.txt",
    "chars": 831,
    "preview": "<i>인터넷에 공유하기 전에 사진에서 메타데이터를 제거함으로써 개인정보를 보호합니다.</i>\n\n<h2><b>기능:</b></h2>\n\n ✔️ 메타데이터 보기\n ✔️ 사진 미리보기\n ✔️ 메타데이터 삭제하기\n ✔️ 간단"
  },
  {
    "path": "app/src/main/play/listings/ko/google_play.xml",
    "chars": 1213,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[메타데이터 제거기]]></title>\n  <fullDescription><![CDATA[<div"
  },
  {
    "path": "app/src/main/play/listings/ko/short-description.txt",
    "chars": 28,
    "preview": "사진에서 메타자료를 지워 개인 정보를 보호하세요!\n"
  },
  {
    "path": "app/src/main/play/listings/ko/title.txt",
    "chars": 10,
    "preview": "메타데이터 제거기\n"
  },
  {
    "path": "app/src/main/play/listings/mr/full-description.txt",
    "chars": 1619,
    "preview": "<div><i>इंटरनेट वर तुमची छायाचित्रे शेअर करण्या अगोदर, त्याच्या मधून मेटाडेटा काढून तुमची गोपनीयता सावरणे!</i><br><br><h"
  },
  {
    "path": "app/src/main/play/listings/mr/google_play.xml",
    "chars": 1878,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[मेटाडेटा काढणे]]></title>\n  <fullDescription><![CDATA"
  },
  {
    "path": "app/src/main/play/listings/mr/short-description.txt",
    "chars": 63,
    "preview": "तुमच्या फोटोंमधून मेटाडेटा काढून तुमची गोपनीयता सुरक्षित ठेवा!\n"
  },
  {
    "path": "app/src/main/play/listings/mr/title.txt",
    "chars": 18,
    "preview": "मेटाडेटा रिमूव्हर\n"
  },
  {
    "path": "app/src/main/play/listings/my/full-description.txt",
    "chars": 1838,
    "preview": "<div><i>သင့်ဓာတ်ပုံများကို အင်တာနက်ပေါ်တွင် မျှဝေခြင်းမပြုမီ ထိုပုံများထဲမှ Metadata ကိုဖယ်ရှားခြင်းဖြင့် သင့်ကိုယ်ရေးကိ"
  },
  {
    "path": "app/src/main/play/listings/my/google_play.xml",
    "chars": 2131,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Metadata ဖယ်ရှားခြင်း]]></title>\n  <fullDescription><"
  },
  {
    "path": "app/src/main/play/listings/my/short-description.txt",
    "chars": 80,
    "preview": "သင့်ဓာတ်ပုံများမှ Metadata ကိုဖယ်ရှားခြင်းဖြင့် သင့်ကိုယ်ရေးကိုယ်တာကို ကာကွယ်ပါ။"
  },
  {
    "path": "app/src/main/play/listings/my/title.txt",
    "chars": 21,
    "preview": "Metadata ဖယ်ရှားခြင်း"
  },
  {
    "path": "app/src/main/play/listings/nb-NO/full-description.txt",
    "chars": 1419,
    "preview": "<i>Beskytt ditt personvern ved å fjerne metadata fra bildene dine før du deler dem på Internett.</i>\n\n<h2><b>Funksjoner:"
  },
  {
    "path": "app/src/main/play/listings/nl/full-description.txt",
    "chars": 1559,
    "preview": "<i>Bescherm uw privacy door metadata te verwijderen uit je foto's voordat u ze deelt op internet.</i>\n\n<h2><b>Kenmerken:"
  },
  {
    "path": "app/src/main/play/listings/nl/google_play.xml",
    "chars": 2012,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Metadata Remover]]></title>\n  <fullDescription><![CDA"
  },
  {
    "path": "app/src/main/play/listings/nl/short-description.txt",
    "chars": 64,
    "preview": "Bescherm uw privacy door metadata te verwijderen van uw foto's!\n"
  },
  {
    "path": "app/src/main/play/listings/nl/title.txt",
    "chars": 22,
    "preview": "Metadata Verwijderaar\n"
  },
  {
    "path": "app/src/main/play/listings/pa-IN/full-description.txt",
    "chars": 1692,
    "preview": "<div><i>ਆਪਣੀਆਂ ਫ਼ੋਟੋਆਂ ਨੂੰ ਇੰਟਰਨੈੱਟ 'ਤੇ ਸਾਂਝਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ, ਉਹਨਾਂ ਵਿੱਚੋਂ ਮੈਟਾਡੇਟਾ ਨੂੰ ਹਟਾਕੇ ਆਪਣੀ ਪਰਦੇਦਾਰੀ ਦੀ ਰੱਖਿਆ ਕਰੋ"
  },
  {
    "path": "app/src/main/play/listings/pa-IN/google_play.xml",
    "chars": 1971,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[ਮੇਟਾਡਾਟਾ ਹਟਾਉਣ ਵਾਲਾ]]></title>\n  <fullDescription><!["
  },
  {
    "path": "app/src/main/play/listings/pa-IN/short-description.txt",
    "chars": 68,
    "preview": "ਆਪਣੀਆਂ ਫ਼ੋਟੋਆਂ ਵਿੱਚੋਂ ਮੈਟਾਡੇਟਾ ਨੂੰ ਹਟਾਕੇ ਆਪਣੀ ਪਰਦੇਦਾਰੀ ਦੀ ਰੱਖਿਆ ਕਰੋ!"
  },
  {
    "path": "app/src/main/play/listings/pa-IN/title.txt",
    "chars": 19,
    "preview": "ਮੇਟਾਡਾਟਾ ਹਟਾਉਣ ਵਾਲਾ"
  },
  {
    "path": "app/src/main/play/listings/pl-PL/full-description.txt",
    "chars": 1492,
    "preview": "<i>Chroń swoją prywatność usuwając metadane z twoich zdjęć, zanim udostępnisz je do internetu.</i>\n\n<h2><b>Funkcje:</b><"
  },
  {
    "path": "app/src/main/play/listings/pl-PL/short-description.txt",
    "chars": 66,
    "preview": "Chroń swoją prywatność przez usuwanie metadanych ze swoich zdjęć!\n"
  },
  {
    "path": "app/src/main/play/listings/pl-PL/title.txt",
    "chars": 20,
    "preview": "Usuwanie metadanych\n"
  },
  {
    "path": "app/src/main/play/listings/pt-BR/full-description.txt",
    "chars": 1608,
    "preview": "i>Proteja sua privacidade removendo metadados das suas fotos antes de compartilhá-las na Internet.</i>\n\n<h2><b>Recursos:"
  },
  {
    "path": "app/src/main/play/listings/pt-BR/google_play.xml",
    "chars": 275,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[]]></title>\n  <fullDescription><![CDATA[<div><i></i><"
  },
  {
    "path": "app/src/main/play/listings/pt-BR/short-description.txt",
    "chars": 60,
    "preview": "Proteja sua privacidade removendo metadados das suas fotos!\n"
  },
  {
    "path": "app/src/main/play/listings/pt-BR/title.txt",
    "chars": 23,
    "preview": "Removedor de Metadados\n"
  },
  {
    "path": "app/src/main/play/listings/pt-PT/full-description.txt",
    "chars": 1668,
    "preview": "<i>Proteja a sua privacidade ao remover metadados das suas fotos, antes de as partilhar na Internet.</i>\n\n<h2><b>Funcion"
  },
  {
    "path": "app/src/main/play/listings/pt-PT/google_play.xml",
    "chars": 2084,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Removedor de Metadados]]></title>\n  <fullDescription>"
  },
  {
    "path": "app/src/main/play/listings/pt-PT/short-description.txt",
    "chars": 71,
    "preview": "Proteja a sua privacidade removendo os metadados das suas fotografias!\n"
  },
  {
    "path": "app/src/main/play/listings/pt-PT/title.txt",
    "chars": 23,
    "preview": "Removedor de metadados\n"
  },
  {
    "path": "app/src/main/play/listings/ru/full-description.txt",
    "chars": 1495,
    "preview": "<i>Защитите свою конфиденциальность, удалив метаданные со своих фотографий, прежде чем выложить их в Интернет.</i>\n\n<h2>"
  },
  {
    "path": "app/src/main/play/listings/ru/google_play.xml",
    "chars": 2074,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<content>\n  <title><![CDATA[Восстановление метаданных]]></title>\n  <fullDescripti"
  },
  {
    "path": "app/src/main/play/listings/ru/short-description.txt",
    "chars": 73,
    "preview": "Защитите свою конфиденциальность, удалив метаданные со своих фотографий!\n"
  },
  {
    "path": "app/src/main/play/listings/ru/title.txt",
    "chars": 20,
    "preview": "Удаление метаданных\n"
  },
  {
    "path": "app/src/main/play/listings/si-LK/full-description.txt",
    "chars": 116,
    "preview": "<div><i></i><br><br><h2><b>විශේෂාංග:</b></h2><br><br><br><br><h2><b>තව දැනගන්න:</b></h2><br><br> 😽</i></i></i></div>"
  }
]

// ... and 244 more files (download for full content)

About this extraction

This page contains the full source code of the Crazy-Marvin/MetadataRemover GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 444 files (4.1 MB), approximately 1.1M tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!