content: package com.drew.metadata.wav;
  
  import com.drew.imaging.riff.RiffHandler;
  import com.drew.lang.ByteArrayReader;
  import com.drew.lang.annotations.NotNull;
  import com.drew.metadata.Metadata;
  import com.drew.metadata.MetadataException;
  
  import java.io.IOException;
  
  /**
   * Implementation of {@link RiffHandler} specialising in Wav support.
   *
   * Extracts data from chunk/list types:
   *
   * <ul>
   *     <li><code>"INFO"</code>: artist, title, product, track number, date created, genre, comments, copyright, software, duration</li>
   *     <li><code>"fmt "</code>: format, channels, samples/second, bytes/second, block alignment, bits/sample</li>
   *     <li><code>"data"</code>: duration</li>
   * </ul>
   *
   * Sources: https://www.neurophys.wisc.edu/auditory/riff-format.txt
   *          https://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
   *          https://wiki.audacityteam.org/wiki/WAV
   *
   * @author Payton Garland
   */
  public class WavRiffHandler implements RiffHandler
  {
      @NotNull
      private final WavDirectory _directory;
  
      @NotNull
      private String _currentList = "";
  
      public WavRiffHandler(@NotNull Metadata metadata)
      {
          _directory = new WavDirectory();
          metadata.addDirectory(_directory);
      }
  
      public boolean shouldAcceptRiffIdentifier(@NotNull String identifier)
      {
          return identifier.equals(WavDirectory.FORMAT);
      }
  
      public boolean shouldAcceptChunk(@NotNull String fourCC)
      {
          return fourCC.equals(WavDirectory.CHUNK_FORMAT)
              || (_currentList.equals(WavDirectory.LIST_INFO) && WavDirectory._tagIntegerMap.containsKey(fourCC))
              || fourCC.equals(WavDirectory.CHUNK_DATA);
      }
  
      @Override
      public boolean shouldAcceptList(@NotNull String fourCC)
      {
          if (fourCC.equals(WavDirectory.LIST_INFO)) {
              _currentList = WavDirectory.LIST_INFO;
              return true;
          } else {
              _currentList = "";
              return false;
          }
      }
  
      public void processChunk(@NotNull String fourCC, @NotNull byte[] payload)
      {
          try {
              if (fourCC.equals(WavDirectory.CHUNK_FORMAT)) {
                  ByteArrayReader reader = new ByteArrayReader(payload);
                  reader.setMotorolaByteOrder(false);
                  int wFormatTag = reader.getInt16(0);
                  int wChannels = reader.getInt16(2);
                  int dwSamplesPerSec = reader.getInt32(4);
                  int dwAvgBytesPerSec = reader.getInt32(8);
                  int wBlockAlign = reader.getInt16(12);
  
                  switch (wFormatTag) {
                      // Microsoft Pulse Code Modulation (PCM)
                      case (0x0001):
                          int wBitsPerSample = reader.getInt16(14);
                          _directory.setInt(WavDirectory.TAG_BITS_PER_SAMPLE, wBitsPerSample);
                          _directory.setString(WavDirectory.TAG_FORMAT, WavDirectory._audioEncodingMap.get(wFormatTag));
                          break;
                      default:
                          if (WavDirectory._audioEncodingMap.containsKey(wFormatTag)) {
                              _directory.setString(WavDirectory.TAG_FORMAT, WavDirectory._audioEncodingMap.get(wFormatTag));
                          } else {
                              _directory.setString(WavDirectory.TAG_FORMAT, "Unknown");
                          }
                  }
  
                  _directory.setInt(WavDirectory.TAG_CHANNELS, wChannels);
                  _directory.setInt(WavDirectory.TAG_SAMPLES_PER_SEC, dwSamplesPerSec);
                  _directory.setInt(WavDirectory.TAG_BYTES_PER_SEC, dwAvgBytesPerSec);
                  _directory.setInt(WavDirectory.TAG_BLOCK_ALIGNMENT, wBlockAlign);
              } else if (fourCC.equals(WavDirectory.CHUNK_DATA)) {
                  try {
                      if (_directory.containsTag(WavDirectory.TAG_BYTES_PER_SEC)) {
                          double duration = (double)payload.length / _directory.getDouble(WavDirectory.TAG_BYTES_PER_SEC);
                          Integer hours = (int)duration / (int)(Math.pow(60, 2));
                          Integer minutes = ((int)duration / (int)(Math.pow(60, 1))) - (hours * 60);
                          Integer seconds = (int)Math.round((duration / (Math.pow(60, 0))) - (minutes * 60));
                          String time = String.format("%1$02d:%2$02d:%3$02d", hours, minutes, seconds);
                          _directory.setString(WavDirectory.TAG_DURATION, time);
                      }
                  } catch (MetadataException ex) {
                      _directory.addError("Error calculating duration: bytes per second not found");
                  }
              }else if (WavDirectory._tagIntegerMap.containsKey(fourCC)) {
                  _directory.setString(WavDirectory._tagIntegerMap.get(fourCC), new String(payload).substring(0, payload.length - 1));
              }
          } catch (IOException ex) {
              _directory.addError(ex.getMessage());
          }
      }
  
      @Override
      public void addError(@NotNull String message)
      {
          _directory.addError(message);
      }
  }
  