package tiny_libs

  1. Overview
  2. Docs
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_libs.graphics_avi/Avi.ml.html

Source file Avi.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Avi.mli *)

type header = { width : int; height : int; rate : int * int; codec : string }

(*****************************************************************************)
(* RIFF *)
(*****************************************************************************)

let u16 (s : string) (i : int) : int = String.get_uint16_le s i
let u32 (s : string) (i : int) : int = Int32.to_int (String.get_int32_le s i) land 0xFFFFFFFF

(* the chunks between [from] and [upto]: (name, where its bytes start,
 * their size); a LIST's name is its type ("hdrl", "movi"), its bytes
 * after the type *)
let chunks (s : string) (from : int) (upto : int) : (string * int * int) list =
  let upto = min upto (String.length s) in
  let rec go i acc =
    if i + 8 > upto then List.rev acc
    else
      let id = String.sub s i 4 and size = u32 s (i + 4) in
      let size = min size (upto - i - 8) in
      let entry = if id = "LIST" && size >= 4 then (String.sub s (i + 8) 4, i + 12, size - 4) else (id, i + 8, size) in
      go (i + 8 + size + (size land 1)) (entry :: acc)
  in
  go from []

let find (name : string) (cs : (string * int * int) list) : (int * int) option =
  List.find_map (fun (n, at, size) -> if n = name then Some (at, size) else None) cs

(*****************************************************************************)
(* Reading *)
(*****************************************************************************)

type stream = { kind : string; handler : string; scale : int; rate : int; format : int * int (* strf *) }

let of_string (s : string) : header * Movie.t * Signal.t option =
  if String.length s < 12 || String.sub s 0 4 <> "RIFF" || String.sub s 8 4 <> "AVI " then failwith "AVI: not an AVI file";
  let top = chunks s 12 (String.length s) in
  let hdrl = match find "hdrl" top with Some (at, size) -> chunks s at (at + size) | None -> failwith "AVI: no headers (hdrl)" in
  let movi = match find "movi" top with Some (at, size) -> (at, size) | None -> failwith "AVI: no data (movi)" in
  (* the streams, in order: stream n's chunks are named "0n.." *)
  let streams =
    List.filter_map
      (fun (n, at, size) ->
        if n <> "strl" then None
        else
          let cs = chunks s at (at + size) in
          match (find "strh" cs, find "strf" cs) with
          | Some (h, _), Some f -> Some { kind = String.sub s h 4; handler = String.sub s (h + 4) 4; scale = u32 s (h + 20); rate = u32 s (h + 24); format = f }
          | _ -> None)
      hdrl
  in
  let numbered = List.mapi (fun i st -> (i, st)) streams in
  let video = match List.find_opt (fun (_, st) -> st.kind = "vids") numbered with Some v -> v | None -> failwith "AVI: no video stream" in
  let vn, vs = video in
  let bitmap, _ = vs.format in
  let width = u32 s (bitmap + 4) and height = abs (Int32.to_int (String.get_int32_le s (bitmap + 8))) in
  let codec = String.sub s (bitmap + 16) 4 in
  if String.uppercase_ascii codec <> "MJPG" then failwith ("AVI: the codec " ^ String.escaped codec ^ ", not read here (only Motion JPEG)");
  (* the data: every chunk of "movi", "rec " lists opened *)
  let rec data at size = List.concat_map (fun (n, a, sz) -> if n = "rec " then data a sz else [ (n, a, sz) ]) (chunks s at (at + size)) in
  let data = data (fst movi) (snd movi) in
  let of_stream n = List.filter (fun (name, _, _) -> String.length name = 4 && int_of_string_opt (String.sub name 0 2) = Some n) data in
  let frames = Array.of_list (List.filter (fun (name, _, sz) -> (String.sub name 2 2 = "dc" || String.sub name 2 2 = "db") && sz > 0) (of_stream vn)) in
  if frames = [||] then failwith "AVI: no frames";
  let rate = if vs.rate > 0 && vs.scale > 0 then (vs.rate, vs.scale) else (1_000_000, max 1 (match find "avih" hdrl with Some (a, _) -> u32 s a | None -> 40_000)) in
  let period = float_of_int (snd rate) /. float_of_int (fst rate) in
  let count = Array.length frames in
  (* the last frame decoded, kept: a player shows each 2 or 3 times (60
   * screens a second for 25 frames) *)
  let last = ref (-1, Rgba_image.create ~width:0 ~height:0) in
  let decode i =
    if fst !last <> i then (let _, at, size = frames.(i) in last := (i, Jpeg.decode (String.sub s at size)));
    snd !last
  in
  let movie : Movie.t =
    {
      width;
      height;
      times = Array.init count (fun i -> float_of_int i *. period);
      duration = float_of_int count *. period;
      (* each frame a JPEG of its own: decoded alone *)
      frame = decode;
    }
  in
  (* the sound: the first audio stream's chunks, end to end *)
  let sound =
    match List.find_opt (fun (_, st) -> st.kind = "auds") numbered with
    | None -> None
    | Some (an, st) ->
        let fmt, _ = st.format in
        let tag = u16 s fmt and channels = u16 s (fmt + 2) and rate = u32 s (fmt + 4) and bits = u16 s (fmt + 14) in
        if tag <> 1 || (bits <> 8 && bits <> 16) || (channels <> 1 && channels <> 2) then failwith "AVI: sound not 8 or 16-bit PCM, mono or stereo";
        let bytes = String.concat "" (List.map (fun (_, at, size) -> String.sub s at size) (of_stream an)) in
        let width = bits / 8 * channels in
        let sample i c =
          let at = (i * width) + (c * bits / 8) in
          if bits = 16 then float_of_int (String.get_int16_le bytes at) /. 32767. else float_of_int (Char.code bytes.[at] - 128) /. 127.
        in
        let mono = Array.init (String.length bytes / width) (fun i -> if channels = 1 then sample i 0 else (sample i 0 +. sample i 1) /. 2.) in
        Some (Resample.to_rate Linear rate mono)
  in
  ({ width; height; rate; codec }, movie, sound)

(*****************************************************************************)
(* Writing *)
(*****************************************************************************)

(* a chunk, its bytes padded to even; a list, its type then its chunks *)
let chunk (b : Buffer.t) (id : string) (body : string) : unit =
  Buffer.add_string b id;
  Buffer.add_int32_le b (Int32.of_int (String.length body));
  Buffer.add_string b body;
  if String.length body land 1 = 1 then Buffer.add_char b '\000'

let list (b : Buffer.t) (typ : string) (body : string) : unit = chunk b "LIST" (typ ^ body)

(* little-endian numbers into a string *)
let le (fields : [ `U16 of int | `U32 of int | `S of string ] list) : string =
  let b = Buffer.create 64 in
  List.iter (function `U16 v -> Buffer.add_uint16_le b v | `U32 v -> Buffer.add_int32_le b (Int32.of_int v) | `S s -> Buffer.add_string b s) fields;
  Buffer.contents b

let to_string ?(quality = 75) ?(sound : Signal.t option) ~(rate : int * int) (frames : Rgba_image.t list) : string =
  let first = match frames with [] -> invalid_arg "Avi.to_string: no frames" | f :: _ -> f in
  let w = first.width and h = first.height and num, den = rate in
  let n = List.length frames in
  (* the data, interleaved: each frame, then the sound that plays with
   * it; each chunk's name, offset (from "movi") and size, for the index *)
  let movi = Buffer.create (n * 8192) and index = ref [] in
  let add id body =
    index := (id, 4 + Buffer.length movi, String.length body) :: !index;
    chunk movi id body
  in
  let sample_at i = i * Signal.rate * den / num in
  List.iteri
    (fun i (img : Rgba_image.t) ->
      if img.width <> w || img.height <> h then invalid_arg "Avi.to_string: frames of different sizes";
      add "00dc" (Jpeg_encode.encode ~quality img);
      Option.iter
        (fun (samples : Signal.t) ->
          let a = min (Array.length samples) (sample_at i) and z = if i = n - 1 then Array.length samples else min (Array.length samples) (sample_at (i + 1)) in
          if z > a then add "01wb" (String.init (2 * (z - a)) (fun k -> let v = Signal.to_int16 samples.(a + (k / 2)) land 0xFFFF in Char.chr (if k land 1 = 0 then v land 0xFF else v lsr 8))))
        sound)
    frames;
  let largest = List.fold_left (fun m (_, _, size) -> max m size) 0 !index in
  let streams = if sound = None then 1 else 2 in
  let avih =
    le [ `U32 (1_000_000 * den / num); `U32 (largest * num / den); `U32 0; `U32 0x110 (* an index, interleaved *); `U32 n; `U32 0; `U32 streams; `U32 largest; `U32 w; `U32 h; `S (String.make 16 '\000') ]
  in
  let video_strl =
    let b = Buffer.create 128 in
    chunk b "strh" (le [ `S "vidsMJPG"; `U32 0; `U16 0; `U16 0; `U32 0; `U32 den; `U32 num; `U32 0; `U32 n; `U32 largest; `U32 0xFFFFFFFF; `U32 0; `U16 0; `U16 0; `U16 w; `U16 h ]);
    chunk b "strf" (le [ `U32 40; `U32 w; `U32 h; `U16 1; `U16 24; `S "MJPG"; `U32 (w * h * 3); `U32 0; `U32 0; `U32 0; `U32 0 ]);
    Buffer.contents b
  in
  let audio_strl (samples : Signal.t) =
    let b = Buffer.create 128 in
    (* PCM's rate/scale: blocks of 2 bytes (one 16-bit sample), 2 x rate bytes a second *)
    chunk b "strh" (le [ `S "auds"; `U32 0; `U32 0; `U16 0; `U16 0; `U32 0; `U32 2; `U32 (2 * Signal.rate); `U32 0; `U32 (Array.length samples); `U32 largest; `U32 0xFFFFFFFF; `U32 2; `U16 0; `U16 0; `U16 0; `U16 0 ]);
    chunk b "strf" (le [ `U16 1; `U16 1; `U32 Signal.rate; `U32 (2 * Signal.rate); `U16 2; `U16 16 ]);
    Buffer.contents b
  in
  let hdrl = Buffer.create 512 in
  chunk hdrl "avih" avih;
  list hdrl "strl" video_strl;
  Option.iter (fun samples -> list hdrl "strl" (audio_strl samples)) sound;
  let idx1 = Buffer.create (16 * List.length !index) in
  List.iter (fun (id, offset, size) -> Buffer.add_string idx1 (le [ `S id; `U32 0x10 (* a key frame *); `U32 offset; `U32 size ])) (List.rev !index);
  let body = Buffer.create (Buffer.length movi + 1024) in
  list body "hdrl" (Buffer.contents hdrl);
  list body "movi" (Buffer.contents movi);
  chunk body "idx1" (Buffer.contents idx1);
  let file = Buffer.create (Buffer.length body + 12) in
  chunk file "RIFF" ("AVI " ^ Buffer.contents body);
  Buffer.contents file