<?php
declare(strict_types = 1);

/**
 * Gestion des vidéos.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class Video
{
	/**
	 * Extensions de vidéos autorisées.
	 *
	 * @param array
	 */
	const EXT_ARRAY = ['mp4', 'webm'];



	/**
	 * Gestion des captures d'image.
	 *
	 * @return array
	 */
	public static function captures(): array
	{
		if (!Config::$params['video_captures'])
		{
			return [];
		}

		// Récupération des vidéos dont on n'a pas encore extrait les informations.
		$limit = 50;
		$videos_items = $videos_pending = [];
		$sql = "SELECT item_id,
					   item_path,
					   item_adddt
				  FROM {items}
				 WHERE item_type IN (" . DB::inInt(Item::VIDEO_TYPES) . ")
				   AND item_duration IS NULL
			  ORDER BY item_id DESC
				 LIMIT $limit";
		if (DB::execute($sql))
		{
			$videos_items = DB::fetchAll('item_id');
		}
		$limit -= count($videos_items);
		if ($limit)
		{
			$sql = "SELECT 'p'||pending_id AS item_id,
						   pending_file AS item_path,
						   pending_adddt AS item_adddt
					  FROM {items_pending}
					 WHERE pending_type IN (" . DB::inInt(Item::VIDEO_TYPES) . ")
					   AND pending_duration IS NULL
				  ORDER BY item_id DESC
					 LIMIT $limit";
			if (DB::execute($sql))
			{
				$videos_pending = DB::fetchAll('item_id');
			}
		}
		$videos = $videos_items + $videos_pending;
		if (!$videos)
		{
			Config::changeDBParams(['video_captures' => 0]);
			return [];
		}

		// On retourne les informations des vidéos restantes.
		$captures = [];
		foreach ($videos as $id => &$i)
		{
			$captures[] =
			[
				'hash' => self::uniqueHash((string) $id, $i['item_adddt']),
				'key' => Security::fileKeyHash([$id]),
				'id' => $id,
				'path' => App::getFileSource(
					(substr((string) $id, 0, 1) == 'p' ? '/pending/' : '') . $i['item_path']
				)
			];
		}
		return $captures;
	}

	/**
	 * Retourne le nom de fichier de la capture d'une vidéo.
	 *
	 * @param int $id
	 *   Identifiant de la vidéo.
	 * @param string $adddt
	 *   Date d'ajout de la vidéo.
	 * @param bool $pending
	 *   Est-ce une vidéo en attente de validation ?
	 *
	 * @return string
	 */
	public static function getCapture(int $id, string $adddt, bool $pending = FALSE): string
	{
		$id = $pending ? "p$id" : $id;
		$hash = self::uniqueHash((string) $id, $adddt);

		return $id . '-' . $hash . '.jpg';
	}

	/**
	 * Envoi une vidéo au navigateur.
	 *
	 * Code basé sur la classe suivante :
	 * https://gist.github.com/ranacseruet/9826293#file-videostream-php
	 *
	 * @param string $file
	 *   Chemin du fichier.
	 * @param bool $download
	 *   TRUE = télécharge la vidéo.
	 *   FALSE = diffuse la vidéo.
	 *
	 * @return void
	 */
	public static function read(string $file, bool $download = FALSE): void
	{
		// Ouverture du fichier.
		if (!$handle = fopen($file, 'rb'))
		{
			die('Failed to open file.');
		}

		// Initialisation.
		$filename = basename($file);
		$filetype = File::getMimeType($file);
		$filetype = $filetype == '' ? 'video/mp4' : $filetype;
		$size = filesize($file);
		$buffer = 102400;
		$start = 0;
		$end = $size - 1;
		set_time_limit(0);
        ob_get_clean();

		// Envoi des en-têtes HTTP.
		$content_type = $download ? 'application/octet-stream' : $filetype;
		$content_disposition = $download ? 'attachment' : 'inline';
		header("Content-Disposition: $content_disposition; filename=\"$filename\"");
		header("Content-Type: $content_type");
		header('Accept-Ranges: bytes');
		if (isset($_SERVER['HTTP_RANGE']))
		{
			$c_start = $start;
			$c_end = $end;
			list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
			if (strpos($range, ',') !== false)
			{
				header('HTTP/1.1 416 Requested Range Not Satisfiable');
				header("Content-Range: bytes $start-$end/$size");
				die;
			}
			if ($range == '-')
			{
				$c_start = $size - substr($range, 1);
			}
			else
			{
				$range = explode('-', $range);
				$c_start = $range[0];
				$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
			}
			$c_end = ($c_end > $end) ? $end : $c_end;
			if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size)
			{
				header('HTTP/1.1 416 Requested Range Not Satisfiable');
				header("Content-Range: bytes $start-$end/$size");
				die;
			}
			$start = $c_start;
			$end = $c_end;
			$length = $end - $start + 1;
			fseek($handle, (int) $start);
			header('HTTP/1.1 206 Partial Content');
			header("Content-Length: $length");
			header("Content-Range: bytes $start-$end/$size");
		}
		else
		{
			header("Content-Length: $size");
		}

		// Envoi des données.
		#! Fonctionne moins bien et peut provoquer une erreur
		#! "Allowed Memory Size of NUM Bytes Exhausted...".
		#echo stream_get_contents($handle);

		#! Peut provoquer une erreur "Maximum execution time...".
		$i = $start;
		while(!feof($handle) && $i <= $end)
		{
			$bytes_to_read = $buffer;
			if (($i + $bytes_to_read) > $end)
			{
				$bytes_to_read = $end - $i + 1;
			}
			echo fread($handle, (int) $bytes_to_read);
			flush();
			$i += $bytes_to_read;
		}

		// Fermeture du fichier.
		fclose($handle);
		die;
	}

	/**
	 * Retourne une signature de paramètres d'une vidéo
	 * permettant d'identifier de manière unique cette vidéo.
	 *
	 * @param string $id
	 *   Identifiant.
	 * @param string $adddt
	 *   Date d'ajout.
	 *
	 * @return string
	 */
	public static function uniqueHash(string $id, string $adddt): string
	{
		return md5($id . '|' . $adddt . '|' . CONF_KEY);
	}
}
?>