(*****************************************************************************

  Liquidsoap, a programmable stream generator.
  Copyright 2003-2024 Savonet team

  This program 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 2 of the License, or
  (at your option) any later version.

  This program 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, fully stated in the COPYING
  file at the root of the liquidsoap distribution.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA

 *****************************************************************************)

open Mm

(** Ogg Stream Encoder *)

val log : Log.t

(** {2 Types} *)

exception Invalid_data
exception Invalid_usage

(** Audio data type *)
type audio = Audio.t

(** Video data type *)
type video = Video.buffer

(** A data unit *)
type 'a data = { data : 'a; offset : int; length : int }

(** A track data is a data unit of either audio or video. *)
type track_data = Audio_data of audio data | Video_data of video data

(** A track encoder takes the track data,
  * the ogg logical stream, and fills the stream.
  * If the encoding process outputs ogg pages, then
  * the encoder should use the last argument to add its pages
  * to the stream. *)
type 'a track_encoder =
  'a data -> Ogg.Stream.stream -> (Ogg.Page.t -> unit) -> unit

(** Returns the first page of the stream,
  * to be placed at the very beginning. *)
type header_encoder = Ogg.Stream.stream -> Ogg.Page.t

(** Return the end time of a page, in milliseconds. *)
type position = Unknown | Time of float

(** Type for a function returning a page's ending time. *)
type page_end_time = Ogg.Page.t -> position

(** Returns an optional fisbone packet, which
  * will contain the data for this stream to
  * put in the ogg skeleton, if enabled in
  * the encoder. *)
type fisbone_packet = Ogg.Stream.stream -> Ogg.Stream.packet option

(** Returns the remaining header data, before data encoding starts. *)
type stream_start = Ogg.Stream.stream -> Ogg.Page.t list

(** Ends the track. *)
type end_of_stream = Ogg.Stream.stream -> Ogg.Page.t list

(** A data encoder is an encoder for either a audio or a video track. *)
type data_encoder =
  | Audio_encoder of audio track_encoder
  | Video_encoder of video track_encoder

(** The full stream encoder type. *)
type stream_encoder = {
  header_encoder : header_encoder;
  fisbone_packet : fisbone_packet;
  stream_start : stream_start;
  data_encoder : data_encoder;
  end_of_page : page_end_time;
  end_of_stream : end_of_stream;
}

(** Main type for the ogg encoder *)
type t

(** You may register new tracks on state Eos or Bos.
  * You can't register new track on state Streaming. *)
type state = Eos | Streaming | Bos

(** {2 API} *)

(** Usage:
   *
   * Encoding:
   *
   * - [create ~skeleton name] : create a new encoder
   * - [register_track encoder stream_encoder] : register a new track
   * - ibid
   * - (...)
   * - [streams_start encoder] : start the tracks (optional)
   * - [encode encoder track_serial track_data] : encode data for one track
   * - ibid
   * - (...)
   * - (encode data for other tracks)
   * - [end_of_track encoder track_serial] : ends a track. (track end do not need to be simultaneous)
   * - (...)
   * - [end_of_stream encoder]: ends all tracks as well as the encoder. Set Eos state on the encoder.
   * - [register_track encoder stream_encoder] : register a new track, starts a new sequentialized stream
   * - And so on..
   *
   * You get encoded data by calling [get_data], [peek_data].
   *
   * See: http://xiph.org/ogg/doc/oggstream.html for more details on the
   * specifications of an ogg stream. This API reflects exactly what is recommended to do. *)

(** Create a new encoder.
  * Add an ogg skeleton if [skeleton] is [true]. *)
val create : skeleton:bool -> string -> t

(** Get the state of an encoder. *)
val state : t -> state

(** Get and remove encoded data.. *)
val get_data : t -> Strings.t

(** Get header of a stream. *)
val get_header : t -> Strings.t

(** Peek encoded data without removing it. *)
val peek_data : t -> Strings.t

(** Register a new track to the stream.
  * The state needs to be [Bos] or [Eos].
  *
  * [fill] parameter is used to try to control ogg
  * logical page's size. See [Ogg.get_page] for more
  * details.
  *
  * Returns the serial number of the registered ogg
  * stream. *)
val register_track : ?fill:int -> t -> stream_encoder -> nativeint

(** Start streams, set state to [Streaming]. *)
val streams_start : t -> unit

(** Encode data. Implicitly calls [streams_start]
  * if not called before. Fails if state is not [Streaming] *)
val encode : t -> nativeint -> track_data -> unit

(** Finish a track. Raises [Not_found] if
  * no such track exists. *)
val end_of_track : t -> nativeint -> unit

(** Ends all tracks, flush remaining encoded data.
  * Set state to [Eos]. *)
val end_of_stream : t -> unit

(** {2 Utils} *)

(** flush all availables pages from an ogg stream *)
val flush_pages : Ogg.Stream.stream -> Ogg.Page.t list
