content: /*
   * Copyright 2002-2019 Drew Noakes and contributors
   *
   *    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
   *
   *        https://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.
   *
   * More information about this project is available at:
   *
   *    https://drewnoakes.com/code/exif/
   *    https://github.com/drewnoakes/metadata-extractor
   */
  package com.drew.metadata.exif.makernotes;
  
  import com.drew.lang.ByteArrayReader;
  import com.drew.lang.Charsets;
  import com.drew.lang.RandomAccessReader;
  import com.drew.lang.annotations.NotNull;
  import com.drew.lang.annotations.Nullable;
  import com.drew.metadata.Age;
  import com.drew.metadata.Face;
  import com.drew.metadata.TagDescriptor;
  
  import java.text.DecimalFormat;
  import java.io.IOException;
  
  import static com.drew.metadata.exif.makernotes.PanasonicMakernoteDirectory.*;
  
  /**
   * Provides human-readable string representations of tag values stored in a {@link PanasonicMakernoteDirectory}.
   * <p>
   * Some information about this makernote taken from here:
   * <ul>
   * <li><a href="https://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html">https://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html</a></li>
   * <li><a href="https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Panasonic.html">https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Panasonic.html</a></li>
   * </ul>
   *
   * @author Drew Noakes https://drewnoakes.com
   * @author Philipp Sandhaus
   */
  @SuppressWarnings("WeakerAccess")
  public class PanasonicMakernoteDescriptor extends TagDescriptor<PanasonicMakernoteDirectory>
  {
      public PanasonicMakernoteDescriptor(@NotNull PanasonicMakernoteDirectory directory)
      {
          super(directory);
      }
  
      @Override
      @Nullable
      public String getDescription(int tagType)
      {
          switch (tagType) {
              case TAG_QUALITY_MODE:
                  return getQualityModeDescription();
              case TAG_FIRMWARE_VERSION:
                  return getVersionDescription();
              case TAG_WHITE_BALANCE:
                  return getWhiteBalanceDescription();
              case TAG_FOCUS_MODE:
                  return getFocusModeDescription();
              case TAG_AF_AREA_MODE:
                  return getAfAreaModeDescription();
              case TAG_IMAGE_STABILIZATION:
                  return getImageStabilizationDescription();
              case TAG_MACRO_MODE:
                  return getMacroModeDescription();
              case TAG_RECORD_MODE:
                  return getRecordModeDescription();
              case TAG_AUDIO:
                  return getAudioDescription();
              case TAG_UNKNOWN_DATA_DUMP:
                  return getUnknownDataDumpDescription();
              case TAG_COLOR_EFFECT:
                  return getColorEffectDescription();
              case TAG_UPTIME:
                  return getUptimeDescription();
              case TAG_BURST_MODE:
                  return getBurstModeDescription();
              case TAG_CONTRAST_MODE:
                  return getContrastModeDescription();
              case TAG_NOISE_REDUCTION:
                  return getNoiseReductionDescription();
              case TAG_SELF_TIMER:
                  return getSelfTimerDescription();
              case TAG_ROTATION:
                  return getRotationDescription();
              case TAG_AF_ASSIST_LAMP:
                  return getAfAssistLampDescription();
              case TAG_COLOR_MODE:
                  return getColorModeDescription();
              case TAG_OPTICAL_ZOOM_MODE:
                  return getOpticalZoomModeDescription();
              case TAG_CONVERSION_LENS:
                  return getConversionLensDescription();
              case TAG_CONTRAST:
                  return getContrastDescription();
              case TAG_WORLD_TIME_LOCATION:
                  return getWorldTimeLocationDescription();
              case TAG_ADVANCED_SCENE_MODE:
                  return getAdvancedSceneModeDescription();
              case TAG_FACE_DETECTION_INFO:
                  return getDetectedFacesDescription();
              case TAG_TRANSFORM:
                  return getTransformDescription();
              case TAG_TRANSFORM_1:
                  return getTransform1Description();
              case TAG_INTELLIGENT_EXPOSURE:
                  return getIntelligentExposureDescription();
              case TAG_FLASH_WARNING:
                  return getFlashWarningDescription();
              case TAG_COUNTRY:
                  return getCountryDescription();
              case TAG_STATE:
                  return getStateDescription();
              case TAG_CITY:
                  return getCityDescription();
              case TAG_LANDMARK:
                  return getLandmarkDescription();
              case TAG_INTELLIGENT_RESOLUTION:
                  return getIntelligentResolutionDescription();
              case TAG_FACE_RECOGNITION_INFO:
                  return getRecognizedFacesDescription();
              case TAG_SCENE_MODE:
                  return getSceneModeDescription();
              case TAG_FLASH_FIRED:
                  return getFlashFiredDescription();
              case TAG_TEXT_STAMP:
                  return getTextStampDescription();
              case TAG_TEXT_STAMP_1:
                  return getTextStamp1Description();
              case TAG_TEXT_STAMP_2:
                  return getTextStamp2Description();
              case TAG_TEXT_STAMP_3:
                  return getTextStamp3Description();
              case TAG_MAKERNOTE_VERSION:
                  return getMakernoteVersionDescription();
              case TAG_EXIF_VERSION:
                  return getExifVersionDescription();
              case TAG_INTERNAL_SERIAL_NUMBER:
                  return getInternalSerialNumberDescription();
              case TAG_TITLE:
                  return getTitleDescription();
              case TAG_BRACKET_SETTINGS:
                  return getBracketSettingsDescription();
              case TAG_FLASH_CURTAIN:
                  return getFlashCurtainDescription();
              case TAG_LONG_EXPOSURE_NOISE_REDUCTION:
                  return getLongExposureNoiseReductionDescription();
              case TAG_BABY_NAME:
                  return getBabyNameDescription();
              case TAG_LOCATION:
                  return getLocationDescription();
  
              case TAG_LENS_FIRMWARE_VERSION:
                  return getLensFirmwareVersionDescription();
              case TAG_INTELLIGENT_D_RANGE:
                  return getIntelligentDRangeDescription();
              case TAG_CLEAR_RETOUCH:
                  return getClearRetouchDescription();
              case TAG_PHOTO_STYLE:
                  return getPhotoStyleDescription();
              case TAG_SHADING_COMPENSATION:
                  return getShadingCompensationDescription();
  
              case TAG_ACCELEROMETER_Z:
                  return getAccelerometerZDescription();
              case TAG_ACCELEROMETER_X:
                  return getAccelerometerXDescription();
              case TAG_ACCELEROMETER_Y:
                  return getAccelerometerYDescription();
              case TAG_CAMERA_ORIENTATION:
                  return getCameraOrientationDescription();
              case TAG_ROLL_ANGLE:
                  return getRollAngleDescription();
              case TAG_PITCH_ANGLE:
                  return getPitchAngleDescription();
              case TAG_SWEEP_PANORAMA_DIRECTION:
                  return getSweepPanoramaDirectionDescription();
              case TAG_TIMER_RECORDING:
                  return getTimerRecordingDescription();
              case TAG_HDR:
                  return getHDRDescription();
              case TAG_SHUTTER_TYPE:
                  return getShutterTypeDescription();
              case TAG_TOUCH_AE:
                  return getTouchAeDescription();
  
              case TAG_BABY_AGE:
                  return getBabyAgeDescription();
              case TAG_BABY_AGE_1:
                  return getBabyAge1Description();
              default:
                  return super.getDescription(tagType);
          }
      }
  
      @Nullable
      public String getTextStampDescription()
      {
          return getIndexedDescription(TAG_TEXT_STAMP, 1, "Off", "On");
      }
  
  	@Nullable
      public String getTextStamp1Description()
      {
          return getIndexedDescription(TAG_TEXT_STAMP_1, 1, "Off", "On");
      }
  
  	@Nullable
      public String getTextStamp2Description()
      {
          return getIndexedDescription(TAG_TEXT_STAMP_2, 1, "Off", "On");
      }
  
  	@Nullable
      public String getTextStamp3Description()
      {
          return getIndexedDescription(TAG_TEXT_STAMP_3, 1, "Off", "On");
      }
  
  	@Nullable
      public String getMacroModeDescription()
      {
          return getIndexedDescription(TAG_MACRO_MODE, 1, "Off", "On");
      }
  
      @Nullable
      public String getFlashFiredDescription()
      {
          return getIndexedDescription(TAG_FLASH_FIRED, 1, "Off", "On");
      }
  
      @Nullable
      public String getImageStabilizationDescription()
      {
          return getIndexedDescription(TAG_IMAGE_STABILIZATION,
              2,
              "On, Mode 1",
              "Off",
              "On, Mode 2"
          );
      }
  
      @Nullable
      public String getAudioDescription()
      {
          return getIndexedDescription(TAG_AUDIO, 1, "Off", "On");
      }
  
      @Nullable
      public String getTransformDescription()
      {
          return getTransformDescription(TAG_TRANSFORM);
      }
  
      @Nullable
      public String getTransform1Description()
      {
          return getTransformDescription(TAG_TRANSFORM_1);
      }
  
      @Nullable
      private String getTransformDescription(int tag)
      {
          byte[] values = _directory.getByteArray(tag);
          if (values == null)
              return null;
  
          RandomAccessReader reader = new ByteArrayReader(values);
  
          try
          {
              int val1 = reader.getUInt16(0);
              int val2 = reader.getUInt16(2);
  
              if (val1 == -1 && val2 == 1)
                  return "Slim Low";
              if (val1 == -3 && val2 == 2)
                  return "Slim High";
              if (val1 == 0 && val2 == 0)
                  return "Off";
              if (val1 == 1 && val2 == 1)
                  return "Stretch Low";
              if (val1 == 3 && val2 == 2)
                  return "Stretch High";
  
              return "Unknown (" + val1 + " " + val2 + ")";
          } catch (IOException e) {
              return null;
          }
      }
  
      @Nullable
      public String getIntelligentExposureDescription()
      {
          return getIndexedDescription(TAG_INTELLIGENT_EXPOSURE,
              "Off", "Low", "Standard", "High");
      }
  
      @Nullable
      public String getFlashWarningDescription()
      {
          return getIndexedDescription(TAG_FLASH_WARNING,
              "No", "Yes (Flash required but disabled)");
      }
  
      @Nullable
      private static String trim(@Nullable String s)
      {
          return s == null ? null : s.trim();
      }
  
      @Nullable
      public String getCountryDescription()
      {
          return trim(getStringFromBytes(TAG_COUNTRY, Charsets.UTF_8));
      }
  
      @Nullable
      public String getStateDescription()
      {
          return trim(getStringFromBytes(TAG_STATE, Charsets.UTF_8));
      }
  
      @Nullable
      public String getCityDescription()
      {
          return trim(getStringFromBytes(TAG_CITY, Charsets.UTF_8));
      }
  
      @Nullable
      public String getLandmarkDescription()
      {
          return trim(getStringFromBytes(TAG_LANDMARK, Charsets.UTF_8));
      }
  
      @Nullable
      public String getTitleDescription()
      {
          return trim(getStringFromBytes(TAG_TITLE, Charsets.UTF_8));
      }
  
      @Nullable
      public String getBracketSettingsDescription()
      {
          return getIndexedDescription(TAG_BRACKET_SETTINGS,
              "No Bracket", "3 Images, Sequence 0/-/+", "3 Images, Sequence -/0/+", "5 Images, Sequence 0/-/+",
              "5 Images, Sequence -/0/+", "7 Images, Sequence 0/-/+", "7 Images, Sequence -/0/+");
      }
  
      @Nullable
      public String getFlashCurtainDescription()
      {
          return getIndexedDescription(TAG_FLASH_CURTAIN,
              "n/a", "1st", "2nd");
      }
  
      @Nullable
      public String getLongExposureNoiseReductionDescription()
      {
          return getIndexedDescription(TAG_LONG_EXPOSURE_NOISE_REDUCTION, 1,
              "Off", "On");
      }
  
      @Nullable
      public String getLensFirmwareVersionDescription()
      {
          // lens version has 4 parts separated by periods
          byte[] bytes = _directory.getByteArray(TAG_LENS_FIRMWARE_VERSION);
          if (bytes == null)
              return null;
  
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < bytes.length; i++) {
              sb.append(bytes[i]);
              if (i < bytes.length - 1)
                  sb.append(".");
          }
          return sb.toString();
          //return string.Join(".", bytes.Select(b => b.ToString()).ToArray());
      }
  
      @Nullable
      public String getIntelligentDRangeDescription()
      {
          return getIndexedDescription(TAG_INTELLIGENT_D_RANGE,
              "Off", "Low", "Standard", "High");
      }
  
      @Nullable
      public String getClearRetouchDescription()
      {
          return getIndexedDescription(TAG_CLEAR_RETOUCH,
                  "Off", "On");
  
      }
  
      @Nullable
      public String getPhotoStyleDescription()
      {
          return getIndexedDescription(TAG_PHOTO_STYLE,
              "Auto", "Standard or Custom", "Vivid", "Natural", "Monochrome", "Scenery", "Portrait");
      }
  
      @Nullable
      public String getShadingCompensationDescription()
      {
          return getIndexedDescription(TAG_SHADING_COMPENSATION,
              "Off", "On");
      }
  
      @Nullable
      public String getAccelerometerZDescription()
      {
          Integer value = _directory.getInteger(TAG_ACCELEROMETER_Z);
          if (value == null)
              return null;
  
          // positive is acceleration upwards
          return String.valueOf(value.shortValue());
      }
  
      @Nullable
      public String getAccelerometerXDescription()
      {
          Integer value = _directory.getInteger(TAG_ACCELEROMETER_X);
          if (value == null)
              return null;
  
          // positive is acceleration to the left
          return String.valueOf(value.shortValue());
      }
  
      @Nullable
      public String getAccelerometerYDescription()
      {
          Integer value = _directory.getInteger(TAG_ACCELEROMETER_Y);
          if (value == null)
              return null;
  
          // positive is acceleration backwards
          return String.valueOf(value.shortValue());
      }
  
      @Nullable
      public String getCameraOrientationDescription()
      {
          return getIndexedDescription(TAG_CAMERA_ORIENTATION,
                  "Normal", "Rotate CW", "Rotate 180", "Rotate CCW", "Tilt Upwards", "Tile Downwards");
      }
  
      @Nullable
      public String getRollAngleDescription()
      {
          Integer value = _directory.getInteger(TAG_ROLL_ANGLE);
          if (value == null)
              return null;
  
          DecimalFormat format = new DecimalFormat("0.#");
          // converted to degrees of clockwise camera rotation
          return format.format(value.shortValue() / 10.0);
      }
  
      @Nullable
      public String getPitchAngleDescription()
      {
          Integer value = _directory.getInteger(TAG_PITCH_ANGLE);
          if (value == null)
              return null;
  
          DecimalFormat format = new DecimalFormat("0.#");
          // converted to degrees of upward camera tilt
          return format.format(-value.shortValue() / 10.0);
      }
  
      @Nullable
      public String getSweepPanoramaDirectionDescription()
      {
          return getIndexedDescription(TAG_SWEEP_PANORAMA_DIRECTION,
                  "Off", "Left to Right", "Right to Left", "Top to Bottom", "Bottom to Top");
      }
  
      @Nullable
      public String getTimerRecordingDescription()
      {
          return getIndexedDescription(TAG_TIMER_RECORDING,
                  "Off", "Time Lapse", "Stop-motion Animation");
      }
  
      @Nullable
      public String getHDRDescription()
      {
          Integer value = _directory.getInteger(TAG_HDR);
          if (value == null)
              return null;
  
          switch (value)
          {
              case 0:
                  return "Off";
              case 100:
                  return "1 EV";
              case 200:
                  return "2 EV";
              case 300:
                  return "3 EV";
              case 32868:
                  return "1 EV (Auto)";
              case 32968:
                  return "2 EV (Auto)";
              case 33068:
                  return "3 EV (Auto)";
              default:
                  return String.format("Unknown (%d)", value);
          }
      }
  
      @Nullable
      public String getShutterTypeDescription()
      {
          return getIndexedDescription(TAG_SHUTTER_TYPE,
                  "Mechanical", "Electronic", "Hybrid");
      }
  
      @Nullable
      public String getTouchAeDescription()
      {
          return getIndexedDescription(TAG_TOUCH_AE,
                  "Off", "On");
      }
  
      @Nullable
      public String getBabyNameDescription()
      {
          return trim(getStringFromBytes(TAG_BABY_NAME, Charsets.UTF_8));
      }
  
  	@Nullable
      public String getLocationDescription()
      {
          return trim(getStringFromBytes(TAG_LOCATION, Charsets.UTF_8));
      }
  
      @Nullable
      public String getIntelligentResolutionDescription()
      {
          return getIndexedDescription(TAG_INTELLIGENT_RESOLUTION,
              "Off", null, "Auto", "On");
      }
  
      @Nullable
      public String getContrastDescription()
      {
          return getIndexedDescription(TAG_CONTRAST, "Normal");
      }
  
      @Nullable
      public String getWorldTimeLocationDescription()
      {
          return getIndexedDescription(TAG_WORLD_TIME_LOCATION,
              1, "Home", "Destination");
      }
  
      @Nullable
      public String getAdvancedSceneModeDescription()
      {
          return getIndexedDescription(TAG_ADVANCED_SCENE_MODE,
              1,
              "Normal",
              "Outdoor/Illuminations/Flower/HDR Art",
              "Indoor/Architecture/Objects/HDR B&W",
              "Creative",
              "Auto",
              null,
              "Expressive",
              "Retro",
              "Pure",
              "Elegant",
              null,
              "Monochrome",
              "Dynamic Art",
              "Silhouette"
          );
      }
  
      @Nullable
      public String getUnknownDataDumpDescription()
      {
          return getByteLengthDescription(TAG_UNKNOWN_DATA_DUMP);
      }
  
      @Nullable
      public String getColorEffectDescription()
      {
          return getIndexedDescription(TAG_COLOR_EFFECT,
              1, "Off", "Warm", "Cool", "Black & White", "Sepia"
          );
      }
  
      @Nullable
      public String getUptimeDescription()
      {
          Integer value = _directory.getInteger(TAG_UPTIME);
          if (value == null)
              return null;
          return value / 100f + " s";
      }
  
      @Nullable
      public String getBurstModeDescription()
      {
          return getIndexedDescription(TAG_BURST_MODE,
              "Off", null, "On", "Indefinite", "Unlimited"
          );
      }
  
      @Nullable
      public String getContrastModeDescription()
      {
          Integer value = _directory.getInteger(TAG_CONTRAST_MODE);
          if (value == null)
              return null;
          switch (value) {
              case 0x0: return "Normal";
              case 0x1: return "Low";
              case 0x2: return "High";
              case 0x6: return "Medium Low";
              case 0x7: return "Medium High";
              case 0x100: return "Low";
              case 0x110: return "Normal";
              case 0x120: return "High";
              default:
                  return "Unknown (" + value + ")";
          }
      }
  
      @Nullable
      public String getNoiseReductionDescription()
      {
          return getIndexedDescription(TAG_NOISE_REDUCTION,
              "Standard (0)", "Low (-1)", "High (+1)", "Lowest (-2)", "Highest (+2)"
          );
      }
  
      @Nullable
      public String getSelfTimerDescription()
      {
          return getIndexedDescription(TAG_SELF_TIMER,
              1, "Off", "10 s", "2 s"
          );
      }
  
      @Nullable
      public String getRotationDescription()
      {
          Integer value = _directory.getInteger(TAG_ROTATION);
          if (value == null)
              return null;
          switch (value) {
              case 1: return "Horizontal";
              case 3: return "Rotate 180";
              case 6: return "Rotate 90 CW";
              case 8: return "Rotate 270 CW";
              default:
                  return "Unknown (" + value + ")";
          }
      }
  
      @Nullable
      public String getAfAssistLampDescription()
      {
          return getIndexedDescription(TAG_AF_ASSIST_LAMP,
              1, "Fired", "Enabled but not used", "Disabled but required", "Disabled and not required"
          );
      }
  
      @Nullable
      public String getColorModeDescription()
      {
          return getIndexedDescription(TAG_COLOR_MODE,
              "Normal", "Natural", "Vivid"
          );
      }
  
      @Nullable
      public String getOpticalZoomModeDescription()
      {
          return getIndexedDescription(TAG_OPTICAL_ZOOM_MODE,
              1, "Standard", "Extended"
          );
      }
  
      @Nullable
      public String getConversionLensDescription()
      {
          return getIndexedDescription(TAG_CONVERSION_LENS,
              1, "Off", "Wide", "Telephoto", "Macro"
          );
      }
  
      @Nullable
      public String getDetectedFacesDescription()
      {
          return buildFacesDescription(_directory.getDetectedFaces());
      }
  
      @Nullable
      public String getRecognizedFacesDescription()
      {
          return buildFacesDescription(_directory.getRecognizedFaces());
      }
  
      @Nullable
      private String buildFacesDescription(@Nullable Face[] faces)
      {
          if (faces == null)
              return null;
  
          StringBuilder result = new StringBuilder();
  
          for (int i = 0; i < faces.length; i++)
              result.append("Face ").append(i + 1).append(": ").append(faces[i].toString()).append("\n");
  
          return result.length() > 0 ? result.substring(0, result.length() - 1) : null;
  
      }
  
      private static final String[] _sceneModes = new String[] {
          "Normal", // 1
          "Portrait",
          "Scenery",
          "Sports",
          "Night Portrait",
          "Program",
          "Aperture Priority",
          "Shutter Priority",
          "Macro",
          "Spot", // 10
          "Manual",
          "Movie Preview",
          "Panning",
          "Simple",
          "Color Effects",
          "Self Portrait",
          "Economy",
          "Fireworks",
          "Party",
          "Snow", // 20
          "Night Scenery",
          "Food",
          "Baby",
          "Soft Skin",
          "Candlelight",
          "Starry Night",
          "High Sensitivity",
          "Panorama Assist",
          "Underwater",
          "Beach", // 30
          "Aerial Photo",
          "Sunset",
          "Pet",
          "Intelligent ISO",
          "Clipboard",
          "High Speed Continuous Shooting",
          "Intelligent Auto",
          null,
          "Multi-aspect",
          null, // 40
          "Transform",
          "Flash Burst",
          "Pin Hole",
          "Film Grain",
          "My Color",
          "Photo Frame",
          null,
          null,
          null,
          null, // 50
          "HDR"
      };
  
      @Nullable
      public String getRecordModeDescription()
      {
          return getIndexedDescription(TAG_RECORD_MODE, 1, _sceneModes);
      }
  
      @Nullable
      public String getSceneModeDescription()
      {
          return getIndexedDescription(TAG_SCENE_MODE, 1, _sceneModes);
      }
  
      @Nullable
      public String getFocusModeDescription()
      {
          return getIndexedDescription(TAG_FOCUS_MODE, 1,
              "Auto", "Manual", null, "Auto, Focus Button", "Auto, Continuous");
      }
  
      @Nullable
      public String getAfAreaModeDescription()
      {
          int[] value = _directory.getIntArray(TAG_AF_AREA_MODE);
          if (value == null || value.length < 2)
              return null;
          switch (value[0]) {
              case 0:
                  switch (value[1]) {
                      case 1: return "Spot Mode On";
                      case 16: return "Spot Mode Off";
                      default: return "Unknown (" + value[0] + " " + value[1] + ")";
                  }
              case 1:
                  switch (value[1]) {
                      case 0: return "Spot Focusing";
                      case 1: return "5-area";
                      default: return "Unknown (" + value[0] + " " + value[1] + ")";
                  }
              case 16:
                  switch (value[1]) {
                      case 0: return "1-area";
                      case 16: return "1-area (high speed)";
                      default: return "Unknown (" + value[0] + " " + value[1] + ")";
                  }
              case 32:
                  switch (value[1]) {
                      case 0: return "Auto or Face Detect";
                      case 1: return "3-area (left)";
                      case 2: return "3-area (center)";
                      case 3: return "3-area (right)";
                      default: return "Unknown (" + value[0] + " " + value[1] + ")";
                  }
              case 64: return "Face Detect";
              default: return "Unknown (" + value[0] + " " + value[1] + ")";
          }
      }
  
      @Nullable
      public String getQualityModeDescription()
      {
          return getIndexedDescription(TAG_QUALITY_MODE,
              2,
              "High", // 2
              "Normal",
              null,
              null,
              "Very High",
              "Raw",
              null,
              "Motion Picture" // 9
          );
      }
  
      @Nullable
      public String getVersionDescription()
      {
          return getVersionBytesDescription(TAG_FIRMWARE_VERSION, 2);
      }
  
      @Nullable
      public String getMakernoteVersionDescription()
      {
          return getVersionBytesDescription(TAG_MAKERNOTE_VERSION, 2);
      }
  
      @Nullable
      public String getExifVersionDescription()
      {
          return getVersionBytesDescription(TAG_EXIF_VERSION, 2);
      }
  
      @Nullable
      public String getInternalSerialNumberDescription()
      {
          return get7BitStringFromBytes(TAG_INTERNAL_SERIAL_NUMBER);
      }
  
      @Nullable
      public String getWhiteBalanceDescription()
      {
          return getIndexedDescription(TAG_WHITE_BALANCE,
              1,
              "Auto", // 1
              "Daylight",
              "Cloudy",
              "Incandescent",
              "Manual",
              null,
              null,
              "Flash",
              null,
              "Black & White", // 10
              "Manual",
              "Shade" // 12
          );
      }
  
  	@Nullable
  	public String getBabyAgeDescription()
      {
          final Age age = _directory.getAge(TAG_BABY_AGE);
          return age == null ? null : age.toFriendlyString();
      }
  
  	@Nullable
  	public String getBabyAge1Description()
      {
          final Age age = _directory.getAge(TAG_BABY_AGE_1);
          return age == null ? null : age.toFriendlyString();
      }
  }
  