Write tag filter based on tag kind/'Level'
This commit is contained in:
1 parent
76721c6893
commit
b3ced063ef
1 file changed
+169
-43
+169
-43
@@ -1,23 +1,68 @@
|
||||
use crate::tags::{PhotometricInterpretation, PlanarConfiguration};
|
||||
|
||||
/// A filter of tags copied in
|
||||
/// [`DirectoryEncoder::write_metadata_from`][`super::DirectoryEncoder::write_metadata_from`].
|
||||
pub struct TagFilter {
|
||||
unknown: bool,
|
||||
filter_level: FilterLevel,
|
||||
}
|
||||
|
||||
impl TagFilter {
|
||||
/// Return a filter that allows all tags from metadata subdirectories.
|
||||
pub fn allow_all_in_subdirectories() -> &'static TagFilter {
|
||||
static INSTANCE: TagFilter = TagFilter { unknown: true };
|
||||
static INSTANCE: TagFilter = TagFilter {
|
||||
filter_level: FilterLevel::All,
|
||||
};
|
||||
&INSTANCE
|
||||
}
|
||||
|
||||
/// Return a filter that has an explicit allow list of tags, for each of a known list of
|
||||
/// metadata subdirectories.
|
||||
pub fn allow_known() -> &'static TagFilter {
|
||||
static INSTANCE: TagFilter = TagFilter { unknown: false };
|
||||
pub fn only_known_tags() -> &'static TagFilter {
|
||||
static INSTANCE: TagFilter = TagFilter {
|
||||
filter_level: FilterLevel::Known,
|
||||
};
|
||||
&INSTANCE
|
||||
}
|
||||
|
||||
/// Return a filter that attempts to remove identifying tags. This is stricter than
|
||||
/// [`Self::only_known_tags`] and excludes many tags except those describing the image data
|
||||
/// layout.
|
||||
///
|
||||
/// **Note that it comes with *no warranty*.** although this will remove tags with prejudice.
|
||||
pub fn attempt_at_privacy() -> &'static TagFilter {
|
||||
static INSTANCE: TagFilter = TagFilter {
|
||||
filter_level: FilterLevel::Privacy,
|
||||
};
|
||||
&INSTANCE
|
||||
}
|
||||
|
||||
/// FIXME: take a configuration by which to filter out complete metadata kinds.
|
||||
/// FIXME: when we iterate over a whole directory of tags, this iteration is more expensive
|
||||
/// than necessary. Tags within a directory should be ordered. We can thus perform a binary set
|
||||
/// join which is more efficient than checking the individual filter on each. Does not matter
|
||||
/// much in most cases though.
|
||||
pub(crate) fn filter_primary(&self, tag: u16) -> Level {
|
||||
let as_exif = self.filter_primary_exif(tag);
|
||||
|
||||
if !matches!(as_exif, Level::Unknown) {
|
||||
return as_exif;
|
||||
}
|
||||
|
||||
let as_gps = self.filter_primary_gps(tag);
|
||||
|
||||
if !matches!(as_gps, Level::Unknown) {
|
||||
return as_gps;
|
||||
}
|
||||
|
||||
let as_icc = self.filter_icc(tag);
|
||||
|
||||
if !matches!(as_icc, Level::Unknown) {
|
||||
return as_icc;
|
||||
}
|
||||
|
||||
Level::Unknown
|
||||
}
|
||||
|
||||
/// As per EXIF V3.0 tags, Primary Image (0th IFD).
|
||||
pub(crate) fn filter_primary_exif(&self, tag: u16) -> Level {
|
||||
match tag {
|
||||
@@ -25,20 +70,26 @@ impl TagFilter {
|
||||
0x0100..=0x0103 => Level::SampleLayout,
|
||||
// photometric interpretation
|
||||
0x0106 => Level::SampleLayout,
|
||||
0x010e..=0x0110 => Level::Basic,
|
||||
// data tied to the camera and customized description, potentially PI.
|
||||
0x010f..=0x0110 => Level::Value,
|
||||
// strip offsets
|
||||
0x0111 => Level::SampleLayout,
|
||||
0x0112 => Level::Basic,
|
||||
// orientation
|
||||
0x0112 => Level::Value,
|
||||
// strip byte counts, samples
|
||||
0x0115..=0x0117 => Level::SampleLayout,
|
||||
0x011a..=0x011b => Level::Basic,
|
||||
// pixel dimensions (ratio). Should be generic enough.
|
||||
0x011a..=0x011b => Level::Value,
|
||||
// planar configuration
|
||||
0x011c => Level::Photometric(Photometric::Planar),
|
||||
0x0128 => Level::Basic,
|
||||
// units for 0x011a,0x011b
|
||||
0x0128 => Level::Value,
|
||||
// transfer function
|
||||
0x012d => Level::Photometric(Photometric::All),
|
||||
0x0131..=0x0132 => Level::Basic,
|
||||
0x013b => Level::Basic,
|
||||
// file data time, software pipeline. I'm wary of privacy implications.
|
||||
0x0131..=0x0132 => Level::Value,
|
||||
// artist, clearly PI unless you explicitly wish it to be attributed.
|
||||
0x013b => Level::Value,
|
||||
// Whitepoint, Chromaticities
|
||||
0x013e..=0x013f => Level::Photometric(Photometric::All),
|
||||
// The table lists these as Not allowed for anything but we may have a choice here for
|
||||
@@ -46,7 +97,8 @@ impl TagFilter {
|
||||
0x0201..=0x0202 => Level::Special(Special::JpegThumbnail),
|
||||
0x0211..=0x0213 => Level::Photometric(Photometric::YCbCr),
|
||||
0x0214 => Level::Photometric(Photometric::All),
|
||||
0x8298 => Level::Basic,
|
||||
// Copyright holder (not same as artist).
|
||||
0x8298 => Level::Value,
|
||||
0x8769 => Level::Ifd(SubifdKind::ExifPrivate),
|
||||
_ => Level::Unknown,
|
||||
}
|
||||
@@ -61,54 +113,57 @@ impl TagFilter {
|
||||
|
||||
pub(crate) fn filter_icc(&self, tag: u16) -> Level {
|
||||
match tag {
|
||||
0x8ff3 => Level::Basic,
|
||||
0x8ff3 => Level::Value,
|
||||
_ => Level::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn filter_exif_private(&self, tag: u16) -> Level {
|
||||
match tag {
|
||||
0x829a => Level::Basic,
|
||||
0x829d => Level::Basic,
|
||||
0x8822 => Level::Basic,
|
||||
0x8824 => Level::Basic,
|
||||
0x8827 => Level::Basic,
|
||||
0x8828 => Level::Basic,
|
||||
0x8830..=0x8835 => Level::Basic,
|
||||
0x9000 => Level::Basic,
|
||||
0x9003..=0x9004 => Level::Basic,
|
||||
0x9010..=0x9012 => Level::Basic,
|
||||
// Maybe this is not personal identifying but some equipment may be very specifically
|
||||
// known through these. And it's not critical to displaying it correctly.
|
||||
0x829a => Level::Value,
|
||||
0x829d => Level::Value,
|
||||
0x8822 => Level::Value,
|
||||
0x8824 => Level::Value,
|
||||
0x8827 => Level::Value,
|
||||
// Opto-electric coefficients
|
||||
0x8828 => Level::Value,
|
||||
0x8830..=0x8835 => Level::Value,
|
||||
0x9000 => Level::Value,
|
||||
0x9003..=0x9004 => Level::Value,
|
||||
0x9010..=0x9012 => Level::Value,
|
||||
// Not allowed for uncompressed data!
|
||||
0x9101..=0x9102 => Level::Special(Special::CompressedExclusive),
|
||||
0x9201..=0x9209 => Level::Basic,
|
||||
0x920a => Level::Basic,
|
||||
0x9214 => Level::Basic,
|
||||
0x927c => Level::Basic,
|
||||
0x9286 => Level::Basic,
|
||||
0x9290..=0x9292 => Level::Basic,
|
||||
0x9400..=0x9405 => Level::Basic,
|
||||
0xa000 => Level::Basic,
|
||||
0x9201..=0x9209 => Level::Value,
|
||||
0x920a => Level::Value,
|
||||
0x9214 => Level::Value,
|
||||
0x927c => Level::Value,
|
||||
0x9286 => Level::Value,
|
||||
0x9290..=0x9292 => Level::Value,
|
||||
0x9400..=0x9405 => Level::Value,
|
||||
0xa000 => Level::Value,
|
||||
0xa001 => Level::Photometric(Photometric::All),
|
||||
0xa002..=0xa003 => Level::Special(Special::CompressedExclusive),
|
||||
0xa004 => Level::Basic,
|
||||
0xa004 => Level::Value,
|
||||
0xa005 => Level::Ifd(SubifdKind::Interoperability),
|
||||
0xa20b..=0xa20c => Level::Basic,
|
||||
0xa20e..=0xa210 => Level::Basic,
|
||||
0xa214..=0xa215 => Level::Basic,
|
||||
0xa217 => Level::Basic,
|
||||
0xa300..=0xa302 => Level::Basic,
|
||||
0xa401..=0xa40c => Level::Basic,
|
||||
0xa420 => Level::Basic,
|
||||
0xa430..=0xa43c => Level::Basic,
|
||||
0xa460..=0xa462 => Level::Basic,
|
||||
0xa20b..=0xa20c => Level::Value,
|
||||
0xa20e..=0xa210 => Level::Value,
|
||||
0xa214..=0xa215 => Level::Value,
|
||||
0xa217 => Level::Value,
|
||||
0xa300..=0xa302 => Level::Value,
|
||||
0xa401..=0xa40c => Level::Value,
|
||||
0xa420 => Level::Value,
|
||||
0xa430..=0xa43c => Level::Value,
|
||||
0xa460..=0xa462 => Level::Value,
|
||||
0xa500 => Level::Photometric(Photometric::All),
|
||||
_ => Level::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_gps_private(&self, tag: u16) -> Level {
|
||||
pub(crate) fn filter_gps_private(&self, tag: u16) -> Level {
|
||||
match tag {
|
||||
0x0000..=0x001f => Level::Basic,
|
||||
0x0000..=0x001f => Level::Value,
|
||||
_ => Level::Unknown,
|
||||
}
|
||||
}
|
||||
@@ -130,6 +185,61 @@ impl TagFilter {
|
||||
pub(crate) fn filter_secondary_gps(&self, tag: u16) -> Level {
|
||||
self.filter_primary_gps(tag)
|
||||
}
|
||||
|
||||
pub(crate) fn should_keep_on_level(
|
||||
&self,
|
||||
level: Level,
|
||||
target: &super::EncodeMetadataApplicability,
|
||||
) -> Choice {
|
||||
match level {
|
||||
Level::Unknown if matches!(self.filter_level, FilterLevel::All) => Choice::Ok,
|
||||
Level::SampleLayout => Choice::Ok,
|
||||
Level::Ifd(subifd_kind) => Choice::Descend(subifd_kind),
|
||||
Level::Photometric(photometric) => {
|
||||
match (photometric, target.planar, target.photometric) {
|
||||
(Photometric::All, _, _) => Choice::Ok,
|
||||
(Photometric::Planar, Some(PlanarConfiguration::Planar), _) => Choice::Ok,
|
||||
(Photometric::Planar, None, _)
|
||||
if matches!(self.filter_level, FilterLevel::All) =>
|
||||
{
|
||||
Choice::Ok
|
||||
}
|
||||
(Photometric::YCbCr, _, Some(PhotometricInterpretation::YCbCr)) => Choice::Ok,
|
||||
(Photometric::YCbCr, _, None)
|
||||
if matches!(self.filter_level, FilterLevel::All) =>
|
||||
{
|
||||
Choice::Ok
|
||||
}
|
||||
_ => Choice::Discard,
|
||||
}
|
||||
}
|
||||
Level::Value if matches!(self.filter_level, FilterLevel::Privacy) => Choice::Discard,
|
||||
Level::Value => Choice::Ok,
|
||||
Level::Special(Special::JpegThumbnail)
|
||||
if matches!(self.filter_level, FilterLevel::Privacy) =>
|
||||
{
|
||||
// Contains arbitrary unverified other data, so, no, we do not heed backward
|
||||
// compatibility of a tag that should no longer be in active use if possible.
|
||||
Choice::Discard
|
||||
}
|
||||
Level::Special(special) => Choice::Special(special),
|
||||
_ => Choice::Discard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Choice {
|
||||
Ok,
|
||||
Descend(SubifdKind),
|
||||
Discard,
|
||||
Special(Special),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum FilterLevel {
|
||||
All,
|
||||
Known,
|
||||
Privacy,
|
||||
}
|
||||
|
||||
/// The level of support for a value.
|
||||
@@ -138,12 +248,27 @@ pub(crate) enum Level {
|
||||
Unknown,
|
||||
/// Value describes the image data layout, which we will not want to write into the encoder's
|
||||
/// directory where the image data is written later.
|
||||
///
|
||||
/// These are treated as privacy preserving.
|
||||
SampleLayout,
|
||||
/// Value is an IFD and must be copied by making a subdirectory.
|
||||
///
|
||||
/// These are treated as privacy preserving, independent of the tags within the subdirectory
|
||||
/// which may not be. (For instance the presence of an empty directory is fine but we will omit
|
||||
/// a GPS anyways).
|
||||
Ifd(SubifdKind),
|
||||
/// Value is related to photometric interpretation and should not be copied when the target
|
||||
/// does not match the expected interpretation.
|
||||
///
|
||||
/// These are *generally* treated as privacy preserving.
|
||||
Photometric(Photometric),
|
||||
Basic,
|
||||
/// This is a value we know of.
|
||||
/// FIXME: we do not differentiate by personal identifiable information and otherwise which is
|
||||
/// quite important for some variations of copying metadata.
|
||||
Value,
|
||||
/// We recognize the tag but it can not be copied by itself through normal means.
|
||||
///
|
||||
/// Privacy treatment depends on the tag.
|
||||
Special(Special),
|
||||
}
|
||||
|
||||
@@ -165,6 +290,7 @@ pub(crate) enum SubifdKind {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Special {
|
||||
/// Thumbnail which we regard as PI since we do not deep-clean the JPEG structure.
|
||||
JpegThumbnail,
|
||||
/// May only occur in compressed (JPEG APP segments) data.
|
||||
CompressedExclusive,
|
||||
|
||||
Reference in new issue
Block a user