Immich bindings and CLI in OCaml
at main 187 lines 7.5 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6open Cmdliner 7 8(* Styled output helpers *) 9let header_style = Fmt.(styled `Bold string) 10let id_style = Fmt.(styled `Faint string) 11let name_style = Fmt.(styled (`Fg `Cyan) string) 12let hidden_style = Fmt.(styled (`Fg `Yellow) string) 13let count_style = Fmt.(styled (`Fg `Green) int) 14let success_style = Fmt.(styled (`Fg `Green) string) 15 16(* Search for people by name *) 17 18let search_action ~requests_config ~profile ~name ~with_hidden env = 19 Immich_auth.Error.wrap (fun () -> 20 Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 21 let api = Immich_auth.Client.client client in 22 let session = Immich.session api in 23 let base_url = Immich.base_url api in 24 (* Person.search_person returns ResponseDto (single) but should be a list *) 25 (* Use low-level API to get proper list response *) 26 let query = Printf.sprintf "?name=%s%s" 27 (Uri.pct_encode name) 28 (if with_hidden then "&withHidden=true" else "") in 29 let url = base_url ^ "/people/search" ^ query in 30 let response = Requests.get session url in 31 if Requests.Response.ok response then begin 32 let json = Requests.Response.json response in 33 let people = Openapi.Runtime.Json.decode_json_exn 34 (Jsont.list Immich.Person.ResponseDto.jsont) json in 35 if people = [] then 36 Fmt.pr "%a '%a'@." 37 Fmt.(styled `Faint string) "No people found matching" 38 name_style name 39 else begin 40 Fmt.pr "%a '%a':@." 41 header_style "People matching" 42 name_style name; 43 List.iter (fun person -> 44 let pname = Immich.Person.ResponseDto.name person in 45 let id = Immich.Person.ResponseDto.id person in 46 let is_hidden = Immich.Person.ResponseDto.is_hidden person in 47 let display_name = if pname = "" then "<unnamed>" else pname in 48 Fmt.pr " %a %a%a@." 49 id_style id 50 name_style display_name 51 hidden_style (if is_hidden then " (hidden)" else "") 52 ) people 53 end 54 end else begin 55 (* Raise API error for proper handling *) 56 raise (Openapi.Runtime.Api_error { 57 operation = "search_people"; 58 method_ = "GET"; 59 url; 60 status = Requests.Response.status_code response; 61 body = Requests.Response.text response; 62 parsed_body = None; 63 }) 64 end 65 ) env 66 ) 67 68let name_arg = 69 let doc = "Name to search for." in 70 Arg.(required & pos 0 (some string) None & info [] ~docv:"NAME" ~doc) 71 72let with_hidden_arg = 73 let doc = "Include hidden people in results." in 74 Arg.(value & flag & info ["with-hidden"] ~doc) 75 76let search_cmd env fs = 77 let doc = "Search for people by name." in 78 let info = Cmd.info "search" ~doc in 79 let search' (style_renderer, level) requests_config profile name with_hidden = 80 Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 81 search_action ~requests_config ~profile ~name ~with_hidden env 82 in 83 Cmd.v info Term.(const search' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ name_arg $ with_hidden_arg) 84 85(* Get person thumbnail *) 86 87let output_arg = 88 let doc = "Output file path. Use '-' for stdout." in 89 Arg.(value & opt string "-" & info ["output"; "o"] ~docv:"FILE" ~doc) 90 91let person_id_arg = 92 let doc = "Person ID." in 93 Arg.(required & pos 0 (some string) None & info [] ~docv:"PERSON_ID" ~doc) 94 95let thumbnail_action ~requests_config ~profile ~person_id ~output env = 96 Immich_auth.Error.wrap (fun () -> 97 Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 98 let api = Immich_auth.Client.client client in 99 let session = Immich.session api in 100 let base_url = Immich.base_url api in 101 let url = Printf.sprintf "%s/people/%s/thumbnail" base_url person_id in 102 let response = Requests.get session url in 103 if Requests.Response.ok response then begin 104 let data = Requests.Response.text response in 105 if output = "-" then 106 print_string data 107 else begin 108 let oc = open_out_bin output in 109 output_string oc data; 110 close_out oc; 111 Fmt.pr "%a %a@." 112 success_style "Thumbnail saved to" 113 Fmt.(styled `Bold string) output 114 end 115 end else begin 116 raise (Openapi.Runtime.Api_error { 117 operation = "get_person_thumbnail"; 118 method_ = "GET"; 119 url; 120 status = Requests.Response.status_code response; 121 body = Requests.Response.text response; 122 parsed_body = None; 123 }) 124 end 125 ) env 126 ) 127 128let thumbnail_cmd env fs = 129 let doc = "Download a person's thumbnail image." in 130 let info = Cmd.info "thumbnail" ~doc in 131 let thumbnail' (style_renderer, level) requests_config profile person_id output = 132 Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 133 thumbnail_action ~requests_config ~profile ~person_id ~output env 134 in 135 Cmd.v info Term.(const thumbnail' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ person_id_arg $ output_arg) 136 137(* List all people *) 138 139let list_action ~requests_config ~profile ~with_hidden env = 140 Immich_auth.Error.wrap (fun () -> 141 Immich_auth.Cmd.with_client ~requests_config ?profile (fun _fs client -> 142 let api = Immich_auth.Client.client client in 143 let with_hidden_param = if with_hidden then Some "true" else None in 144 let resp = Immich.People.get_all_people api ?with_hidden:with_hidden_param () in 145 let people = Immich.People.ResponseDto.people resp in 146 let total = Immich.People.ResponseDto.total resp in 147 let hidden_count = Immich.People.ResponseDto.hidden resp in 148 Fmt.pr "%a %a total, %a hidden@." 149 header_style "People:" 150 count_style total 151 count_style hidden_count; 152 if people = [] then 153 Fmt.pr "%a@." Fmt.(styled `Faint string) "No people found." 154 else begin 155 List.iter (fun person -> 156 let name = Immich.Person.ResponseDto.name person in 157 let id = Immich.Person.ResponseDto.id person in 158 let is_hidden = Immich.Person.ResponseDto.is_hidden person in 159 let display_name = if name = "" then "<unnamed>" else name in 160 Fmt.pr " %a %a%a@." 161 id_style id 162 name_style display_name 163 hidden_style (if is_hidden then " (hidden)" else "") 164 ) people 165 end 166 ) env 167 ) 168 169let list_cmd env fs = 170 let doc = "List all people." in 171 let info = Cmd.info "list" ~doc in 172 let list' (style_renderer, level) requests_config profile with_hidden = 173 Immich_auth.Cmd.setup_logging_with_config style_renderer level requests_config; 174 list_action ~requests_config ~profile ~with_hidden env 175 in 176 Cmd.v info Term.(const list' $ Immich_auth.Cmd.setup_logging $ Immich_auth.Cmd.requests_config_term fs $ Immich_auth.Cmd.profile_arg $ with_hidden_arg) 177 178(* Faces command group *) 179 180let faces_cmd env fs = 181 let doc = "Face and people commands." in 182 let info = Cmd.info "faces" ~doc in 183 Cmd.group info 184 [ list_cmd env fs 185 ; search_cmd env fs 186 ; thumbnail_cmd env fs 187 ]