Source file format.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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
(*****************************************************************************)
(*                                                                           *)
(*  Copyright (C) 2026 Yves Ndiaye                                           *)
(*                                                                           *)
(* This Source Code Form is subject to the terms of the Mozilla Public       *)
(* License, v. 2.0. If a copy of the MPL was not distributed with this       *)
(* file, You can obtain one at https://mozilla.org/MPL/2.0/.                 *)
(*                                                                           *)
(*****************************************************************************)

module Ids = SkhcDb.Ids.Map
module Strings = Map.Make (String)
module Roles = SkhcDb.Roles
module Metadata = SkhcMetadata.Metadata

let names s = String.concat " | " s

let participants' participants =
  let a, b, c =
    Ids.fold
      (fun _ (name, roles) ->
        Roles.fold
          (fun role ((artist_names, album_artist, compositors) as aac) ->
            match role with
            | SkhcDb.Role.Artist ->
                (name :: artist_names, album_artist, compositors)
            | AlbumArtist ->
                (artist_names, name :: album_artist, compositors)
            | Composer ->
                (artist_names, album_artist, name :: compositors)
            | Unknown ->
                aac
          )
          roles
      )
      participants ([], [], [])
  in
  (List.rev a, List.rev b, List.rev c)

let substitute_f ?metadata m format =
  let format =
    match metadata with
    | None ->
        format
    | Some metadata ->
        SkhcQuery.Format.map
          (function
            | String (Arbitrary s) ->
                let s = String.uppercase_ascii s in
                let s =
                  metadata
                  |> SkhcMetadata.Metadata.property_list s
                  |> String.concat " | "
                in
                Text s
            | s ->
                s
            )
          format
  in
  SkhcQuery.Format.(format |> substitute m |> to_string)

let substitute_safe_f ?metadata map format =
  let map =
    List.map
      (fun (key, value) ->
        let value = SkhcUtil.StringUtil.make_path_safe value in
        (key, value)
      )
      map
  in
  substitute_f ?metadata map format

let filename_f ~extension config metadata format =
  let title =
    let default = SkhcConfig.Config.default_title config in
    Option.value ~default @@ Metadata.title metadata
  in
  let artist_names =
    match Metadata.artists metadata with
    | [] ->
        SkhcConfig.Config.default_artist config
    | _ :: _ as a ->
        names a
  in
  let album_artists = Metadata.album_artists metadata in
  let album =
    let default = config |> SkhcConfig.Config.default_album in
    Option.value ~default @@ Metadata.album metadata
  in
  let composers = Metadata.composers metadata in
  let title_sort = Metadata.title_sort metadata in
  let year = Metadata.year metadata in
  let track = Metadata.track metadata in
  let disc = Metadata.disc metadata in
  let genre = Option.value ~default:String.empty @@ Metadata.genre metadata in
  let some_f = Printf.sprintf "%02d" in
  let zero = "0" in
  let map =
    SkhcQuery.Format.Node.
      [
        (String Title, title);
        (String SortName, Option.value ~default:String.empty title_sort);
        (String Artist, artist_names);
        (String AlbumArtist, names album_artists);
        (String Composer, names composers);
        (String Album, album);
        (String Genre, genre);
        (String FileExtension, extension);
        (Int Track, Option.fold ~some:some_f ~none:zero track);
        (Int Year, Option.fold ~some:some_f ~none:zero year);
        (Int Disc, Option.fold ~some:some_f ~none:zero disc);
      ]
  in
  substitute_safe_f map format

let filename ~extension config metadata format =
  filename_f ~extension config metadata (SkhcQuery.Format.of_string format)

let music_f music album artist_roles properties format =
  (* Cannot open SkhcDb otherwise ocaml/dune treat SkhcDb.Artist like Artist
    and it causes dependencies cycles
  *)
  let artist_names, album_artists, composers = participants' artist_roles in
  let metadata = Metadata.init None [||] properties in
  let zero = "0" in
  let title = Metadata.title metadata in
  let title_sort = Metadata.title_sort metadata in
  let year = Metadata.year metadata in
  let track = Metadata.track metadata in
  let disc = Metadata.disc metadata in
  let music_path = SkhcDb.MusicFile.path music in
  let extension = Filename.extension music_path in
  let map =
    SkhcQuery.Format.Node.
      [
        (String Title, Option.value ~default:String.empty title);
        (String SortName, Option.value ~default:String.empty title_sort);
        (String Artist, names artist_names);
        (String Album, SkhcDb.Album.name album);
        (String AlbumArtist, names album_artists);
        (String Composer, names composers);
        ( String Genre,
          Option.value ~default:String.empty @@ SkhcDb.Album.genre album
        );
        (String Path, music_path);
        (String FileExtension, extension);
        (Bool Art, Bool.to_string @@ SkhcDb.MusicFile.has_cover music);
        (Bool Flag, Bool.to_string @@ SkhcDb.MusicFile.flag music);
        (Int64 Id, Printf.sprintf "%Lu" (SkhcDb.MusicFile.id music));
        (Int64 Size, Printf.sprintf "%u" (SkhcDb.MusicFile.size music));
        (Int Track, Option.fold ~some:string_of_int ~none:zero track);
        (Int Disc, Option.fold ~some:string_of_int ~none:zero disc);
        (Int Year, Option.fold ~some:string_of_int ~none:zero year);
        (Int Plays, string_of_int (SkhcDb.MusicFile.play_count music));
        (Int Length, string_of_int (SkhcDb.MusicFile.duration music));
        ( Int Rating,
          Option.fold ~some:string_of_int ~none:String.empty
            (SkhcDb.MusicFile.rating music)
        );
        (Int AlbumCount, string_of_int 1);
        (Float AvgDuration, string_of_int (SkhcDb.MusicFile.duration music));
        ( Float AvgRating,
          Option.fold ~some:string_of_int ~none:String.empty
            (SkhcDb.MusicFile.rating music)
        );
        (Float AvgSize, Printf.sprintf "%u" (SkhcDb.MusicFile.size music));
      ]
  in
  substitute_f ~metadata map format

let music music album artist_roles properties format =
  music_f music album artist_roles properties (SkhcQuery.Format.of_string format)

let album_f ~safe ~extension album stats participants format =
  let artist_names, album_artists, composers = participants' participants in
  let album_name = SkhcDb.Album.name album in
  let artwork =
    album |> SkhcDb.Album.cover_path |> Option.is_some |> Bool.to_string
  in
  let flag = album |> SkhcDb.Album.flag |> Bool.to_string in

  let map =
    SkhcQuery.Format.Node.
      [
        (String Title, album_name);
        ( String SortName,
          Option.value ~default:String.empty (SkhcDb.Album.sort_name album)
        );
        (String Artist, names artist_names);
        (String Album, album_name);
        (String AlbumArtist, names album_artists);
        (String Composer, names composers);
        ( String Genre,
          Option.value ~default:String.empty @@ SkhcDb.Album.genre album
        );
        (Bool Art, artwork);
        (Bool Flag, flag);
        (Int64 Id, Printf.sprintf "%Lu" (SkhcDb.Album.id album));
        (Int64 Size, Printf.sprintf "%Lu" (SkhcDb.Album.Stats.size stats));
        ( Int Year,
          Option.fold ~some:string_of_int ~none:String.empty
            (SkhcDb.Album.year album)
        );
        (Int Plays, string_of_int (SkhcDb.Album.Stats.play_count stats));
        (Int Length, string_of_int (SkhcDb.Album.Stats.duration stats));
        ( Int Rating,
          Option.fold ~some:string_of_int ~none:String.empty
            (SkhcDb.Album.rating album)
        );
        (Int AlbumCount, string_of_int (SkhcDb.Album.Stats.music_count stats));
        ( Float AvgDuration,
          string_of_float (SkhcDb.Album.Stats.avg_duration stats)
        );
        (Float AvgRating, string_of_float (SkhcDb.Album.Stats.avg_rating stats));
        (Float AvgSize, string_of_float (SkhcDb.Album.Stats.avg_size stats));
      ]
  in
  let map =
    match extension with
    | None ->
        map
    | Some extension ->
        (String FileExtension, extension) :: map
  in
  match safe with
  | false ->
      substitute_f map format
  | true ->
      substitute_safe_f map format

let album ~safe ~extension album stats participants format =
  album_f ~safe ~extension album stats participants
    (SkhcQuery.Format.of_string format)

let album_metadata_f ~safe ~extension metadata format =
  let artist_names = SkhcMetadata.Metadata.artist ~sep:", " metadata in
  let album_artists = SkhcMetadata.Metadata.album_artist ~sep:", " metadata in
  let composers = SkhcMetadata.Metadata.composer ~sep:", " metadata in
  let album_name =
    Option.value ~default:String.empty @@ SkhcMetadata.Metadata.album metadata
  in
  let sort_name = SkhcMetadata.Metadata.album_sort metadata in
  let genre = SkhcMetadata.Metadata.genre metadata in
  let artwork =
    metadata |> SkhcMetadata.Metadata.covers |> Array.length |> ( <> ) 0
    |> Bool.to_string
  in
  let map =
    SkhcQuery.Format.Node.
      [
        (String Title, album_name);
        (String SortName, Option.value ~default:String.empty sort_name);
        (String Artist, artist_names);
        (String AlbumArtist, album_artists);
        (String Album, album_name);
        (String Composer, composers);
        (String Genre, Option.value ~default:String.empty genre);
        (Bool Art, artwork);
      ]
  in
  let map =
    match extension with
    | None ->
        map
    | Some extension ->
        (String FileExtension, extension) :: map
  in
  match safe with
  | false ->
      substitute_f map format
  | true ->
      substitute_safe_f map format

let album_metadata ~safe ~extension metadata format =
  album_metadata_f ~safe ~extension metadata (SkhcQuery.Format.of_string format)

let artist_f ~safe ~extension artist format =
  let artist_name = SkhcDb.Artist.name artist in
  let artwork =
    artist |> SkhcDb.Artist.artwork |> Option.is_some |> Bool.to_string
  in
  let flag = artist |> SkhcDb.Artist.flag |> Bool.to_string in
  let map =
    SkhcQuery.Format.Node.
      [
        (Int64 Id, Printf.sprintf "%Lu" (SkhcDb.Artist.id artist));
        ( String SortName,
          Option.value ~default:String.empty (SkhcDb.Artist.sort_name artist)
        );
        (String Artist, artist_name);
        (String AlbumArtist, artist_name);
        (String Composer, artist_name);
        (Bool Art, artwork);
        (Bool Flag, flag);
        ( Int Rating,
          Option.fold ~some:string_of_int ~none:String.empty
            (SkhcDb.Artist.rating artist)
        );
        ( Float AvgRating,
          Option.fold ~some:string_of_int ~none:String.empty
            (SkhcDb.Artist.rating artist)
        );
      ]
  in
  let map =
    match extension with
    | None ->
        map
    | Some extension ->
        (String FileExtension, extension) :: map
  in
  match safe with
  | false ->
      substitute_f map format
  | true ->
      substitute_safe_f map format

let artist ~safe ~extension artist format =
  artist_f ~safe ~extension artist (SkhcQuery.Format.of_string format)