content: /*
   This file is part of Airsonic.
  
   Airsonic is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
  
   Airsonic 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 General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with Airsonic.  If not, see <http://www.gnu.org/licenses/>.
  
   Copyright 2016 (C) Airsonic Authors
   Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
   */
  package org.airsonic.player.service.metadata;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import org.airsonic.player.domain.MediaFile;
  import org.airsonic.player.service.SettingsService;
  import org.airsonic.player.service.TranscodingService;
  import org.apache.commons.io.FilenameUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.core.annotation.Order;
  import org.springframework.stereotype.Service;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  /**
   * Parses meta data from video files using FFmpeg (http://ffmpeg.org/).
   * <p/>
   * Currently duration, bitrate and dimension are supported.
   *
   * @author Sindre Mehus
   */
  @Service("ffmpegParser")
  @Order(100)
  public class FFmpegParser extends MetaDataParser {
  
      private static final Logger LOG = LoggerFactory.getLogger(FFmpegParser.class);
      private static final ObjectMapper objectMapper = new ObjectMapper();
      private static final String[] FFPROBE_OPTIONS = {
          "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams"
      };
  
      @Autowired
      private TranscodingService transcodingService;
      @Autowired
      private SettingsService settingsService;
  
      /**
       * Parses meta data for the given music file. No guessing or reformatting is done.
       *
       *
       * @param file The music file to parse.
       * @return Meta data for the file.
       */
      @Override
      public MetaData getRawMetaData(File file) {
  
          MetaData metaData = new MetaData();
  
          try {
              // Use `ffprobe` in the transcode directory if it exists, otherwise let the system sort it out.
              String ffprobe;
              File inTranscodeDirectory = new File(transcodingService.getTranscodeDirectory(), "ffprobe");
              if (inTranscodeDirectory.exists()) {
                  ffprobe = inTranscodeDirectory.getAbsolutePath();
              } else {
                  ffprobe = "ffprobe";
              }
  
              List<String> command = new ArrayList<>();
              command.add(ffprobe);
              command.addAll(Arrays.asList(FFPROBE_OPTIONS));
              command.add(file.getAbsolutePath());
  
              Process process = Runtime.getRuntime().exec(command.toArray(new String[0]));
              final JsonNode result = objectMapper.readTree(process.getInputStream());
  
              metaData.setDurationSeconds(result.at("/format/duration").asInt());
              // Bitrate is in Kb/s
              metaData.setBitRate(result.at("/format/bit_rate").asInt() / 1000);
  
              // Find the first (if any) stream that has dimensions and use those.
              // 'width' and 'height' are display dimensions; compare to 'coded_width', 'coded_height'.
              for (JsonNode stream : result.at("/streams")) {
                  if (stream.has("width") && stream.has("height")) {
                      metaData.setWidth(stream.get("width").asInt());
                      metaData.setHeight(stream.get("height").asInt());
                      break;
                  }
              }
          } catch (Throwable x) {
              LOG.warn("Error when parsing metadata in " + file, x);
          }
  
          return metaData;
      }
  
      /**
       * Not supported.
       */
      @Override
      public void setMetaData(MediaFile file, MetaData metaData) {
          throw new RuntimeException("setMetaData() not supported in " + getClass().getSimpleName());
      }
  
      /**
       * Returns whether this parser supports tag editing (using the {@link #setMetaData} method).
       *
       * @return Always false.
       */
      @Override
      public boolean isEditingSupported() {
          return false;
      }
  
      @Override
      SettingsService getSettingsService() {
          return settingsService;
      }
  
      /**
       * Returns whether this parser is applicable to the given file.
       *
       * @param file The file in question.
       * @return Whether this parser is applicable to the given file.
       */
      @Override
      public boolean isApplicable(File file) {
          String format = FilenameUtils.getExtension(file.getName()).toLowerCase();
  
          for (String s : settingsService.getVideoFileTypesAsArray()) {
              if (format.equals(s)) {
                  return true;
              }
          }
          return false;
      }
  
      public void setTranscodingService(TranscodingService transcodingService) {
          this.transcodingService = transcodingService;
      }
  
      public void setSettingsService(SettingsService settingsService) {
          this.settingsService = settingsService;
      }
  }
  