feat(api): enhance image module management and export functionality
- Added read-write access to the darktable SQLite catalog for module parameter edits. - Implemented new endpoints for exporting images and retrieving full-resolution images. - Introduced PATCH method for updating module parameters with optional preview. - Added comprehensive tests for new API functionalities, including module retrieval and error handling. - Updated CORS headers to support new HTTP methods. - Created detailed documentation for new API endpoints and module operations.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
meta {
|
||||
name: Get Module (Not Found)
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/v1/images/{{imageId}}/modules/nonexistent_module
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{accessToken}}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("returns 404", function() {
|
||||
expect(res.status).to.equal(404);
|
||||
});
|
||||
|
||||
test("error is module_not_found", function() {
|
||||
expect(res.body.error).to.equal("module_not_found");
|
||||
});
|
||||
}
|
||||
|
||||
docs {
|
||||
## Get Module — Not Found
|
||||
|
||||
Verifies 404 is returned for an operation that does not exist in the image history.
|
||||
}
|
||||
55
contrib/darktable-api/bruno/modules/Get Module.bru
Normal file
55
contrib/darktable-api/bruno/modules/Get Module.bru
Normal file
@@ -0,0 +1,55 @@
|
||||
meta {
|
||||
name: Get Module
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/v1/images/{{imageId}}/modules/filmicrgb
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{accessToken}}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("returns 200", function() {
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
|
||||
test("operation is filmicrgb", function() {
|
||||
expect(res.body.operation).to.equal("filmicrgb");
|
||||
});
|
||||
|
||||
test("has enabled flag", function() {
|
||||
expect(res.body.enabled).to.be.a("boolean");
|
||||
});
|
||||
|
||||
test("has decoded params", function() {
|
||||
expect(res.body.params).to.be.an("object");
|
||||
expect(res.body.params.grey_point_source).to.be.a("number");
|
||||
expect(res.body.params.black_point_source).to.be.a("number");
|
||||
expect(res.body.params.white_point_source).to.be.a("number");
|
||||
expect(res.body.params.contrast).to.be.a("number");
|
||||
expect(res.body.params.saturation).to.be.a("number");
|
||||
});
|
||||
|
||||
// Save for the Patch Module test
|
||||
bru.setVar("filmicGreyPointSource", res.body.params.grey_point_source);
|
||||
}
|
||||
|
||||
docs {
|
||||
## Get Module
|
||||
|
||||
Returns the active history entry for the `filmicrgb` module.
|
||||
|
||||
Change the URL path segment to inspect a different module, e.g.:
|
||||
- `.../modules/exposure`
|
||||
- `.../modules/temperature`
|
||||
- `.../modules/highlights`
|
||||
- `.../modules/channelmixerrgb`
|
||||
|
||||
Returns 404 if the operation has never been added to this image's history.
|
||||
}
|
||||
79
contrib/darktable-api/bruno/modules/List Modules.bru
Normal file
79
contrib/darktable-api/bruno/modules/List Modules.bru
Normal file
@@ -0,0 +1,79 @@
|
||||
meta {
|
||||
name: List Modules
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}/api/v1/images/{{imageId}}/modules
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{accessToken}}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("returns 200", function() {
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
|
||||
test("has image_id", function() {
|
||||
expect(res.body.image_id).to.equal(Number(bru.getVar("imageId")));
|
||||
});
|
||||
|
||||
test("has items array", function() {
|
||||
expect(res.body.items).to.be.an("array");
|
||||
expect(res.body.items.length).to.be.above(0);
|
||||
});
|
||||
|
||||
test("each item has required fields", function() {
|
||||
res.body.items.forEach(function(m) {
|
||||
expect(m.operation).to.be.a("string");
|
||||
expect(m.enabled).to.be.a("boolean");
|
||||
expect(m.module_version).to.be.a("number");
|
||||
expect(m.history_num).to.be.a("number");
|
||||
});
|
||||
});
|
||||
|
||||
test("known modules have decoded params", function() {
|
||||
var known = res.body.items.filter(function(m) {
|
||||
return ["exposure","temperature","filmicrgb","highlights",
|
||||
"channelmixerrgb","flip","gamma","rawprepare","demosaic"].indexOf(m.operation) >= 0;
|
||||
});
|
||||
known.forEach(function(m) {
|
||||
expect(m.params).to.not.be.null;
|
||||
expect(typeof m.params).to.equal("object");
|
||||
});
|
||||
});
|
||||
|
||||
test("unknown modules have params_b64", function() {
|
||||
var unknown = res.body.items.filter(function(m) { return m.params === undefined || m.params === null; });
|
||||
unknown.forEach(function(m) {
|
||||
expect(m.params_b64).to.be.a("string");
|
||||
});
|
||||
});
|
||||
|
||||
// Save the first module op for subsequent tests
|
||||
bru.setVar("moduleOp", res.body.items[0].operation);
|
||||
}
|
||||
|
||||
docs {
|
||||
## List Modules
|
||||
|
||||
Returns all active development modules (history stack entries) for the image.
|
||||
|
||||
Each item includes:
|
||||
- `operation` – module name (e.g. `"exposure"`, `"filmicrgb"`)
|
||||
- `enabled` – whether the module is currently active in the pipeline
|
||||
- `module_version` – params struct version stored in the database
|
||||
- `multi_name` – user label for multi-instance modules (empty for single-instance)
|
||||
- `history_num` – position in the history stack (higher = more recent)
|
||||
- `params` – decoded named parameters (for supported modules)
|
||||
- `params_b64` – base64-encoded raw binary blob (for unsupported modules)
|
||||
|
||||
### Supported modules (params decoded)
|
||||
`exposure`, `temperature`, `filmicrgb`, `channelmixerrgb`, `highlights`,
|
||||
`flip`, `gamma`, `rawprepare`, `demosaic`
|
||||
}
|
||||
89
contrib/darktable-api/bruno/modules/Patch Module.bru
Normal file
89
contrib/darktable-api/bruno/modules/Patch Module.bru
Normal file
@@ -0,0 +1,89 @@
|
||||
meta {
|
||||
name: Patch Module
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
patch {
|
||||
url: {{baseUrl}}/api/v1/images/{{imageId}}/modules/filmicrgb
|
||||
body: json
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{accessToken}}
|
||||
}
|
||||
|
||||
params:query {
|
||||
~preview: medium
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"enabled": true,
|
||||
"params": {
|
||||
"grey_point_source": 18.45,
|
||||
"white_point_source": 4.0,
|
||||
"black_point_source": -8.0,
|
||||
"contrast": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("returns 204 (no preview) or 200 (with preview)", function() {
|
||||
expect([200, 204]).to.include(res.status);
|
||||
});
|
||||
|
||||
test("if 200, content-type is image/jpeg", function() {
|
||||
if (res.status === 200) {
|
||||
expect(res.headers["content-type"]).to.equal("image/jpeg");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
docs {
|
||||
## Patch Module
|
||||
|
||||
Update one or more parameters on a development module and optionally get a
|
||||
preview image back showing the result.
|
||||
|
||||
### Body fields
|
||||
- `enabled` (boolean, optional) – enable or disable the module
|
||||
- `params` (object, optional) – named parameter overrides for known modules
|
||||
- `params_b64` (string, optional) – raw base64 binary blob for unknown modules
|
||||
(mutually exclusive with `params`)
|
||||
|
||||
Only fields present in `params` are changed; all others keep their current values.
|
||||
|
||||
### Preview query param
|
||||
Add `?preview=small|medium|large|xlarge` to receive a JPEG thumbnail (200)
|
||||
instead of empty 204. The server waits up to 30 s for the thumbnail to
|
||||
regenerate before returning. Without this param the endpoint returns
|
||||
**204 No Content** immediately (the bridge regenerates async in ~1-3 s).
|
||||
|
||||
### filmicrgb key parameters
|
||||
| Field | Description | Default |
|
||||
|---|---|---|
|
||||
| `grey_point_source` | middle-grey exposure % | 18.45 |
|
||||
| `black_point_source` | black relative exposure EV | -8.0 |
|
||||
| `white_point_source` | white relative exposure EV | 4.0 |
|
||||
| `contrast` | S-curve contrast | 1.0 |
|
||||
| `saturation` | extreme-luminance saturation | 0.0 |
|
||||
| `balance` | shadows ↔ highlights balance | 0.0 |
|
||||
| `auto_hardness` | auto-adjust curve hardness | true |
|
||||
|
||||
### exposure key parameters
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `exposure` | exposure compensation in EV |
|
||||
| `black` | black level correction |
|
||||
| `mode` | 0=manual, 1=deflicker |
|
||||
|
||||
### temperature key parameters
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `red` | red channel multiplier |
|
||||
| `green` | green channel multiplier |
|
||||
| `blue` | blue channel multiplier |
|
||||
}
|
||||
Reference in New Issue
Block a user