# DrewMediaMetadataExtractor.java

    /*
     * Copyright (C) 2000 - 2024 Silverpeas
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU Affero General Public License as
     * published by the Free Software Foundation, either version 3 of the
     * License, or (at your option) any later version.
     *
     * As a special exception to the terms and conditions of version 3.0 of
     * the GPL, you may redistribute this Program in connection with Free/Libre
     * Open Source Software ("FLOSS") applications as described in Silverpeas's
     * FLOSS exception. You should have received a copy of the text describing
     * the FLOSS exception, and it is also available here:
     * "http://www.silverpeas.org/docs/core/legal/floss_exception.html"
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     * GNU Affero General Public License for more details.
     *
     * You should have received a copy of the GNU Affero General Public License
     * along with this program. If not, see <https://www.gnu.org/licenses/>.
     */
    package org.silverpeas.components.gallery.media;

    import com.drew.imaging.ImageMetadataReader;
    import com.drew.imaging.ImageProcessingException;
    import com.drew.metadata.Metadata;
    import com.drew.metadata.exif.ExifIFD0Descriptor;
    import com.drew.metadata.exif.ExifIFD0Directory;
    import com.drew.metadata.iptc.IptcDirectory;
    import org.silverpeas.components.gallery.model.MetaData;
    import org.silverpeas.core.i18n.I18NHelper;
    import org.silverpeas.core.util.ArrayUtil;
    import org.silverpeas.core.util.Charsets;
    import org.silverpeas.core.util.EncodingUtil;

    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.TimeZone;

    import static com.drew.metadata.iptc.IptcDirectory.*;

    /**
     * @author ehugonnet
     */
    public class DrewMediaMetadataExtractor extends AbstractMediaMetadataExtractor {

      public DrewMediaMetadataExtractor(String instanceId) {
        init(instanceId);
      }

      @Override
      public List<MetaData> extractImageExifMetaData(File image)
          throws MediaMetadataException {
        return extractImageExifMetaData(image, I18NHelper.getDefaultLanguage());
      }

      @Override
      public List<MetaData> extractImageExifMetaData(File image, String lang)
          throws MediaMetadataException {
        try {
          List<MetaData> result = new ArrayList<>();
          // lire le fichier des properties
          // 1. Traitement des metadata EXIF
          Metadata metadata = ImageMetadataReader.readMetadata(image);
          ExifIFD0Directory exifDirectory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
          ExifIFD0Descriptor descriptor = new ExifIFD0Descriptor(exifDirectory);
          String value;
          if (exifDirectory != null) {
            for (ExifProperty property : imageProperties) {
              value = fetchLabel(exifDirectory, descriptor, property);
              if (value != null) {
                addLabelToMediaMetadata(lang, result, value, property);
              }
            }
          }
          return result;
        } catch (IOException | ImageProcessingException ex) {
          throw new MediaMetadataException(ex);
        }
      }

      private void addLabelToMediaMetadata(final String lang, final List<MetaData> result,
          final String value, final ExifProperty property) {
        // ajout de cette metadata à la photo
        MetaData metaData = new MetaData(value);
        metaData.setLabel(property.getLabel(lang));
        metaData.setProperty(property.getProperty() + "");
        result.add(metaData);
      }

      private String fetchLabel(final ExifIFD0Directory exifDirectory,
          final ExifIFD0Descriptor descriptor, final ExifProperty property) {
        // rechercher la valeur de la metadata "label"
        final String value;
        int currentMetadata = property.getProperty();
        switch (currentMetadata) {
          case ExifIFD0Directory.TAG_WIN_AUTHOR:
            value = descriptor.getWindowsAuthorDescription();
            break;
          case ExifIFD0Directory.TAG_WIN_COMMENT:
            value = descriptor.getWindowsCommentDescription();
            break;

          case ExifIFD0Directory.TAG_WIN_KEYWORDS:
            value = descriptor.getWindowsKeywordsDescription();
            break;

          case ExifIFD0Directory.TAG_WIN_SUBJECT:
            value = descriptor.getWindowsSubjectDescription();
            break;

          case ExifIFD0Directory.TAG_WIN_TITLE:
            value = descriptor.getWindowsTitleDescription();
            break;

          case ExifIFD0Directory.TAG_RESOLUTION_UNIT:
            value = getExifDirectoryValue(exifDirectory, currentMetadata);
            break;

          default:
            value = exifDirectory.getString(currentMetadata);
        }
        return value;
      }

      private String getExifDirectoryValue(final ExifIFD0Directory exifDirectory,
          final int currentMetadata) {
        String value = exifDirectory.getString(currentMetadata);
        if("2".equals(value)) {
          //dots per inch
          value = "DPI";
        } else if ("3".equals(value)) {
          //dots per cm
          value = "DPC";
        }
        return value;
      }

      @Override
      public List<MetaData> extractImageIptcMetaData(File image)
          throws MediaMetadataException, IOException {
        return extractImageIptcMetaData(image, I18NHelper.getDefaultLanguage());
      }

      @Override
      public List<MetaData> extractImageIptcMetaData(File image, String lang)
          throws IOException, MediaMetadataException {
        try {
          final int defaultSize = 1024;
          List<MetaData> result = new ArrayList<>();
          Metadata metadata = ImageMetadataReader.readMetadata(image);
          ByteArrayOutputStream forEncodingDetection = new ByteArrayOutputStream(defaultSize);
          IptcDirectory iptcDirectory = metadata.getFirstDirectoryOfType(IptcDirectory.class);
          String iptcCharset = getIptcCharset(iptcDirectory);
          for (IptcProperty iptcProperty : imageIptcProperties) {
            switch (iptcProperty.getProperty()) {
              case TAG_RELEASE_DATE:
              case TAG_DATE_CREATED:
              case TAG_KEYWORDS:
                addStringMetaData(result, iptcDirectory, iptcProperty, lang, forEncodingDetection);
                break;
              default:
                addMetaData(result, iptcDirectory, iptcProperty, lang, forEncodingDetection);
                break;
            }
          }
          String defaultEncoding = Charsets.UTF_8.name();
          if (iptcCharset != null) {
            defaultEncoding = iptcCharset;
          }
          String encoding =
              EncodingUtil.detectStringEncoding(forEncodingDetection.toByteArray(), defaultEncoding);

          for (MetaData metaData : result) {
            metaData.convert(encoding);
          }
          return result;
        } catch (ImageProcessingException ex) {
          throw new MediaMetadataException(ex);
        }
      }

      private void addStringMetaData(List<MetaData> metadata, IptcDirectory iptcDirectory,
          IptcProperty iptcProperty, String lang, ByteArrayOutputStream forEncodingDetection)
          throws IOException {
        String value = getIptcStringValue(iptcDirectory, iptcProperty.getProperty());
        if (value != null) {
          MetaData meta = new MetaData(value.getBytes());
          meta.setLabel(iptcProperty.getLabel(lang));
          meta.setProperty(String.valueOf(iptcProperty.getProperty()));
          if (iptcProperty.isDate()) {
            meta.setDate(true);
            meta.setDateValue(iptcDirectory.getDate(iptcProperty.getProperty(), TimeZone.getDefault()));
          } else {
            forEncodingDetection.write(value.getBytes());
          }
          metadata.add(meta);
        }
      }

      private void addMetaData(List<MetaData> metadata, IptcDirectory iptcDirectory,
          IptcProperty iptcProperty, String lang, ByteArrayOutputStream forEncodingDetection)
          throws IOException {
        byte[] data = getIptcValue(iptcDirectory, iptcProperty.getProperty());
        if (data != null && !ArrayUtil.isEmpty(data)) {
          MetaData meta = new MetaData(data);
          meta.setLabel(iptcProperty.getLabel(lang));
          meta.setProperty(String.valueOf(iptcProperty.getProperty()));
          if (iptcProperty.isDate() && iptcDirectory != null) {
            meta.setDate(true);
            meta.setDateValue(iptcDirectory.getDate(iptcProperty.getProperty(), TimeZone.getDefault()));
          } else {
            forEncodingDetection.write(data);
          }
          metadata.add(meta);
        }
      }

      private byte[] getIptcValue(IptcDirectory iptcDirectory, int iptcTag) {
        if (iptcDirectory != null && iptcDirectory.containsTag(iptcTag)) {
          return iptcDirectory.getByteArray(iptcTag);
        }
        return new byte[0];
      }

      private String getIptcStringValue(IptcDirectory iptcDirectory, int iptcTag) {
        if (iptcDirectory != null && iptcDirectory.containsTag(iptcTag)) {
          return iptcDirectory.getString(iptcTag);
        }
        return null;
      }

      private String getIptcCharset(IptcDirectory iptcDirectory) {
        if (iptcDirectory != null && iptcDirectory.containsTag(TAG_CODED_CHARACTER_SET)) {
          byte[] data = iptcDirectory.getByteArray(TAG_CODED_CHARACTER_SET);
          if (data != null) {
            String escapeCode = new String(data, Charsets.UTF_8);
            if ("%G".equals(escapeCode)) {
              return Charsets.UTF_8.name();
            }
          }
        }
        return null;
      }
    }