Skip to content
This repository was archived by the owner on Dec 11, 2024. It is now read-only.

Commit

Permalink
Add BMFF support for video & etc (#25)
Browse files Browse the repository at this point in the history
* Add BMFF support for video & etc
* Inject c2patool/version into claim_generator
* Create table of supported types for readme
* Update to c2pa 0.7
* Create output folders if missing
  • Loading branch information
gpeacock authored Jun 29, 2022
1 parent 7a30020 commit dfec044
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 73 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ rust-version = "1.61.0"

[dependencies]
anyhow = "1.0"
c2pa = { version = "0.6.1", features = ["file_io", "xmp_write"] }
c2pa = { version = "0.7", features = ["bmff", "file_io", "xmp_write"] }
env_logger = "0.9"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ c2patool is a command line tool for working with C2PA [manifests](https://c2pa.o
- Adding a C2PA manifest to [supported file formats](#supported-file-formats)

## Supported file formats

- `image/jpeg`
- `image/png`
| MIME type | extensions | read only |
| ----------------- | ----------- | --------- |
| `image/jpeg` | `jpg, jpeg` | |
| `image/png` | `png` | |
| `image/avif` | `avif` | X |
| `image/heic` | `heic` | X |
| `image/heif` | `heif` | X |
| `video/mp4` | `mp4` | |
| `application/mp4` | `mp4` | |
| `audio/mp4` | `m4a` | |
| `application/c2pa`| `c2pa` | X |
| | `mov` | |

## Installation

Expand Down
134 changes: 69 additions & 65 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
#![doc = include_str!("../README.md")]
/// Tool to display and create C2PA manifests
///
/// A file path to a jpeg must be provided
/// A file path to an asset must be provided
/// If only the path is given, this will generate a summary report of any claims in that file
/// If a claim def json file is specified, the claim will be added to any existing claims
/// If the claim def includes an asset_path, the claims in that file will be used instead
///
///
use anyhow::Result;
use anyhow::{anyhow, Result};
use c2pa::{Error, Ingredient, Manifest, ManifestStore, ManifestStoreReport};

use std::{
Expand Down Expand Up @@ -83,7 +83,7 @@ fn handle_config(
parent: Option<&Path>,
output_opt: Option<&Path>,
is_detailed: bool,
) -> Result<()> {
) -> Result<String> {
let config: Config = serde_json::from_str(json)?;

let base_path = match &config.base_path {
Expand All @@ -93,10 +93,13 @@ fn handle_config(

let signer = get_c2pa_signer(&config, &base_path)?;

let claim_generator = match config.claim_generator {
Some(claim_generator) => claim_generator,
None => format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
};
// construct a claim generator for this tool
let mut claim_generator = format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));

// if the config has a claim_generator, add it as the first entry
if let Some(generator) = config.claim_generator {
claim_generator = format!("{} {}", generator, claim_generator);
}

let mut manifest = Manifest::new(claim_generator);

Expand All @@ -110,7 +113,7 @@ fn handle_config(
}
}

// if claim_def has a parent, set the parent asset
// if the config has a parent, set the parent asset
let parent = match parent {
Some(parent) => Some(PathBuf::from(parent)),
None => config
Expand All @@ -126,7 +129,7 @@ fn handle_config(
manifest.set_parent(Ingredient::from_file(parent)?)?;
}

// add all the ingredients (claim def ingredients do not include the parent)
// add all the ingredients (config ingredients do not include the parent)
if let Some(ingredients) = config.ingredients.as_ref() {
for ingredient in ingredients {
let path = fix_relative_path(ingredient, &base_path);
Expand Down Expand Up @@ -182,6 +185,9 @@ fn handle_config(
let source_path = match output.exists() {
true => output,
false => {
let mut output_dir = PathBuf::from(output);
output_dir.pop();
std::fs::create_dir_all(&output_dir)?;
parent.as_deref().filter(|p| p.exists()).or_else(||{
eprintln!("A valid parent path or existing output file is required for claim embedding");
exit(1);
Expand Down Expand Up @@ -209,43 +215,29 @@ fn handle_config(
.or_else(|_| std::fs::copy(&temp_path, &output).and(Ok(())))
.map_err(Error::IoError)?;

// print a report on the output file
report_from_path(&output, is_detailed);

Ok(())
// generate a report on the output file
report_from_path(&output, is_detailed)
} else if is_detailed {
Err(anyhow!("detailed report not supported for preview"))
} else {
if is_detailed {
eprintln!("detailed report not supported for preview")
} else {
println!("{}", ManifestStore::from_manifest(&manifest)?);
}
Ok(())
Ok(ManifestStore::from_manifest(&manifest)?.to_string())
}
}

// prints the requested kind of report or exits with error
fn report_from_path<P: AsRef<Path>>(path: &P, is_detailed: bool) {
fn report_from_path<P: AsRef<Path>>(path: &P, is_detailed: bool) -> Result<String> {
let report = match is_detailed {
true => ManifestStoreReport::from_file(path).map(|r| r.to_string()),
false => ManifestStore::from_file(path).map(|r| r.to_string()),
};
match report {
Ok(report) => {
println!("{}", report);
}
Err(Error::JumbfNotFound) | Err(Error::LogStop) => {
eprintln!("No claim found");
exit(1)
}
Err(Error::PrereleaseError) => {
eprintln!("Prerelease claim found");
exit(1)
}
Err(e) => {
eprintln!("Error Loading {:?} {:?}", &path.as_ref(), e);
exit(1);
}
}
// Map some errors to strings we expect
report.map_err(|e| match e {
Error::JumbfNotFound => anyhow!("No claim found"),
Error::FileNotFound(name) => anyhow!("File not found: {}", name),
Error::UnsupportedType => anyhow!("Unsupported file type"),
Error::PrereleaseError => anyhow!("Prerelease claim found"),
_ => e.into(),
})
}

fn main() -> Result<()> {
Expand All @@ -261,39 +253,51 @@ fn main() -> Result<()> {
let mut base_dir = PathBuf::from(".");

if let Some(path) = args.path.clone() {
if !path.exists() {
eprintln!("File not found {:?}", path);
exit(1);
}

let extension = path.extension().and_then(|p| p.to_str()).unwrap_or("");
// path can be a jpeg source file or a json working claim description
match extension {
"jpg" | "jpeg" | "png" | "c2pa" => {
report_from_path(&path, args.detailed);
}
"json" => {
// file paths in Config are relative to the json file
base_dir = PathBuf::from(&path);
base_dir.pop();
if extension == "json" {
// file paths in Config are relative to the json file
base_dir = PathBuf::from(&path);
base_dir.pop();

config = Some(fs::read_to_string(&path)?);
}
_ => {
eprintln!("Unsupported file type {}", extension);
exit(1);
}
};
config = Some(fs::read_to_string(&path)?);
} else {
println!("{}", report_from_path(&path, args.detailed)?);
}
}

if let Some(json) = config {
handle_config(
&json,
&base_dir,
args.parent.as_deref(),
args.output.as_deref(),
args.detailed,
)?;
println!(
"{}",
handle_config(
&json,
&base_dir,
args.parent.as_deref(),
args.output.as_deref(),
args.detailed,
)?
);
}
Ok(())
}

#[cfg(test)]
pub mod tests {
#![allow(clippy::unwrap_used)]

use super::*;
const CONFIG: &str = r#"{"assertions": [{"label": "org.contentauth.test", "data": {"my_key": "whatever I want"}}]}"#;

#[test]
fn test_handle_config() {
//let config = Some(fs::read_to_string("sample/test.json").expect("read_json"));
let report = handle_config(
CONFIG,
&PathBuf::from(env!("CARGO_MANIFEST_DIR")),
Some(&PathBuf::from("tests/fixtures/earth_apollo17.jpg")),
Some(&PathBuf::from("target/tmp/unit_out.jpg")),
false,
)
.expect("handle_config");
assert!(report.contains("my_key"));
}
}
Binary file modified tests/fixtures/C.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn temp_path(name: &str) -> PathBuf {
#[test]
fn tool_not_found() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("c2patool")?;
cmd.arg("test/file/not.found");
cmd.arg("test/file/notfound.jpg");
cmd.assert()
.failure()
.stderr(predicate::str::contains("File not found"));
Expand Down

0 comments on commit dfec044

Please sign in to comment.