use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict};
use crate::hardbinding::{self, Algorithm, DataHash, Exclusion, RustCrypto, UnicodeNfc};
fn map_err(e: crate::Error) -> PyErr {
match e.code() {
Some(code) => PyValueError::new_err(format!("{e} [{code}]")),
None => PyValueError::new_err(e.to_string()),
}
}
fn algorithm(alg: &str) -> PyResult<Algorithm> {
Algorithm::from_id(alg).map_err(map_err)
}
#[pyfunction]
fn embed(text: &str, payload: &[u8]) -> PyResult<String> {
crate::wrapper::embed(text, payload).map_err(map_err)
}
#[pyfunction]
fn encode(payload: &[u8]) -> PyResult<String> {
crate::wrapper::encode(payload).map_err(map_err)
}
#[pyfunction]
fn encode_padded(payload: &[u8]) -> PyResult<String> {
crate::wrapper::encode_padded(payload).map_err(map_err)
}
#[pyfunction]
fn target_length(manifest_len: usize) -> usize {
crate::wrapper::target_length(manifest_len)
}
#[pyfunction]
fn extract<'py>(py: Python<'py>, text: &str) -> PyResult<Option<Bound<'py, PyDict>>> {
let w = match crate::wrapper::extract(text) {
Ok(w) => w,
Err(crate::Error::NotFound) => return Ok(None),
Err(e) => return Err(map_err(e)),
};
let out = PyDict::new(py);
out.set_item("payload", PyBytes::new(py, &w.payload))?;
out.set_item("version", w.version)?;
out.set_item("start", w.start)?;
out.set_item("length", w.length)?;
Ok(Some(out))
}
#[pyfunction]
fn locate_all(text: &str) -> Vec<(usize, usize)> {
crate::wrapper::locate_all(text)
.into_iter()
.map(|w| (w.start, w.length))
.collect()
}
#[pyfunction]
fn strip(text: &str, start: usize, length: usize) -> PyResult<String> {
crate::wrapper::strip(text, start..start + length).map_err(map_err)
}
#[pyfunction]
#[pyo3(signature = (text, alg = "sha256"))]
fn compute_data_hash<'py>(py: Python<'py>, text: &str, alg: &str) -> PyResult<Bound<'py, PyDict>> {
let dh = hardbinding::compute_data_hash(text, algorithm(alg)?, &RustCrypto, &UnicodeNfc)
.map_err(map_err)?;
let out = PyDict::new(py);
out.set_item("alg", dh.alg.as_str())?;
out.set_item("hash", PyBytes::new(py, &dh.hash))?;
out.set_item(
"exclusions",
dh.exclusions
.iter()
.map(|e| (e.start, e.length))
.collect::<Vec<_>>(),
)?;
Ok(out)
}
#[pyfunction]
#[pyo3(signature = (text, hash, exclusions, alg = "sha256"))]
fn verify_data_hash(
text: &str,
hash: &[u8],
exclusions: Vec<(usize, usize)>,
alg: &str,
) -> PyResult<()> {
let dh = DataHash {
exclusions: exclusions
.into_iter()
.map(|(start, length)| Exclusion { start, length })
.collect(),
alg: algorithm(alg)?.id().to_string(),
hash: hash.to_vec(),
name: None,
};
hardbinding::verify_data_hash(text, &dh, &RustCrypto, &UnicodeNfc).map_err(map_err)
}
#[pymodule]
fn c2pa_unstructured_text(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(embed, m)?)?;
m.add_function(wrap_pyfunction!(encode, m)?)?;
m.add_function(wrap_pyfunction!(encode_padded, m)?)?;
m.add_function(wrap_pyfunction!(target_length, m)?)?;
m.add_function(wrap_pyfunction!(extract, m)?)?;
m.add_function(wrap_pyfunction!(locate_all, m)?)?;
m.add_function(wrap_pyfunction!(strip, m)?)?;
m.add_function(wrap_pyfunction!(compute_data_hash, m)?)?;
m.add_function(wrap_pyfunction!(verify_data_hash, m)?)?;
m.add("MARKER", crate::wrapper::MARKER.to_string())?;
m.add("VERSION", crate::wrapper::VERSION)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
Ok(())
}