API
Kaiken, mitä käyttöliittymä tekee, voi tehdä skripti: paikallinen REST-API, tunnus, TypeScript-SDK ja esimerkit.
1:45FLOGVIT.photosuite ajaa paikallista REST-APIa, ja kaikki, mitä käyttöliittymä osaa, on siinä. Ei suurin osa: kaikki. Sovelluksen testi kaataa koostamisen, kun jollakin reitillä ei ole dokumentaatiota, ja toinen, kun kehitykseen tulee asetus, jota API ei tarjoa.
Se on säännön koko tarkoitus. Kirjoittamasi skripti ei kierrä sovellusta — se käyttää samaa ovea.
Aloitus#
API kuuntelee osoitteessa 127.0.0.1:9100, kun sovellus on auki, ja se vaatii bearer-tunnuksen. Koneesi ei ole ainoa asia, joka koneellasi pyörii.
export CULLING_API_TOKEN=$(culling --print-api-token)
curl -H "Authorization: Bearer $CULLING_API_TOKEN" \
http://127.0.0.1:9100/api/status
Aseta CULLING_API_TOKEN ennen sovelluksen käynnistämistä, niin se käyttää sitä tunnusta avainnipun tunnuksen sijaan — näin sitä ajetaan valvomatta koneella, jolla ei ole avattavaa avainnippua.
Sovelluksen ollessa käynnissä osoitteessa localhost:9100/docs on myös vuorovaikutteinen selain, ja raaka OpenAPI-dokumentti osoitteessa /api-doc/openapi.json. Kumpikin on auki ilman tunnusta.
TypeScript-SDK#
SDK on yksi tiedosto, eikä mitään tarvitse asentaa. Tallenna photosuite-sdk.mjs skriptisi viereen ja photosuite-sdk.d.mts sen viereen, jos haluat tyypit editoriisi:
curl -O https://photosuite.flogvit.com/sdk/photosuite-sdk.mjs
curl -O https://photosuite.flogvit.com/sdk/photosuite-sdk.d.mts
Tuo se polullaan ja aja skripti Bunilla (bun hello.ts) tai Node 24:llä (node hello.ts; Node 22:lla node --experimental-strip-types hello.ts). Ilman asetuksia se löytää tämän koneen sovelluksen ja sen tunnuksen itse tiedostosta, jonka sovellus kirjoittaa API:n käynnistyessä. Valmiita skriptejä aloittamiseen ja SDK-tiedoston löydät skriptikokoelmasta.
import { CullingClient } from './photosuite-sdk.mjs'
const client = new CullingClient() // reads CULLING_API_TOKEN
await client.folder.open('/Users/me/Pictures/2026-06-06 Coots')
const status = await client.status()
console.log(`${status.total_images} frames`)
SDK on järjestetty kuten sovellus: client.navigate, client.images, client.selection, client.burst, client.develop, client.export, client.metadata, client.iptc, client.folder, client.catalog, client.library, client.eventphotos, client.upload, client.connections, client.workflow, client.tether, client.setups, client.ui, client.pipeline. Jokainen sovelluksen reitti on jonkin niistä metodi.
Karsinta skriptistä#
await client.filter.set({ min_rating: 3 })
// `sharpness` is null until the background pass has scored the frame
const soft = (await client.images.list())
.filter((image) => image.sharpness !== null && image.sharpness < 400)
.map((image) => image.index)
if (soft.length > 0) {
await client.batch.apply({ indices: soft, action: 'set_flag', value: 'reject' })
}
Erä asettaa lipun. client.images.reject() vaihtaa sen näytöllä olevalle kuvalle kuten näppäin, joten kahdesti ajettu skripti poistaisi omat hylkäyksensä.
Muokkaus skriptistä#
Jokainen säätö on täällä, ja niin on jokainen työkalukin.
// The same edit across a whole shoot
await client.develop.set({ exposure_ev: 0.4, shadows: 25, vibrance: 15 })
await client.develop.copy()
await client.selection.all()
await client.develop.paste()
// …or just the white balance, leaving each frame's own exposure alone
await client.develop.paste(['color'])
Maskit ovat rakenteita eivätkä eleitä, joten niitä on yhtä helppo kirjoittaa kuin piirtää:
await client.develop.setMasks([
{
name: 'Sky',
components: [
{ geometry: { kind: 'linear', x0: 0.5, y0: 0, x1: 0.5, y1: 0.35 } },
{ geometry: { kind: 'luminance', low: 0.45, high: 1, feather: 0.25 },
mode: 'intersect' },
],
values: { exposure_ev: -0.6, highlights: -35 },
},
])
Ja kuvaa lukevat työkalut:
await client.images.autoLevel() // finds the angle from the frame's edges
await client.develop.addSkyMask()
await client.images.constrainCrop() // crop clear of a perspective correction
Kukin kieltäytyy arvaamasta, kun kuva ei anna sille mitään, mihin tarttua, ja kertoo sen vastauksessa — kuva, jossa ei ole suoraa reunaa, jätetään ennalleen sen sijaan, että sitä kallistettaisiin sattumanvaraisesti.
Toimitus#
Putki on vaiheiden ketju, jossa kukin ottaa kahvan ja palauttaa uuden.
const session = await client.pipeline.open('/Volumes/Card/DSC00042.ARW')
await session.handle
.develop() // the picture, with the edits stored beside the file
.autoCrop('16:9')
.resize(2048)
.export(null, '/tmp/out') // null: a local folder rather than a connection
await client.pipeline.close(session.session)
develop() on vaihe, jolla on merkitystä, ja se, jonka ihmiset jättävät pois. Ilman sitä putki lataa RAW-tiedoston uudelleen ja piirtää sen sellaisena kuin se otettiin — jokainen kuvaajan korjaus, maski ja roskanpoisto putoaa matkalla asiakkaalle, eikä mikään epäonnistu, koska toisesta päästä tulee silti aivan kelvollinen kuva.
Tapahtumien seuraaminen#
Asiakas yhdistää sovelluksen tapahtumavirtaan itse, samalla tunnuksella kuin kaikki muut kutsut. Jokainen tapahtuma ja sen sisältö on tyypitetty SDK:n EventMapissa. Väärä tai puuttuva tunnus päättää waitFor-kutsun heti 401:llä aikakatkaisun sijaan.
client.on('develop_changed', (e) => console.log('edited', e.index))
client.on('rating_set', (e) => console.log(e.index, 'now has', e.rating, 'stars'))
Käyttöliittymän ohjaaminen#
Näkymät, päällysteet ja työkalutilat ovat myös APIssa, ja se tekee kuvakaappausskriptin mahdolliseksi — jokainen tämän dokumentaation kuva on sovelluksen itse ottama, näillä kutsuilla ohjattuna.
await client.view.setMode('develop')
await client.view.decodeSource('native-raw')
await client.develop.setTools({ before_after: true })
await client.ui.screenshot('main', '/tmp/shot.png')
Mikä paneeli on edessä#
Samaan paikkaan telakoidut paneelit ovat välilehtiä, ja vain yksi niistä on näkyvissä. Paneelin näkyväksi tekeminen ei tee siitä katsomaasi, joten luettavia asioita on kaksi ja asetettavia kutsuja yksi:
for (const panel of await client.ui.panels()) {
console.log(`${panel.title}: ${panel.visible ? 'shown' : 'hidden'}${panel.active ? ', in front' : ''}`);
}
await client.ui.focusPanel('develop');
focusPanel lisää paneelin asetteluun, jos sitä ei ole siellä, joten lopputuloksena paneeli on aina näkyvissä. Nimet ovat folders, filmstrip, sidebar, histogram, viewer, develop, scopes ja map.
Ilman ikkunaa#
Kone, joka vain toimittaa, kuten kilpailun suorat kuvat tai joka yö ajettava skripti, ei tarvitse ikkunaa. Käynnistä sovellus ilman ikkunaa ja ohjaa sitä pelkän API:n kautta:
/Applications/FLOGVIT.photosuite.app/Contents/MacOS/culling --headless
Windowsissa se on FLOGVIT.photosuite.exe --headless kansiossa, johon photosuite on asennettu. Se on koko sovellus ilman ikkunaa ja ilman Dockin kuvaketta: samat asetukset, sama kirjasto, samat workflow’t ja sama API, ja se kehittää näytönohjaimella kuten ikkunan kanssa. API vastaa Settingsin portissa, vaikka se olisi siellä pois päältä, koska se on ainoa tie sisään, ja client.status() kertoo headless: true ja näytönohjaimen nimen.
- Lisenssi on aktivoitava sovelluksessa kerran tällä koneella.
- Anna API-tunnus muuttujassa
CULLING_API_TOKENtai.env-tiedostossa sovelluksen asetuskansiossa (Macissa~/Library/Application Support/flogvit/), jottei sovellus jää odottamaan avainnippua ilman ketään vastaamassa. --screen 3840x2160@2asettaa näytön, jota varten kuvat puretaan ja jossa kuvakaappaukset otetaan. Ilman sitä käytetään Retina-kannettavan näyttöä.- Kuvakaappaukset ja tallennukset toimivat. Se, mikä tarvitsee ikkunan, kuten videon toisto, koko näyttö, ikkunan siirtäminen ja valikon kohdat, jotka avaavat tiedostovalitsimen, vastaa 409 ja kertoo syyn. Reitit ottavat polun sen sijaan.
- Ctrl-C tai
killpysäyttää sen, ja se tallentaa kuten Quit.
Oma kansio#
--dir-valitsimella käynnistetty photosuite asuu yhdessä kansiossa eikä näe mitään sen ulkopuolelta. Sen asetukset, kirjasto, albumit, loki ja mallit ovat kansion .photosuite/-kansiossa, ja kaikessa mitä sille kerrotaan ja mitä se vastaa, / on kansio: /shoot on kansion shoot, ja polku, joka johtaa ulos ..:lla tai linkillä, torjutaan. Samalla koneella voi toimia useita, kullakin oma kansionsa, esimerkiksi yksi tapahtumaa tai asiakasta kohden.
/Applications/FLOGVIT.photosuite.app/Contents/MacOS/culling --headless --dir /Volumes/Events/nm-2026
- Kansion täytyy olla olemassa, ja siinä voi toimia vain yksi instanssi kerrallaan.
- Jos Settingsin portti on varattu, se ottaa vapaan, ja kansion
.photosuite/culling/api.jsonkertoo minkä.new CullingClient({ root: '<kansio>' })lukee sen, ja--port 9200valitsee portin itse. Se on käynnissä, kunclient.status()sanooroot: true. - API-tunnus ja muut salaisuudet tulevat ympäristöstä, kansion
.env-tiedostosta tai.photosuite/.env-tiedostosta, ei koskaan avainnipusta. Sovelluksen itse tallentamat ovat.photosuite/secrets.json-tiedostossa. - Lisenssi on kansion oma, jos sillä on sellainen, muuten koneen, jota se lukee eikä koskaan muuta.
- Mitään kansion ulkopuolista levyä ei listata eikä skannata, eikä kohinanpoistoprofiilin kansion ulkopuolelle kirjoittamaa tulosta oteta. Ikkunan kanssa tiedostovalitsimet avautuvat kansioon ja torjuvat valinnan sen ulkopuolelta.
- Instanssit vuorottelevat DxO PureRAW’n kanssa, joten kaksi ei koskaan aja sitä yhtä aikaa.
- API vastaa vain tällä koneella. Instanssin käynnistämät ohjelmat (kohinanpoistoprofiilin ohjelma, ulkoiset muokkaimet) asetetaan kansion omissa asetuksissa tai ikkunassa, ei koskaan API:n kautta, eikä instanssin oma
.photosuite/ole paikka, jonka API voi nimetä. Kirjaston varmuuskopiot menevät kansionFLOGVIT.photosuite library backups-kansioon. - Jos ne ajetaan palveluina, kukin saa oman launchd-agenttinsa: oman
Label-arvon ja--headless --dir <kansio>ProgramArguments-kohtaan. - Instanssin siirtäminen tai kopioiminen: pysäytä se, siirrä kansio ja käynnistä se siellä. Käynnistyessään se kirjoittaa tallennetut polkunsa uuteen paikkaan. Poistaminen: pysäytä se ja poista kansio; siitä ei jää mitään muualle.
Tällaiselle instanssille kirjoitettu skripti kutsuu ensin requireRoot(). Ilman --dir-valitsinta toimivaa sovellusta vastaan ajettuna se pysähtyy selvään viestiin sen sijaan, että kirjoittaisi koneen omaan /delivery-kansioon:
import { CullingClient } from './photosuite-sdk.mjs'
// The instance started with --dir /Volumes/Events/nm-2026
const client = new CullingClient({ root: '/Volumes/Events/nm-2026' })
await client.requireRoot() // throws against an app without --dir
await client.folder.open('/shoot') // /Volumes/Events/nm-2026/shoot
await client.export.quickJpeg({ output_dir: '/delivery' })
Kaikki reitit#
API-viite luettelee ne kaikki, sovelluksen omasta OpenAPI-dokumentista koostettuna, kunkin ottamat kentät ja curl-rivi kullekin.