<?php

/*
 * This file is part of PHP-FFmpeg.
 *
 * (c) Alchemy <info@alchemy.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace BuddyBossPlatform\FFMpeg;

use BuddyBossPlatform\Alchemy\BinaryDriver\ConfigurationInterface;
use BuddyBossPlatform\Alchemy\BinaryDriver\Exception\ExecutionFailureException;
use BuddyBossPlatform\Doctrine\Common\Cache\ArrayCache;
use BuddyBossPlatform\Doctrine\Common\Cache\Cache;
use BuddyBossPlatform\FFMpeg\Driver\FFProbeDriver;
use BuddyBossPlatform\FFMpeg\FFProbe\DataMapping\Format;
use BuddyBossPlatform\FFMpeg\FFProbe\Mapper;
use BuddyBossPlatform\FFMpeg\FFProbe\MapperInterface;
use BuddyBossPlatform\FFMpeg\FFProbe\OptionsTester;
use BuddyBossPlatform\FFMpeg\FFProbe\OptionsTesterInterface;
use BuddyBossPlatform\FFMpeg\FFProbe\OutputParser;
use BuddyBossPlatform\FFMpeg\FFProbe\OutputParserInterface;
use BuddyBossPlatform\FFMpeg\Exception\InvalidArgumentException;
use BuddyBossPlatform\FFMpeg\Exception\RuntimeException;
use BuddyBossPlatform\FFMpeg\FFProbe\DataMapping\StreamCollection;
use BuddyBossPlatform\Psr\Log\LoggerInterface;
class FFProbe
{
    const TYPE_STREAMS = 'streams';
    const TYPE_FORMAT = 'format';
    /** @var Cache */
    private $cache;
    /** @var OptionsTesterInterface */
    private $optionsTester;
    /** @var OutputParserInterface */
    private $parser;
    /** @var FFProbeDriver */
    private $ffprobe;
    /** @var MapperInterface */
    private $mapper;
    public function __construct(FFProbeDriver $ffprobe, Cache $cache)
    {
        $this->ffprobe = $ffprobe;
        $this->optionsTester = new OptionsTester($ffprobe, $cache);
        $this->parser = new OutputParser();
        $this->mapper = new Mapper();
        $this->cache = $cache;
    }
    /**
     * @return OutputParserInterface
     */
    public function getParser()
    {
        return $this->parser;
    }
    /**
     * @param OutputParserInterface $parser
     *
     * @return FFProbe
     */
    public function setParser(OutputParserInterface $parser)
    {
        $this->parser = $parser;
        return $this;
    }
    /**
     * @return FFProbeDriver
     */
    public function getFFProbeDriver()
    {
        return $this->ffprobe;
    }
    /**
     * @param FFProbeDriver $ffprobe
     *
     * @return FFProbe
     */
    public function setFFProbeDriver(FFProbeDriver $ffprobe)
    {
        $this->ffprobe = $ffprobe;
        return $this;
    }
    /**
     * @param OptionsTesterInterface $tester
     *
     * @return FFProbe
     */
    public function setOptionsTester(OptionsTesterInterface $tester)
    {
        $this->optionsTester = $tester;
        return $this;
    }
    /**
     * @return OptionsTesterInterface
     */
    public function getOptionsTester()
    {
        return $this->optionsTester;
    }
    /**
     * @param Cache $cache
     *
     * @return FFProbe
     */
    public function setCache(Cache $cache)
    {
        $this->cache = $cache;
        return $this;
    }
    /**
     * @return Cache
     */
    public function getCache()
    {
        return $this->cache;
    }
    /**
     * @return MapperInterface
     */
    public function getMapper()
    {
        return $this->mapper;
    }
    /**
     * @param MapperInterface $mapper
     *
     * @return FFProbe
     */
    public function setMapper(MapperInterface $mapper)
    {
        $this->mapper = $mapper;
        return $this;
    }
    /**
     * @api
     *
     * Probes the format of a given file.
     *
     * @param string $pathfile
     *
     * @return Format A Format object
     *
     * @throws InvalidArgumentException
     * @throws RuntimeException
     */
    public function format($pathfile)
    {
        return $this->probe($pathfile, '-show_format', static::TYPE_FORMAT);
    }
    /**
     * @api
     *
     * Checks wether the given `$pathfile` is considered a valid media file.
     *
     * @param string $pathfile
     * @return bool
     * @since 0.10.0
     */
    public function isValid($pathfile)
    {
        try {
            return $this->format($pathfile)->get('duration') > 0;
        } catch (\Exception $e) {
            // complete invalid data
            return \false;
        }
    }
    /**
     * @api
     *
     * Probes the streams contained in a given file.
     *
     * @param string $pathfile
     *
     * @return StreamCollection A collection of streams
     *
     * @throws InvalidArgumentException
     * @throws RuntimeException
     */
    public function streams($pathfile)
    {
        return $this->probe($pathfile, '-show_streams', static::TYPE_STREAMS);
    }
    /**
     * @api
     *
     * Creates an FFProbe.
     *
     * @param array|ConfigurationInterface $configuration
     * @param LoggerInterface              $logger
     * @param Cache                        $cache
     *
     * @return FFProbe
     */
    public static function create($configuration = array(), LoggerInterface $logger = null, Cache $cache = null)
    {
        if (null === $cache) {
            $cache = new ArrayCache();
        }
        return new static(FFProbeDriver::create($configuration, $logger), $cache);
    }
    private function probe($pathfile, $command, $type, $allowJson = \true)
    {
        $id = \sprintf('%s-%s', $command, $pathfile);
        if ($this->cache->contains($id)) {
            return $this->cache->fetch($id);
        }
        if (!$this->optionsTester->has($command)) {
            throw new RuntimeException(\sprintf('This version of ffprobe is too old and ' . 'does not support `%s` option, please upgrade', $command));
        }
        $commands = array($pathfile, $command);
        $parseIsToDo = \false;
        if ($allowJson && $this->optionsTester->has('-print_format')) {
            // allowed in latest PHP-FFmpeg version
            $commands[] = '-print_format';
            $commands[] = 'json';
        } elseif ($allowJson && $this->optionsTester->has('-of')) {
            // option has changed in avconv 9
            $commands[] = '-of';
            $commands[] = 'json';
        } else {
            $parseIsToDo = \true;
        }
        try {
            $output = $this->ffprobe->command($commands);
        } catch (ExecutionFailureException $e) {
            throw new RuntimeException(\sprintf('Unable to probe %s', $pathfile), $e->getCode(), $e);
        }
        if ($parseIsToDo) {
            $data = $this->parser->parse($type, $output);
        } else {
            try {
                // Malformed json may be retrieved
                $data = $this->parseJson($output);
            } catch (RuntimeException $e) {
                return $this->probe($pathfile, $command, $type, \false);
            }
        }
        $ret = $this->mapper->map($type, $data);
        $this->cache->save($id, $ret);
        return $ret;
    }
    private function parseJson($data)
    {
        $ret = @\json_decode($data, \true);
        if (\JSON_ERROR_NONE !== \json_last_error()) {
            throw new RuntimeException(\sprintf('Unable to parse json %s', $ret));
        }
        return $ret;
    }
}
