tool_media_versions
Manages the on-disk media files behind a media component: inspect per-quality versions, delete a quality or a specific version, (re)build versions from the original, conform AV headers, rotate images, and re-sync the component's stored file metadata with what actually exists on disk.
What it does / why & when to use it
A Dédalo media component (component_image, component_av, component_pdf, component_3d, component_svg) does not store a single file — it stores a set of qualities (e.g. original, default, 404, audio, thumb, ...) derived from the uploaded master, each as a file on disk, plus a files_info block in the record that records which qualities exist. tool_media_versions is the panel a cataloguer or media administrator opens to manage that file set directly, without leaving the record.
It surfaces three things in one modal:
- a live preview of the media component itself (read-only),
- a sync/regenerate area that compares the qualities recorded in the database (
files_info_db) against the qualities actually present on disk (files_info_disk) and flags a mismatch, and - a versions grid: one row per quality, showing the real file (name, size, existence) with per-quality buttons to open, (re)build, rotate, delete, or — for AV — conform headers.
Concrete heritage scenario: an archivist uploads a high-resolution TIFF master for a museum object. The streaming/web qualities (default, 404, thumb) are generated automatically on upload, but the archivist notices the web preview is sideways. They open Media versions on the image component, rotate the affected quality, and the derived files are regenerated rotated. Later, a colleague manually deleted a couple of derived files on the server; the record now shows "Files info data is unsync". The archivist opens the tool, sees the disk/DB mismatch in the Show data panel, optionally ticks Delete normalized files, and presses Regenerate to rebuild the missing qualities and write a correct files_info back to the record. For an AV file that won't seek properly in the browser, they use Conform headers to rewrite the moov atom / headers of a quality.
Use it when: derived media qualities are missing, broken, rotated, un-seekable, or out of sync with the record. Do not use it to upload a new master (that is the upload flow / tool_upload / tool_import_files) — this tool operates on the files of an already-existing media component.
How it works (server + client)
Server (tools/tool_media_versions/server/{index,media_versions}.ts). Every action is declaratively gated permission: 'record', minLevel: 1 (read, get_files_info only) or minLevel: 2 (write, everything else) — the 'record' gate kind asserts both the section/component permission level and the per-record project-scope check in one declarative spec. Each handler reads tipo / section_tipo / section_id (plus action-specific params) from options, validates they are present, resolves the target media component's model, and delegates to the real processing engines the Media rebuild shipped:
| Action | Delegates to |
|---|---|
get_files_info |
the files_info scanner (probes each quality on disk) |
delete_quality |
file-ops soft-delete for the quality |
build_version |
the processing engine (buildImageVersion/buildPdfCover/…, pixel-budget resize, never-upscale, CMYK→sRGB). For component_av it is the av encoder (src/core/media/av_versions.ts): the REQUESTED quality tier is transcoded in a background job (returns job_id), and the thumb tier is derived from the posterframe, never from ffmpeg |
get_job_status |
the media job manager — the poll wire for the job_id an av build_version returns |
conform_headers |
AV header/moov-atom rewrite (component_av-only, per register.json's specific_actions) |
rotate |
the rotation engine (component_image-only) for the matching quality entry |
sync_files |
re-reads data then re-derives the component's stored files_info |
delete_version |
the thumb-specific delete path for the thumb quality, else the general file-ops delete |
All eight actions return the standard { result, msg, errors } envelope; the write actions merge the engine response into that envelope. sync_files re-reads current data before regenerating, so the regeneration runs against current data. rotate reads the component's files_info, matches the requested quality, and runs the rotation for each matching entry — gated against real ImageMagick/ffmpeg output on scratch media.
Client (tools/tool_media_versions/js/). tool_media_versions.js is the instance; it extends the standard tool lifecycle (init / build / render / edit from tool_common). In build() it resolves its main_element from tool_config.ddo_map (the ddo flagged with role: 'main_element'), finds that component instance in ar_instances, and pre-computes the file sets it needs: files_info_db (from the record's entries[0].files_info), files_info_disk (fetched live via get_files_info), and filtered views (files_info_safe, files_info_alternative, files_info_original). render_tool_media_versions.js builds the DOM: the read-only main-element preview, the sync/regenerate row, and the versions grid. Each client method (delete_quality, build_version, conform_headers, rotate, sync_files, delete_version, get_job_status) builds an RQO with dd_api: 'dd_tools_api', action: 'tool_request', source: create_source(self, '<action>'), and the options above, then calls data_manager.request(). The destructive/long actions confirm first (confirm(get_label.sure)) and use a long client timeout (3600 * 1000 ms) with a single retry, because rebuilds can take minutes.
Following a transcode. An av build_version returns a job_id, and the panel follows it over the push stream (core/common/js/job_follow.js) rather than polling — both for a build it started and for one it merely DISCOVERED running (an upload's, or another operator's, reported by get_record_jobs). Those streams are owned by the instance: self.job_followers is a follower group that destroy() cancels and that each render pass cancels at its head, re-following whatever is still live against the nodes it is about to build.
Releasing them is not tidiness
A followed job holds one HTTP connection for as long as it runs, and a browser grants six per origin. Before the group existed, closing and reopening this panel over one long transcode left a live stream behind each time; at the sixth, every request on the page queued indefinitely — /health first, the probe that tells a busy server from a dead one — and the panel froze on Loading… with 6 s timeouts against a healthy server. Any new surface that follows a job must own its followers the same way; see data_manager.
Specific actions. The rotate and conform-headers buttons are not shown on every media model. The tool's properties.specific_actions (from register.json) maps each action to the component models it applies to — rotate → component_image, conform_headers → component_av — and render_tool_media_versions.js only renders a specific action when self.main_element.model is in that action's model list.
Actions & options
apiActions declares each media action with permission: 'record_tipo' — minLevel: 1 for the one read action, minLevel: 2 for the six write actions — so the framework asserts the level on the (section_tipo, component) PAIR and the per-record project-scope check before any handler runs. get_job_status is the exception: it mounts the shared MEDIA_JOB_STATUS_ACTION spec with permission: null, because dispatch gates 1–4 already require a caller authorized for this tool and the handler applies the job-record ownership rule itself. build_version is the one backgroundRunnable action.
All actions read the same three required options — tipo (the component tipo), section_tipo, section_id — and refuse with an error envelope if any are missing.
| Action | Gate | Extra required / read options | Purpose |
|---|---|---|---|
get_files_info |
record_tipo, level 1 |
— | Probe disk for every quality; returns the files_info array (existence, name, path, url, size, time per quality) as result, plus files_info_db — what the RECORD stores for the same lang, read in the same breath. The panel compares the two to raise the "files info data is unsync" warning, so both sides must come from one answer: taking the DB side from the component's cached data made every delete raise a false alarm that a page reload cleared. |
delete_quality |
record_tipo, level 2 |
quality (req.) |
Delete the file of one quality. |
build_version |
record_tipo, level 2 |
quality (req.), extension (optional), async (default true) |
(Re)build the requested quality from the original (falling back to the default-quality file when the box has no original). For av it returns job_id and encodes in the background; refusals that are knowable up front — no source, no encode profile for the tier, no audio stream for an audio tier, the original as target — come back as result:false before a job exists. |
get_job_status |
dispatch gates 1–4 (+ job ownership) | job_id (req.) |
Poll one media job: top-level JobStatusFrame fields (pid, pfile, is_running, data, errors, total_time); {result:false, errors:['job_not_found']} on an unknown id. |
conform_headers |
record_tipo, level 2 |
quality (req.) |
Rebuild a quality rewriting file headers (AV seek/compatibility fix). |
rotate |
record_tipo, level 2 |
quality (req.), degrees (req., e.g. -90 / 90) |
Rotate the matching quality entries. |
sync_files |
record_tipo, level 2 |
regenerate_options (object, e.g. { delete_normalized_files: bool }) |
Re-read data and regenerate the component so files_info matches disk. |
delete_version |
record_tipo, level 2 |
quality (req.), extension (optional) |
Delete one specific version: the thumb-specific path for the thumb quality, else the general file-ops delete. |
Response shape for every action: { result, msg, errors }. For get_files_info, result is the files-info array (or false on failure) and files_info_db carries the stored array beside it; for the write actions result is the boolean / merged result of the delegated engine call.
delete_quality vs delete_version: delete_quality removes a quality regardless of extension; delete_version targets one concrete file, handling the thumb specially and accepting an extension to disambiguate when a quality has more than one file (e.g. a .pdf main file alongside derivatives).
How it is registered & surfaced
tools/tool_media_versions/register.json is a column-keyed dump (string/relation/misc/… keyed by component tipo — a seeded matrix-row snapshot, not a hand-authored file); importTools() passes it through as-is (see register.json reference). Essentials it carries:
dd1326name =tool_media_versions;dd1327version =2.0.4;dd1328minimum Dédalo version =6.2.6;dd1644developer = "Dédalo team".dd1330affected_models → the media component models (component_image,component_av,component_pdf,component_3d,component_svg). The tool therefore attaches to components, not to sections or areas.dd1335properties ={ "specific_actions": { "rotate": ["component_image"], "conform_headers": ["component_av"] } }. There is noopen_asproperty, so the tool opens in the default modal.dd1372labels supply the localized UI strings (show_data,sync_data,files_info_is_unsync,delete_normalized_files,regenerate) across all project languages.dd1331show_in_inspector anddd1332show_in_component select where the button renders for the matched component.
Surfacing (in getElementTools, src/core/tools/registry.ts): because affected_models lists the media component models, the Media versions button appears on those components when they are rendered (subject to the inspector/inline flags). The presence of the tool in a component's context is visible in the component sample fixtures, e.g. src/core/components/component_image/samples/context.json, src/core/components/component_av/samples/context.json — each lists { "model": "tool_media_versions", "name": "tool_media_versions", ... } in its tools array.
Examples
Client-side tool_request (built by tool_media_versions.js, sent through dd_tools_api). Probe the real files of an image component:
const rqo = {
dd_api : 'dd_tools_api',
action : 'tool_request',
source : create_source(self, 'get_files_info'), // → tool_media_versions::get_files_info
options : {
tipo : self.main_element.tipo, // e.g. 'rsc176' (the image component tipo)
section_tipo : self.main_element.section_tipo, // e.g. 'rsc167'
section_id : self.main_element.section_id // e.g. 25
}
}
const response = await data_manager.request({ body: rqo, use_worker: true })
// response.result → [{ quality:'original', file_exist:true, file_name:'rsc167_rsc176_25.tif', file_size: 1234567, ... }, ...]
Rotate the default quality 90° (write action, long timeout, confirm-gated client-side):
const rqo = {
dd_api : 'dd_tools_api',
action : 'tool_request',
source : create_source(self, 'rotate'), // → tool_media_versions::rotate
options : {
tipo : self.main_element.tipo,
section_tipo : self.main_element.section_tipo,
section_id : self.main_element.section_id,
quality : 'default',
degrees : 90
}
}
const response = await data_manager.request({ body: rqo, retries: 1, timeout: 3600 * 1000 })
// response → { result:true, msg:'Success. Request done.', errors:[] }
Re-sync after files were removed on disk (rebuild + write correct files_info):
const rqo = {
dd_api : 'dd_tools_api',
action : 'tool_request',
source : create_source(self, 'sync_files'), // → tool_media_versions::sync_files
options : {
tipo : self.main_element.tipo,
section_tipo : self.main_element.section_tipo,
section_id : self.main_element.section_id,
regenerate_options : { delete_normalized_files: false }
}
}
const response = await data_manager.request({ body: rqo, retries: 1, timeout: 3600 * 1000 })
// response → { result:true, msg:'Success. Request done' }
Related
- tool_image_rotation — applies rotation + proportional crop to image-component files across all qualities (the standalone rotation tool);
tool_media_versionsexposes rotation inline as a per-qualityspecific_action. - tool_posterframe — extracts a posterframe/thumbnail from an AV file; complements the AV quality/header management here.
- tool_upload · tool_import_files — get the master file into a media component;
tool_media_versionsthen manages the derived qualities of that master. - Creating new tools · Server contract — the tool model,
apiActions, permission gates and lifecycle this page builds on. - Source:
tools/tool_media_versions/server/{index,media_versions}.ts,tools/tool_media_versions/register.json,tools/tool_media_versions/js/{tool_media_versions,render_tool_media_versions}.js,tools/tool_media_versions/css/tool_media_versions.less. The underlying media engines live undersrc/core/media/(see thededalo-media-protectionskill).