package shakuhachi
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
A music collection manager
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.2.tar.gz
sha256=c72807d303de137441b643fec1c7630c930ebc792b576783c7961b237c2114db
sha512=770f878bbdac8033d90e904ce8e9c84fc75331c7ff9b35e2204fafe3746f432f2664296dac91942a788b82a2da07478d91b76ed85cd880758d2def8a55144840
doc/src/shakuhachi.query/queryMusic.ml.html
Source file queryMusic.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 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346(*****************************************************************************) (* *) (* 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 Metadata = SkhcMetadata.Metadata module Ids = SkhcDb.Ids module Roles = SkhcDb.Roles include QueryUtil let matches_node substrings (music_file, album, participants, properties) node = let metadata = SkhcMetadata.Metadata.init None [||] properties in let album_name = SkhcDb.Album.name album in let id = SkhcDb.MusicFile.id music_file in let path = SkhcDb.MusicFile.path music_file in let extension = Filename.extension path in let size = SkhcDb.MusicFile.size music_file in let play_count = SkhcDb.MusicFile.play_count music_file in let flag = SkhcDb.MusicFile.flag music_file in let rating = SkhcDb.MusicFile.rating music_file in let duration = SkhcDb.MusicFile.duration music_file in let has_cover = SkhcDb.MusicFile.has_cover music_file in let title = Metadata.title metadata in let sort_name = Metadata.title_sort metadata in let track_number = Metadata.track metadata in let disc_number = Metadata.disc metadata in let year = Metadata.year metadata in let genre = Metadata.genre metadata in match node with | Node.Substring s -> let fields = match_participants participants in let fields = Substrings.( fields |> add_field_if_some Title title |> add_field_if_some SortName sort_name |> add Album album_name |> add_field_if_some Genre genre |> add FileExtension extension |> add Path path |> filter (fun key _ -> List.mem key substrings) |> bindings |> List.map snd ) in List.exists (matches ~insensible:true false s) fields | Field (FString s) -> ( let regex = Field.String.regex s in let insensible = Field.String.insensible s in let field = Field.String.key s in let value = Field.String.value s in match field with | Title -> omatches ~insensible regex value title | SortName -> omatches ~insensible regex value sort_name | Artist -> Ids.Map.exists (fun _ (artist_name, roles) -> Roles.mem Artist roles && matches ~insensible regex value artist_name ) participants | Album -> matches ~insensible regex value album_name | AlbumArtist -> Ids.Map.exists (fun _ (artist_name, roles) -> Roles.mem AlbumArtist roles && matches ~insensible regex value artist_name ) participants | Composer -> Ids.Map.exists (fun _ (artist_name, roles) -> Roles.mem Composer roles && matches ~insensible regex value artist_name ) participants | Genre -> omatches ~insensible regex value genre | Path -> matches ~insensible regex value path | FileExtension -> matches ~insensible regex value extension | Arbitrary key -> let key = String.uppercase_ascii key in let mvalues = Metadata.property_list key metadata in List.exists (matches ~insensible regex value) mvalues ) | Field (FBool b) -> ( let field = Field.Bool.key b in let b = Field.Bool.value b in match field with Art -> b = has_cover | Flag -> flag = b ) | Field (FInt64 i64) -> ( let field = Field.Int64.key i64 in let frange = Field.Int64.value i64 in match field with | Id -> Range.matches frange id | Size -> Range.matches frange (Int64.of_int size) ) | Field (FInt i) -> ( let field = Field.Int.key i in let frange = Field.Int.value i in match field with | Track -> track_number |> Option.map (Range.matches frange) |> Option.value ~default:false | Year -> year |> Option.map (Range.matches frange) |> Option.value ~default:false | Disc -> Option.fold ~none:false ~some:(Range.matches frange) disc_number | Plays -> Range.matches frange play_count | Length -> Range.matches frange duration | Rating -> rating |> Option.map (Range.matches frange) |> Option.value ~default:false | AlbumCount -> Range.matches frange 1 ) | Field (FFloat f) -> ( let field = Field.Float.key f in let frange = Field.Float.value f in match field with | AvgDuration -> Range.matches frange (Float.of_int duration) | AvgRating -> rating |> Option.map (fun rating -> let rating = Float.of_int rating in Range.matches frange rating ) |> Option.value ~default:false | AvgSize -> Range.matches frange (Int.to_float size) ) let matches_metadata bmetadata ex_metadata = let module M = SkhcMetadata.Metadata in let ostre ?(eq = Stdlib.( = )) f lhs rhs = match (f lhs, f rhs) with | None, None -> false | Some lhs, Some rhs -> eq lhs rhs | _ -> false in function | Field.FString s -> ( match Field.String.key s with | Title -> ostre M.title bmetadata ex_metadata | SortName -> ostre M.title bmetadata ex_metadata | Artist -> ostre ~eq:M.Values.subset (M.property M.Keys.artist) bmetadata ex_metadata | Album -> ostre M.album bmetadata ex_metadata | AlbumArtist -> ostre ~eq:M.Values.subset (M.property M.Keys.album_artist) bmetadata ex_metadata | Composer -> ostre ~eq:M.Values.subset (M.property M.Keys.composer) bmetadata ex_metadata | Genre -> ostre ~eq:M.Values.subset (M.property M.Keys.artist) bmetadata ex_metadata | Path -> false | FileExtension -> ostre (M.property_min M.Keys.skhc_file_extension) bmetadata ex_metadata | Arbitrary s -> let s = String.uppercase_ascii s in ostre ~eq:M.Values.subset (M.property s) bmetadata ex_metadata ) | Field.FBool b -> ( match Field.Bool.key b with Art -> false | Flag -> false ) | Field.FInt64 i -> ( match Field.Int64.key i with | Id -> false | Size -> ostre (M.property_min M.Keys.skhc_file_size) bmetadata ex_metadata ) | Field.FInt i -> ( match Field.Int.key i with | Track -> ostre M.track bmetadata ex_metadata | Year -> ostre M.year bmetadata ex_metadata | Disc -> ostre M.disc bmetadata ex_metadata | Plays -> false | Length -> ( let lhs = M.audioproperties bmetadata in let rhs = M.audioproperties ex_metadata in let f (a : Otaglibc.audioproperties) = a.length in match (lhs, rhs) with | Some lhs, Some rhs -> f lhs = f rhs | _ -> false ) | Rating -> false | AlbumCount -> true ) | Field.FFloat f -> ( match Field.Float.key f with AvgDuration | AvgRating | AvgSize -> false ) let compare_key (reversed, key) lhs rhs = let module Strings = Set.Make (String) in let module M = SkhcMetadata.Metadata in let set_roles r map = SkhcDb.Ids.Map.fold (fun _ (name, roles) names -> if SkhcDb.Roles.mem r roles then Strings.add name names else names ) map Strings.empty in let (lm, lalbum, lroles, lprops), (rm, ralbum, rroles, rprops) = if reversed then (rhs, lhs) else (lhs, rhs) in let lprops = SkhcMetadata.Metadata.of_properties ~audioproperties:(SkhcDb.MusicFile.audioproperties lm) lprops in let rprops = SkhcMetadata.Metadata.of_properties ~audioproperties:(SkhcDb.MusicFile.audioproperties rm) rprops in let cmp c f lhs rhs = c (f lhs) (f rhs) in let ocmp c f lhs rhs = Option.compare c (f lhs) (f rhs) in match key with | Field.FString s -> ( let key = Field.String.key s in match key with | Title -> ocmp String.compare M.title lprops rprops | Album -> cmp String.compare SkhcDb.Album.name lalbum ralbum | SortName -> ocmp String.compare M.title_sort lprops rprops | Artist -> let lhs = set_roles Artist lroles in let rhs = set_roles Artist rroles in Strings.compare lhs rhs | AlbumArtist -> let lhs = set_roles AlbumArtist lroles in let rhs = set_roles AlbumArtist rroles in Strings.compare lhs rhs | Composer -> let lhs = set_roles Composer lroles in let rhs = set_roles Composer rroles in Strings.compare lhs rhs | Genre -> ocmp String.compare M.genre lprops rprops | Path -> cmp String.compare SkhcDb.MusicFile.path lm rm | FileExtension -> cmp String.compare (fun s -> Filename.extension @@ SkhcDb.MusicFile.path s) lm rm | Arbitrary key -> cmp (List.compare String.compare) (M.property_list key) lprops rprops ) | FBool b -> ( let key = Field.Bool.key b in match key with | Art -> cmp Bool.compare SkhcDb.MusicFile.has_cover lm rm | Flag -> cmp Bool.compare SkhcDb.MusicFile.flag lm rm ) | FInt64 i64 -> ( let key = Field.Int64.key i64 in match key with | Id -> cmp Int64.compare SkhcDb.MusicFile.id lm rm | Size -> cmp Int.compare SkhcDb.MusicFile.size lm rm ) | FInt i -> ( let field = Field.Int.key i in match field with | Year -> ocmp Int.compare M.year lprops rprops | Plays -> (* Even on 32 bit system, the max int is 2^{30} - 1 which is more the 1 billions, so we have some before it overflows *) cmp Int.compare SkhcDb.MusicFile.play_count lm rm | Length -> cmp Int.compare SkhcDb.MusicFile.duration lm rm | Rating -> ocmp Int.compare SkhcDb.MusicFile.rating lm rm | AlbumCount -> 0 | Track -> ocmp Int.compare M.track lprops rprops | Disc -> ocmp Int.compare M.disc lprops rprops ) | FFloat f -> ( let key = Field.Float.key f in match key with | AvgDuration -> cmp Int.compare SkhcDb.MusicFile.duration lm rm | AvgRating -> ocmp Int.compare SkhcDb.MusicFile.rating lm rm | AvgSize -> cmp Int.compare SkhcDb.MusicFile.size lm rm )
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>