From 65e224ef57b8a64e0fcce4c6e911fbb6f6848456 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 22 Apr 2024 22:43:21 +0200 Subject: [PATCH] #990 renaming: processor for hash (md5/sha1/sha256) --- CHANGELOG.md | 1 + .../channel/calls/MetadataFetchHandler.kt | 39 +++++++++++-- .../deckers/thibault/aves/utils/HashUtils.kt | 35 +++++++++++ lib/convert/metadata/fields.dart | 33 ++++++----- lib/convert/metadata/metadata_type.dart | 2 + lib/l10n/app_en.arb | 1 + lib/model/naming_pattern.dart | 58 ++++++++++++++----- lib/view/src/metadata/metadata_type.dart | 1 + .../entry_editors/rename_entry_set_page.dart | 21 ++++--- .../aves_model/lib/src/metadata/enums.dart | 2 + .../aves_model/lib/src/metadata/fields.dart | 7 +++ untranslated.json | 45 ++++++++++++++ 12 files changed, 204 insertions(+), 41 deletions(-) create mode 100644 android/app/src/main/kotlin/deckers/thibault/aves/utils/HashUtils.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2daf492f8..18b8d37c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - Cataloguing: identify Apple variant of HDR images +- Collection: allow using hash (md5/sha1/sha256) when bulk renaming - option to force using western arabic numerals for dates ### Changed diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index 714a2c2b9..8819f24ff 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -85,6 +85,7 @@ import deckers.thibault.aves.metadata.xmp.XMP.isMotionPhoto import deckers.thibault.aves.metadata.xmp.XMP.isPanorama import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.ContextUtils.queryContentPropValue +import deckers.thibault.aves.utils.HashUtils import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes.TIFF_EXTENSION_PATTERN @@ -104,6 +105,7 @@ import org.json.JSONObject import java.nio.charset.StandardCharsets import java.text.DecimalFormat import java.text.ParseException +import java.util.Locale import kotlin.math.roundToInt import kotlin.math.roundToLong @@ -1307,10 +1309,36 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { return } - val metadataMap = HashMap() + val metadataMap = HashMap() + + val hashFields = fields.filter { it.startsWith(HASH_FIELD_PREFIX) }.toSet() + metadataMap.putAll(getHashFields(uri, mimeType, sizeBytes, hashFields)) + + val exifFields = fields.filterNot { hashFields.contains(it) }.toSet() + metadataMap.putAll(getExifFields(uri, mimeType, sizeBytes, exifFields)) + + result.success(metadataMap) + } + + private fun getHashFields(uri: Uri, mimeType: String, sizeBytes: Long?, fields: Set): FieldMap { + val metadataMap = HashMap() + fields.forEach { field -> + val function = field.substringAfter(HASH_FIELD_PREFIX).lowercase(Locale.ROOT) + try { + Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input -> + metadataMap[field] = HashUtils.getHash(input, function) + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get hash for mimeType=$mimeType uri=$uri function=$function", e) + } + } + return metadataMap + } + + private fun getExifFields(uri: Uri, mimeType: String, sizeBytes: Long?, fields: Set): FieldMap { + val metadataMap = HashMap() if (fields.isEmpty() || isVideo(mimeType)) { - result.success(metadataMap) - return + return metadataMap } var foundExif = false @@ -1359,7 +1387,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { } } - result.success(metadataMap) + return metadataMap } companion object { @@ -1434,6 +1462,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { // additional media key private const val KEY_HAS_EMBEDDED_PICTURE = "Has Embedded Picture" + private const val HASH_FIELD_PREFIX = "hash" private const val VALUE_SKIPPED_DATA = "[skipped]" } -} \ No newline at end of file +} diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/HashUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/HashUtils.kt new file mode 100644 index 000000000..c90e317fa --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/HashUtils.kt @@ -0,0 +1,35 @@ +package deckers.thibault.aves.utils + +import java.io.InputStream +import java.math.BigInteger +import java.security.MessageDigest + +object HashUtils { + fun getHash(input: InputStream, algorithmKey: String): String { + val algorithm = toMessageDigestAlgorithm(algorithmKey) + val digest = MessageDigest.getInstance(algorithm) + val buffer = ByteArray(1 shl 14) + var read: Int + while ((input.read(buffer).also { read = it }) > 0) { + digest.update(buffer, 0, read) + } + val md5sum = digest.digest() + val output = BigInteger(1, md5sum).toString(16) + + return when (algorithm) { + "MD5" -> output.padStart(32, '0') // 128 bits = 32 hex digits + "SHA-1" -> output.padStart(40, '0') // 160 bits = 40 hex digits + "SHA-256" -> output.padStart(64, '0') // 256 bits = 64 hex digits + else -> throw IllegalArgumentException("unsupported hash algorithm: $algorithmKey") + } + } + + private fun toMessageDigestAlgorithm(algorithmKey: String): String { + return when (algorithmKey) { + "md5" -> "MD5" + "sha1" -> "SHA-1" + "sha256" -> "SHA-256" + else -> throw IllegalArgumentException("unsupported hash algorithm: $algorithmKey") + } + } +} diff --git a/lib/convert/metadata/fields.dart b/lib/convert/metadata/fields.dart index a0509b563..659f6bcca 100644 --- a/lib/convert/metadata/fields.dart +++ b/lib/convert/metadata/fields.dart @@ -53,23 +53,30 @@ extension ExtraMetadataFieldConvert on MetadataField { return MetadataType.mp4; case MetadataField.xmpXmpCreateDate: return MetadataType.xmp; + case MetadataField.hashMd5: + case MetadataField.hashSha1: + case MetadataField.hashSha256: + return MetadataType.file; } } String? get toPlatform { - if (type == MetadataType.exif) { - return _toExifInterfaceTag(); - } else { - switch (this) { - case MetadataField.mp4GpsCoordinates: - return 'gpsCoordinates'; - case MetadataField.mp4RotationDegrees: - return 'rotationDegrees'; - case MetadataField.mp4Xmp: - return 'xmp'; - default: - return null; - } + switch (type) { + case MetadataType.exif: + return _toExifInterfaceTag(); + case MetadataType.file: + return name; + default: + switch (this) { + case MetadataField.mp4GpsCoordinates: + return 'gpsCoordinates'; + case MetadataField.mp4RotationDegrees: + return 'rotationDegrees'; + case MetadataField.mp4Xmp: + return 'xmp'; + default: + return null; + } } } diff --git a/lib/convert/metadata/metadata_type.dart b/lib/convert/metadata/metadata_type.dart index 63259e7ba..5c7f40851 100644 --- a/lib/convert/metadata/metadata_type.dart +++ b/lib/convert/metadata/metadata_type.dart @@ -23,6 +23,8 @@ extension ExtraMetadataTypeConvert on MetadataType { return 'photoshop_irb'; case MetadataType.xmp: return 'xmp'; + case MetadataType.file: + return 'file'; } } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f8e52b25f..d803fb63e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -440,6 +440,7 @@ "renameEntrySetPagePreviewSectionTitle": "Preview", "renameProcessorCounter": "Counter", + "renameProcessorHash": "Hash", "renameProcessorName": "Name", "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Delete this album and the item in it?} other{Delete this album and the {count} items in it?}}", diff --git a/lib/model/naming_pattern.dart b/lib/model/naming_pattern.dart index ab988fef5..c31c16143 100644 --- a/lib/model/naming_pattern.dart +++ b/lib/model/naming_pattern.dart @@ -39,18 +39,6 @@ class NamingPattern { final processorKey = match.group(1); final processorOptions = match.group(3); switch (processorKey) { - case DateNamingProcessor.key: - if (processorOptions != null) { - processors.add(DateNamingProcessor(processorOptions.trim(), locale)); - } - case TagsNamingProcessor.key: - processors.add(TagsNamingProcessor(processorOptions?.trim() ?? '')); - case MetadataFieldNamingProcessor.key: - if (processorOptions != null) { - processors.add(MetadataFieldNamingProcessor(processorOptions.trim())); - } - case NameNamingProcessor.key: - processors.add(const NameNamingProcessor()); case CounterNamingProcessor.key: int? start, padding; _applyProcessorOptions(processorOptions, (key, value) { @@ -65,6 +53,22 @@ class NamingPattern { } }); processors.add(CounterNamingProcessor(start: start ?? defaultCounterStart, padding: padding ?? defaultCounterPadding)); + case DateNamingProcessor.key: + if (processorOptions != null) { + processors.add(DateNamingProcessor(processorOptions.trim(), locale)); + } + case HashNamingProcessor.key: + if (processorOptions != null) { + processors.add(HashNamingProcessor(processorOptions.trim())); + } + case MetadataFieldNamingProcessor.key: + if (processorOptions != null) { + processors.add(MetadataFieldNamingProcessor(processorOptions.trim())); + } + case NameNamingProcessor.key: + processors.add(const NameNamingProcessor()); + case TagsNamingProcessor.key: + processors.add(TagsNamingProcessor(processorOptions?.trim() ?? '')); default: debugPrint('unsupported naming processor: ${match.group(0)}'); } @@ -106,6 +110,8 @@ class NamingPattern { switch (processorKey) { case DateNamingProcessor.key: return '<$processorKey, yyyyMMdd-HHmmss>'; + case HashNamingProcessor.key: + return '<$processorKey, md5>'; case TagsNamingProcessor.key: return '<$processorKey, ->'; case CounterNamingProcessor.key: @@ -204,9 +210,7 @@ class MetadataFieldNamingProcessor extends NamingProcessor { } @override - Set getRequiredFields() { - return {field}.whereNotNull().toSet(); - } + Set getRequiredFields() => {field}.whereNotNull().toSet(); @override String? process(AvesEntry entry, int index, Map fieldValues) { @@ -247,3 +251,27 @@ class CounterNamingProcessor extends NamingProcessor { @override String? process(AvesEntry entry, int index, Map fieldValues) => '${index + start}'.padLeft(padding, '0'); } + +@immutable +class HashNamingProcessor extends NamingProcessor { + static const key = 'hash'; + static const optionFunction = 'function'; + + late final MetadataField? function; + + @override + List get props => [function]; + + HashNamingProcessor(String function) { + final lowerField = 'hash${function.toLowerCase()}'; + this.function = MetadataField.values.firstWhereOrNull((v) => v.name.toLowerCase() == lowerField); + } + + @override + Set getRequiredFields() => {function}.whereNotNull().toSet(); + + @override + String? process(AvesEntry entry, int index, Map fieldValues) { + return fieldValues[function?.toPlatform]?.toString(); + } +} diff --git a/lib/view/src/metadata/metadata_type.dart b/lib/view/src/metadata/metadata_type.dart index 33b085901..45db454f4 100644 --- a/lib/view/src/metadata/metadata_type.dart +++ b/lib/view/src/metadata/metadata_type.dart @@ -14,6 +14,7 @@ extension ExtraMetadataTypeView on MetadataType { MetadataType.mp4 => 'MP4', MetadataType.photoshopIrb => 'Photoshop', MetadataType.xmp => 'XMP', + MetadataType.file => 'File', }; } } diff --git a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart index 61e7739a0..ea944f59f 100644 --- a/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart +++ b/lib/widgets/dialogs/entry_editors/rename_entry_set_page.dart @@ -119,14 +119,19 @@ class _RenameEntrySetPageState extends State { icon: AIcons.more, title: MaterialLocalizations.of(context).moreButtonTooltip, items: [ - MetadataField.exifMake, - MetadataField.exifModel, - ] - .map((field) => PopupMenuItem( - value: MetadataFieldNamingProcessor.keyWithField(field), - child: MenuRow(text: field.title), - )) - .toList(), + ...[ + MetadataField.exifMake, + MetadataField.exifModel, + ] + .map((field) => PopupMenuItem( + value: MetadataFieldNamingProcessor.keyWithField(field), + child: MenuRow(text: field.title), + )), + PopupMenuItem( + value: HashNamingProcessor.key, + child: MenuRow(text: l10n.renameProcessorHash), + ) + ], ), ]; }, diff --git a/plugins/aves_model/lib/src/metadata/enums.dart b/plugins/aves_model/lib/src/metadata/enums.dart index 7e933d5b2..70849fd55 100644 --- a/plugins/aves_model/lib/src/metadata/enums.dart +++ b/plugins/aves_model/lib/src/metadata/enums.dart @@ -45,6 +45,8 @@ enum MetadataType { photoshopIrb, // XMP: https://en.wikipedia.org/wiki/Extensible_Metadata_Platform xmp, + // others (hash, etc.) + file, } class MetadataTypes { diff --git a/plugins/aves_model/lib/src/metadata/fields.dart b/plugins/aves_model/lib/src/metadata/fields.dart index 706687483..0f5c0f50e 100644 --- a/plugins/aves_model/lib/src/metadata/fields.dart +++ b/plugins/aves_model/lib/src/metadata/fields.dart @@ -7,6 +7,7 @@ enum MetadataSyntheticField { } enum MetadataField { + // exif exifDate, exifDateOriginal, exifDateDigitized, @@ -46,10 +47,16 @@ enum MetadataField { exifMake, exifModel, exifUserComment, + // mp4 mp4GpsCoordinates, mp4RotationDegrees, mp4Xmp, + // xmp xmpXmpCreateDate, + // file + hashMd5, + hashSha1, + hashSha256, } class MetadataFields { diff --git a/untranslated.json b/untranslated.json index 6713af573..e4a556d8a 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1,9 +1,11 @@ { "ar": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], "be": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -241,6 +243,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -677,6 +680,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -793,6 +797,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -1229,6 +1234,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "collectionActionSetHome", "setHomeCustomCollection", "settingsThumbnailShowHdrIcon", @@ -1494,6 +1500,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -1930,6 +1937,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -1939,6 +1947,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "castDialogTitle", "aboutDataUsageSectionTitle", "aboutDataUsageData", @@ -1956,6 +1965,7 @@ ], "es": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -1964,6 +1974,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -2011,6 +2022,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -2527,6 +2539,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -2959,6 +2972,7 @@ ], "fr": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -3089,6 +3103,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -3775,6 +3790,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -4427,6 +4443,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -4859,10 +4876,12 @@ ], "hu": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], "id": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -4871,6 +4890,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -4879,6 +4899,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "collectionActionSetHome", "setHomeCustomCollection", "settingsThumbnailShowHdrIcon", @@ -4911,6 +4932,7 @@ "videoResumptionModeAlways", "widgetTapUpdateWidget", "vaultBinUsageDialogMessage", + "renameProcessorHash", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "castDialogTitle", @@ -5172,6 +5194,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -5604,6 +5627,7 @@ ], "ko": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -5661,6 +5685,7 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "renameProcessorHash", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "tooManyItemsErrorDialogMessage", @@ -5964,6 +5989,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -6407,6 +6433,7 @@ "widgetOpenPageHome", "widgetOpenPageCollection", "widgetOpenPageViewer", + "renameProcessorHash", "menuActionConfigureView", "castDialogTitle", "aboutDataUsageClearCache", @@ -6532,6 +6559,7 @@ "settingsVideoEnablePip", "widgetTapUpdateWidget", "patternDialogEnter", + "renameProcessorHash", "castDialogTitle", "aboutDataUsageInternal", "aboutDataUsageExternal", @@ -6593,6 +6621,7 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "renameProcessorHash", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "tooManyItemsErrorDialogMessage", @@ -6655,6 +6684,7 @@ "widgetTapUpdateWidget", "authenticateToConfigureVault", "authenticateToUnlockVault", + "renameProcessorHash", "viewDialogSortSectionTitle", "viewDialogReverseSortOrder", "castDialogTitle", @@ -6922,6 +6952,7 @@ "renameEntrySetPagePatternFieldLabel", "renameEntrySetPageInsertTooltip", "renameProcessorCounter", + "renameProcessorHash", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", "exportEntryDialogFormat", @@ -7306,10 +7337,12 @@ ], "pl": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], "pt": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -7318,10 +7351,12 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], "ru": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -7582,6 +7617,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -8018,6 +8054,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -8276,6 +8313,7 @@ "renameEntrySetPageInsertTooltip", "renameEntrySetPagePreviewSectionTitle", "renameProcessorCounter", + "renameProcessorHash", "renameProcessorName", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", @@ -8727,6 +8765,7 @@ "renameAlbumDialogLabel", "renameAlbumDialogLabelAlreadyExistsHelper", "renameEntrySetPageTitle", + "renameProcessorHash", "deleteSingleAlbumConfirmationDialogMessage", "deleteMultiAlbumConfirmationDialogMessage", "editEntryDialogTargetFieldsHeader", @@ -9076,6 +9115,7 @@ "authenticateToConfigureVault", "authenticateToUnlockVault", "vaultBinUsageDialogMessage", + "renameProcessorHash", "exportEntryDialogQuality", "exportEntryDialogWriteMetadata", "editEntryDateDialogExtractFromTitle", @@ -9430,10 +9470,12 @@ ], "tr": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], "uk": [ + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -9442,6 +9484,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -9450,6 +9493,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ], @@ -9458,6 +9502,7 @@ "videoActionABRepeat", "videoRepeatActionSetStart", "videoRepeatActionSetEnd", + "renameProcessorHash", "settingsForceWesternArabicNumeralsTile" ] }