Source file mpg123.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
module Constants = struct
  open C.Types

  let api_version = mpg123_api_version
  let ok = mpg123_ok
  let done_ = mpg123_done
  let flag_id3 = mpg123_id3
  let flag_new_id3 = mpg123_new_id3
  let flag_icy = mpg123_icy
  let flag_new_icy = mpg123_new_icy
  let enc_signed16 = mpg123_enc_signed16
  let enc_float32 = mpg123_enc_float32
end

type error_code = int
type flags = int
type enc = int

include Constants

type id3_v1 =
  { tag : string;
    title : string;
    artist : string;
    album : string;
    year : string;
    comment : string;
    genre : char
  }

type id3_v2_text =
  { lang : string;
    id : string;
    description : string;
    text : string
  }

type id3_v2_picture =
  { type_ : char;
    description : string;
    mime_type : string;
    size : int;
    data : string
  }

type id3_v2 =
  { version : char;
    title : string;
    artist : string;
    album : string;
    year : string;
    genre : string;
    comment : string;
    comment_list : id3_v2_text list;
    text : id3_v2_text list;
    extra : id3_v2_text list;
    picture : id3_v2_picture list
  }

type output_format =
  { rate : int;
    channels : int;
    encoding : int
  }

(*
let memcpy ~dest ~src n =
  let cast p = from_voidp (array n uchar) p in
  cast dest <-@ !@(cast src)
*)

(*
let char_array_as_string a =
	let len = Ctypes.CArray.length a in
	let b = Buffer.create len in
	try
		for i = 0 to len -1 do
			let c = Ctypes.CArray.get a i in
			if c = '\x00'
			then raise Exit
			else Buffer.add_char b c
		done;
		Buffer.contents b
	with Exit ->
		Buffer.contents b
*)
let char_array_as_string a = Ctypes.(string_from_ptr (CArray.start a) ~length:(CArray.length a))

module Functions = struct
  open Ctypes
  open C.Functions

  type handle = C.Types.Handle.t ptr

  let ok_unit_or_err err = if err = ok then Ok () else Error (err : error_code)
  let init () = ok_unit_or_err (mpg123_init ())
  let exit = mpg123_exit

  let new_ ?decoder () =
    let errp = allocate int 0 in
    let h = mpg123_new decoder errp in
    let err = !@errp in
    if err = ok then Ok (h : handle) else Error (err : error_code)

  let delete mh = mpg123_delete mh
  let plain_strerror = mpg123_plain_strerror
  let strerror = mpg123_strerror
  let errcode = mpg123_errcode

  let rec copy_char_pp acc cpp =
    match !@cpp with
    | Some s -> copy_char_pp (s :: acc) (cpp +@ 1)
    | None -> acc

  let decoders () =
    let cpp = mpg123_decoders () in
    copy_char_pp [] cpp

  let supported_decoders () =
    let cpp = mpg123_supported_decoders () in
    copy_char_pp [] cpp

  let decoder mh ~decoder_name = ok_unit_or_err (mpg123_decoder mh decoder_name)
  let current_decoder = mpg123_current_decoder
  let open_ mh ~path = ok_unit_or_err (mpg123_open mh path)
  let close mh = ok_unit_or_err (mpg123_close mh)

  type buf = char CArray.t

  let create_buf len = CArray.make char ~initial:'\x00' len

  let copy_buf_to_bytes buf bytes =
    (* We could eliminate this copy by switching to ocaml_bytes_start
       and using the ocaml_bytes type in foreign, but it force us to
       hold the runtime lock during the read, which would be worse
       for app latency. *)
    assert (CArray.length buf = Bytes.length bytes);
    for i = 0 to pred (CArray.length buf) do
      Bytes.unsafe_set bytes i (CArray.unsafe_get buf i)
    done

  let read mh ~buf ~len =
    let bytes_read = allocate int 0 in
    let retval = mpg123_read mh (CArray.start buf) len bytes_read in
    if retval = ok
    then Ok !@bytes_read
    else if retval = done_
    then if !@bytes_read = 0 then Error (retval : error_code) else Ok !@bytes_read
    else Error (retval : error_code)

  let scan mh = ok_unit_or_err (mpg123_scan mh)

  let length mh =
    let result = mpg123_length mh in
    if result >= 0 then Ok result else Error (result : error_code)

  let meta_check mh : flags = mpg123_meta_check mh
  let meta_free = mpg123_meta_free

  let id3 mh =
    let null_v1 = from_voidp C.Types.Id3v1.t null in
    let id3v1 = allocate (ptr C.Types.Id3v1.t) null_v1 in
    let null_v2 = from_voidp C.Types.Id3v2.t null in
    let id3v2 = allocate (ptr C.Types.Id3v2.t) null_v2 in
    let err = mpg123_id3 mh id3v1 id3v2 in
    if ok <> err
    then Error err
    else (
      let v1 =
        if is_null id3v1
        then None
        else (
          let v1 = !@id3v1 in
          if is_null v1
          then None
          else (
            let v1 = !@(v1 +@ 0) in
            let module ID = C.Types.Id3v1 in
            let cass = char_array_as_string in
            Some
              { tag = cass @@ getf v1 ID.tag;
                title = cass @@ getf v1 ID.title;
                artist = cass @@ getf v1 ID.artist;
                album = cass @@ getf v1 ID.album;
                year = cass @@ getf v1 ID.year;
                comment = cass @@ getf v1 ID.comment;
                genre = getf v1 ID.genre
              }))
      in
      let v2 =
        if is_null id3v2
        then None
        else (
          let v2 = !@id3v2 in
          if is_null v2
          then None
          else (
            let v2 = !@(v2 +@ 0) in
            let module ID = C.Types.Id3v2 in
            let module MS = C.Types.Mpg123_string in
            let module MT = C.Types.Mpg123_text in
            let module MP = C.Types.Mpg123_picture in
            let read_string ms =
              let len = getf ms MS.fill in
              if len < 1
              then ""
              else (
                let a = CArray.from_ptr (getf ms MS.p) (len - 1) in
                char_array_as_string a)
            in
            let get_mpg123_string x =
              let ms = getf v2 x in
              if is_null ms then "" else read_string !@ms
            in
            let get_mpg123_texts num x =
              let len = getf v2 num in
              let ms = getf v2 x in
              if is_null ms
              then []
              else if len = 0
              then []
              else (
                let ms = CArray.from_ptr ms len in
                let cass = char_array_as_string in
                let f i acc =
                  if i = len
                  then List.rev acc
                  else (
                    let mt = CArray.get ms i in
                    { lang = cass @@ getf mt MT.lang;
                      id = cass @@ getf mt MT.id;
                      description = read_string (getf mt MT.description);
                      text = read_string (getf mt MT.text)
                    }
                    :: acc)
                in
                f 0 [])
            in
            let get_mpg123_pictures num x =
              let len = getf v2 num in
              let ms = getf v2 x in
              if is_null ms
              then []
              else if len = 0
              then []
              else (
                let ms = CArray.from_ptr ms len in
                let cass = char_array_as_string in
                let f i acc =
                  if i = len
                  then List.rev acc
                  else (
                    let mp = CArray.get ms i in
                    let size = getf mp MP.size in
                    let data = CArray.from_ptr (getf mp MP.data) (size - 1) in
                    { type_ = getf mp MP.type_;
                      description = read_string (getf mp MP.description);
                      mime_type = read_string (getf mp MP.mime_type);
                      size = getf mp MP.size;
                      data = cass data
                    }
                    :: acc)
                in
                f 0 [])
            in
            let get = get_mpg123_string in
            let get_texts = get_mpg123_texts in
            let get_pics = get_mpg123_pictures in
            Some
              { version = getf v2 ID.version;
                title = get ID.title;
                artist = get ID.artist;
                album = get ID.album;
                year = get ID.year;
                genre = get ID.genre;
                comment = get ID.comment;
                comment_list = get_texts ID.comments ID.comment_list;
                text = get_texts ID.texts ID.text;
                extra = get_texts ID.extras ID.extra;
                picture = get_pics ID.pictures ID.picture
              }))
      in
      Ok (v1, v2))

  let getformat mh =
    let rate = allocate int 0 in
    let channels = allocate int 0 in
    let encoding = allocate int 0 in
    let retval = mpg123_getformat mh rate channels encoding in
    if retval = ok
    then Ok { rate = !@rate; channels = !@channels; encoding = !@encoding }
    else Error (retval : error_code)

  let format_none mh = ok_unit_or_err (mpg123_format_none mh)

  let format_ mh ~rate ~channels ~encodings =
    ok_unit_or_err (mpg123_format mh rate channels encodings)
end

include Functions