Media roots and resolution¶
Media moves: a production is copied to another workstation, a volume is mounted at a different path, or a folder is reorganized. PostProject keeps asset and representation identity stable while storage locations change, and resolution answers where the content can be reached now.
Register a logical media root¶
A media root is a portable, production-wide name such as rushes. It does not
store a machine path. Each machine supplies the local directory for a root name
at resolution time, so the same production resolves correctly on differently
mounted systems.
static pp_error_code_t add_rushes_root(pp_production_t *production,
pp_error_t **error) {
pp_transaction_t *transaction = NULL;
pp_uuid_t root_id;
pp_error_code_t status =
pp_production_begin_transaction(production, &transaction, error);
if (status == PP_OK) {
status = pp_transaction_add_media_root(transaction, "rushes",
"Camera originals", 0, &root_id,
error);
}
if (status == PP_OK) {
status = pp_transaction_commit(transaction, error);
}
pp_transaction_release(transaction);
return status;
}
void add_rushes_root(postproject::Production &production) {
auto transaction = production.beginTransaction();
transaction.addMediaRoot("rushes", "Camera originals");
transaction.commit();
}
def add_rushes_root(production: Production) -> None:
with production.transaction() as transaction:
transaction.add_media_root("rushes", "Camera originals")
fn add_rushes_root(production: &mut SqliteProduction) -> Result<()> {
let root = MediaRoot::new(
MediaRootId::new(),
"rushes",
Some("Camera originals".to_owned()),
None,
0,
true,
)?;
let mut transaction = production.begin_transaction()?;
transaction.add_media_root(root)?;
transaction.commit()
}
postproject root add production.pproj rushes --label "Camera originals"
Roots have an optional label and a priority; lower priorities are searched first. A root can later be disabled, re-enabled, or removed.
Resolve an asset¶
Resolution checks the known locators of every resource and, where content is missing, searches the mapped roots for credible candidates. It reports one aggregate availability per representation (online, partial, offline, ambiguous, or error), the state of each resource, scored candidates with their evidence, and availability issues such as missing sequence frames.
The example maps the rushes root to this machine’s directory after the
imported file has been moved there:
static pp_error_code_t resolve_asset(const pp_production_t *production,
const pp_uuid_t *asset_id,
const char *rushes_directory,
pp_resolution_set_t **out_resolutions,
pp_error_t **error) {
const pp_media_root_mapping_t mappings[] = {{"rushes", rushes_directory}};
pp_resolution_set_t *resolutions = NULL;
pp_error_code_t status = pp_production_resolve_asset(
production, asset_id, mappings, 1, &resolutions, error);
const uint64_t count =
status == PP_OK ? pp_resolution_set_representation_count(resolutions) : 0;
for (uint64_t r = 0; status == PP_OK && r < count; ++r) {
pp_uuid_t representation_id;
pp_representation_availability_t availability;
uint64_t resource_count = 0;
uint64_t issue_count = 0;
status = pp_resolution_set_get_representation(
resolutions, r, &representation_id, &availability, &resource_count,
&issue_count, error);
if (status == PP_OK) {
printf("availability: %u\n", availability);
}
for (uint64_t s = 0; status == PP_OK && s < resource_count; ++s) {
pp_uuid_t resource_id;
pp_resource_resolution_state_t state;
uint64_t candidate_count = 0;
uint64_t evidence_count = 0;
status = pp_resolution_set_get_resource(resolutions, r, s, &resource_id,
&state, &candidate_count,
&evidence_count, error);
for (uint64_t c = 0; status == PP_OK && c < candidate_count; ++c) {
const char *uri = NULL;
uint16_t confidence = 0;
status = pp_resolution_set_get_candidate(resolutions, r, s, c, &uri,
&confidence, &evidence_count,
error);
if (status == PP_OK) {
printf("candidate: %s (%u/10000)\n", uri, confidence);
}
}
}
}
if (status == PP_OK) {
*out_resolutions = resolutions;
} else {
pp_resolution_set_release(resolutions);
}
return status;
}
std::vector<postproject::RepresentationResolution>
resolve_asset(const postproject::Production &production,
const postproject::Uuid &asset_id,
const std::string &rushes_directory) {
const auto resolutions =
production.resolveAsset(asset_id, {{"rushes", rushes_directory}});
for (const auto &representation : resolutions) {
std::cout << "availability: "
<< static_cast<std::uint32_t>(representation.availability)
<< '\n';
for (const auto &resource : representation.resources) {
for (const auto &candidate : resource.candidates) {
std::cout << "candidate: " << candidate.uri << " ("
<< candidate.confidence_basis_points << "/10000)\n";
}
}
}
return resolutions;
}
def resolve_asset(
production: Production, asset_id: AssetId, rushes_directory: Path
) -> tuple[RepresentationResolution, ...]:
resolutions = production.resolve(asset_id, {"rushes": rushes_directory})
for representation in resolutions:
print(f"availability: {representation.availability.name}")
for resource in representation.resources:
for candidate in resource.candidates:
print(
f"candidate: {candidate.uri} "
f"({candidate.confidence_basis_points}/10000)"
)
return resolutions
fn resolve_asset(
production: &SqliteProduction,
asset_id: AssetId,
rushes_directory: &Path,
) -> Result<Vec<RepresentationResolution>> {
let mappings = [MediaRootMapping::new("rushes", rushes_directory)?];
let roots = production.production().media_roots();
let resolver = MediaResolver::default();
let mut resolutions = Vec::new();
for representation in production.representations(asset_id)? {
let mut resources = Vec::new();
for resource in production.resources(representation.id())? {
let locators = production.locators(resource.id())?;
resources.push(resolver.resolve_resource(
&resource,
representation.content_structure(),
&locators,
roots,
&mappings,
)?);
}
let resolution = RepresentationResolution::aggregate(
representation.id(),
representation.content_structure(),
resources,
)?;
println!("availability: {:?}", resolution.availability());
for resource in resolution.resources() {
for candidate in resource.candidates() {
println!("candidate: {}", candidate.uri());
}
}
resolutions.push(resolution);
}
Ok(resolutions)
}
postproject --json media resolve production.pproj "$ASSET_ID" \
--root-map rushes="$PWD/moved" > resolution.json
jq -r '.resolutions[] | .availability' resolution.json
jq -r '.resolutions[].resources[].candidates[].uri' resolution.json
Resolution is read-only. It never changes locators, even when exactly one candidate matches exactly. An unmapped root is reported as unmapped evidence rather than an error, and an unreadable mapping as unavailable; other roots are still searched.
Confirm a candidate¶
Only an explicit confirmation makes a candidate durable. The integration decides which candidate to confirm: a person picks one of several plausible candidates, or a policy accepts a single exact match. Confirmation adds a locator for the resource in a transaction; the stored identity is unchanged.
static pp_error_code_t
confirm_unique_candidates(pp_production_t *production,
const pp_resolution_set_t *resolutions,
pp_error_t **error) {
pp_transaction_t *transaction = NULL;
pp_error_code_t status =
pp_production_begin_transaction(production, &transaction, error);
const uint64_t count = pp_resolution_set_representation_count(resolutions);
for (uint64_t r = 0; status == PP_OK && r < count; ++r) {
pp_uuid_t representation_id;
pp_representation_availability_t availability;
uint64_t resource_count = 0;
uint64_t issue_count = 0;
status = pp_resolution_set_get_representation(
resolutions, r, &representation_id, &availability, &resource_count,
&issue_count, error);
for (uint64_t s = 0; status == PP_OK && s < resource_count; ++s) {
pp_uuid_t resource_id;
pp_resource_resolution_state_t state;
uint64_t candidate_count = 0;
uint64_t evidence_count = 0;
const char *uri = NULL;
uint16_t confidence = 0;
status = pp_resolution_set_get_resource(resolutions, r, s, &resource_id,
&state, &candidate_count,
&evidence_count, error);
/* Several candidates need a person to choose; never pick one here. */
if (status == PP_OK && candidate_count == 1) {
status = pp_resolution_set_get_candidate(resolutions, r, s, 0, &uri,
&confidence, &evidence_count,
error);
}
if (status == PP_OK && uri != NULL) {
status = pp_transaction_confirm_locator(transaction, &resource_id, uri,
error);
}
}
}
if (status == PP_OK) {
status = pp_transaction_commit(transaction, error);
}
pp_transaction_release(transaction);
return status;
}
void confirm_unique_candidates(
postproject::Production &production,
const std::vector<postproject::RepresentationResolution> &resolutions) {
auto transaction = production.beginTransaction();
for (const auto &representation : resolutions) {
for (const auto &resource : representation.resources) {
// Several candidates need a person to choose; never pick one here.
if (resource.candidates.size() == 1) {
transaction.confirmLocator(resource.resource_id,
resource.candidates.front().uri);
}
}
}
transaction.commit();
}
def confirm_unique_candidates(
production: Production, resolutions: tuple[RepresentationResolution, ...]
) -> None:
with production.transaction() as transaction:
for representation in resolutions:
for resource in representation.resources:
# Several candidates need a person to choose; never pick one here.
if len(resource.candidates) == 1:
transaction.confirm_locator(
resource.resource_id, resource.candidates[0].uri
)
fn confirm_unique_candidates(
production: &mut SqliteProduction,
resolutions: &[RepresentationResolution],
) -> Result<()> {
let mut transaction = production.begin_transaction()?;
for resolution in resolutions {
for resource in resolution.resources() {
// Several candidates need a person to choose; never pick one here.
if let [candidate] = resource.candidates() {
let locator = prepare_confirmed_locator(resource.resource_id(), candidate.uri())?;
transaction.add_locator(&locator)?;
}
}
}
transaction.commit()
}
# Confirm only a candidate that a person or policy selected; never pick one of
# several plausible candidates automatically.
CANDIDATE=$(jq -r '.resolutions[0].resources[0].candidates[0].uri' resolution.json)
postproject media resolve production.pproj "$ASSET_ID" \
--root-map rushes="$PWD/moved" --confirm "$CANDIDATE"
Never confirm one of several candidates automatically. Present them, with their confidence and evidence, and let the user choose.