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

Add BMFF support for video & etc #25

Merged
merged 6 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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