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

Add --info option #65

Merged
merged 6 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ readme = "README.md"
keywords = ["c2pa", "xmp", "metadata"]
edition = "2018"
rust-version = "1.61.0"
homepage = "https://contentauthenticity.org"
scouten-adobe marked this conversation as resolved.
Show resolved Hide resolved
repository = "https://github.com/contentauth/c2patool"

[dependencies]
anyhow = "1.0"
c2pa = { version = "0.12.0",features = ["bmff", "fetch_remote_manifests", "file_io", "xmp_write"]}
c2pa = { version = "0.13.0",features = ["bmff", "fetch_remote_manifests", "file_io", "xmp_write"]}
env_logger = "0.9"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -29,3 +31,8 @@ tempfile = "3.3"
[dev-dependencies]
assert_cmd = "2.0"
predicates = "2.1"

[profile.release]
strip = true # Automatically strip symbols from the binary.
# opt-level = "z" # Optimize for size.
gpeacock marked this conversation as resolved.
Show resolved Hide resolved
lto = "thin" # Link time optimization.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ test-local:
# Run this before pushing a PR to pre-validate
test: check-format clippy test-local

fmt:
cargo +nightly fmt

# Creates a folder wtih c2patool bin, samples and readme
c2patool-package:
rm -rf target/c2patool*
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ c2patool -d sample/C.jpg

The tool displays the detailed report to standard output (stdout).

### Detailed manifest report

The `--info` option will print a high level report about any c2pa content in the file.
For a cloud manifest it will display the url to the manifest.
For embedded manifests, the size of the manifest store and number of manifests will be displayed. It will also report if the manifest validated or any errors encountered in validation.


```shell
c2patool sample/C.jpg --info
```

The tool displays the info report to standard output (stdout).

### Adding a manifest to an asset file

To add C2PA manifest data to a file, use the `--manifest` / `-m` option with a manifest JSON file as the option argument and the path to the asset file to be signed. Specify the output file as the argument to the `--output` / `-o` option. For example:
Expand Down
76 changes: 76 additions & 0 deletions src/info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2022 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.
// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use std::path::Path;

use anyhow::Result;
use c2pa::{IngredientOptions, ManifestStore};

/// display additional C2PA information about the asset (not json formatted)
pub fn info(path: &Path) -> Result<()> {
struct Options {}
impl IngredientOptions for Options {
fn thumbnail(&self, _path: &Path) -> Option<(String, Vec<u8>)> {
None
}
}
let ingredient = c2pa::Ingredient::from_file_with_options(path, &Options {})?;
println!("Information for {}", ingredient.title());
//println!("instanceID = {}", ingredient.instance_id());
if let Some(provenance) = ingredient.provenance() {
if !provenance.starts_with("self#jumbf=") {
println!("Cloud URL = {}", provenance);
} else {
println!("XMP provenance URI = {}", provenance);
gpeacock marked this conversation as resolved.
Show resolved Hide resolved
}
}
if let Some(manifest_data) = ingredient.manifest_data() {
let file_size = std::fs::metadata(path).unwrap().len();
println!(
"C2PA manifest store size = {} ({:.2}% of {})",
manifest_data.len(),
file_size as f64 / manifest_data.len() as f64,
gpeacock marked this conversation as resolved.
Show resolved Hide resolved
file_size
);
if let Some(validation_status) = ingredient.validation_status() {
println!("Validation issues:");
for status in validation_status {
println!(" {}", status.code());
}
} else {
println!("Validated");
}
let manifest_store = ManifestStore::from_bytes("c2pa", manifest_data.to_vec(), false)?;
match manifest_store.manifests().len() {
0 => println!("No embedded manifests"),
1 => println!("One manifest"),
n => println!("{} Manifests", n),
gpeacock marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
println!("No C2PA Manifests");
}
Ok(())
}

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

use super::*;

#[test]
fn test_manifest_config() {
const SOURCE_PATH: &str = "tests/fixtures/C.jpg";

info(&std::path::PathBuf::from(SOURCE_PATH)).expect("info");
}
}
48 changes: 29 additions & 19 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
/// If only the path is given, this will generate a summary report of any claims in that file
/// If a manifest definition json file is specified, the claim will be added to any existing claims
///
use std::{
path::{Path, PathBuf},
process::exit,
};
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Context, Result};
use c2pa::{Error, ManifestStore, ManifestStoreReport};
use structopt::{clap::AppSettings, StructOpt};

mod info;
use info::info;
pub mod manifest_config;
use manifest_config::ManifestConfig;
mod signer;
Expand Down Expand Up @@ -89,6 +88,9 @@ struct CliArgs {
help = "Generate a sidecar (.c2pa) manifest"
)]
sidecar: bool,

#[structopt(long = "info", help = "Show manifest size, XMP url and other stats")]
info: bool,
}

// prints the requested kind of report or exits with error
Expand Down Expand Up @@ -116,10 +118,14 @@ fn main() -> Result<()> {
}
env_logger::init();

if args.info && args.path.exists() {
return info(&args.path);
}
gpeacock marked this conversation as resolved.
Show resolved Hide resolved

// get manifest config from either the -manifest option or the -config option
let config = if let Some(json) = args.config {
if args.manifest.is_some() {
eprintln!("Do not use config and manifest options together");
exit(1);
bail!("Do not use config and manifest options together");
}
Some(ManifestConfig::from_json(&json)?)
} else if let Some(config_path) = args.manifest {
Expand All @@ -128,6 +134,7 @@ fn main() -> Result<()> {
None
};

// if we have a manifest config, process it
if let Some(mut manifest_config) = config {
if let Some(parent_path) = args.parent {
manifest_config.parent = Some(parent_path)
Expand All @@ -146,18 +153,18 @@ fn main() -> Result<()> {
}

if let Some(output) = args.output {
if output.extension() != args.path.extension() {
bail!("output type must match source type");
}
if output.exists() && !args.force {
eprintln!("Output already exists, use -f/force to force write");
exit(1);
bail!("Output already exists, use -f/force to force write");
}

if output.file_name().is_none() {
eprintln!("Missing filename on output");
exit(1);
bail!("Missing filename on output");
}
if output.extension().is_none() {
eprintln!("Missing extension output");
exit(1);
bail!("Missing extension output");
}

// create any needed folders for the output path (embed should do this)
Expand All @@ -169,16 +176,12 @@ fn main() -> Result<()> {

manifest
.embed(&args.path, &output, signer.as_ref())
.unwrap_or_else(|e| {
eprintln!("error embedding manifest: {:?}", e);
exit(1);
});
.context("embedding manifest")?;

// generate a report on the output file
println!("{}", report_from_path(&output, args.detailed)?);
} else if args.detailed {
eprintln!("detailed report not supported for preview");
exit(1);
bail!("detailed report not supported for preview");
} else {
// normally the output file provides the title, format and other manifest fields
// since there is no output file, gather some information from the source
Expand All @@ -200,6 +203,13 @@ fn main() -> Result<()> {
}
println!("{}", ManifestStore::from_manifest(&manifest)?)
}
} else if args.output.is_some()
|| args.parent.is_some()
|| args.sidecar
|| args.remote.is_some()
|| args.force
{
bail!("manifest definition required with these options or flags")
} else {
// let extension = path.extension().and_then(|p| p.to_str()).unwrap_or("");
// just report from file if no manifest configuration given
Expand Down
11 changes: 5 additions & 6 deletions src/manifest_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{
path::{Path, PathBuf},
};

use anyhow::{anyhow, Result};
use anyhow::{bail, Context, Result};
use c2pa::{Ingredient, Manifest, ManifestAssertion};
use serde::Deserialize;
use serde_json::Value;
Expand Down Expand Up @@ -64,8 +64,7 @@ impl ManifestConfig {
}

pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json)
.map_err(|e| anyhow!("Error reading manifest configuration {:?}", e))
serde_json::from_str(json).context("reading manifest configuration")
}

pub fn from_file(path: &Path) -> Result<Self> {
Expand Down Expand Up @@ -130,7 +129,7 @@ impl ManifestConfig {

if let Some(parent) = parent.as_ref() {
if !parent.exists() {
return Err(anyhow!("Parent file not found {:#?}", parent));
bail!("parent file not found {:#?}", parent);
}
manifest.set_parent(Ingredient::from_file(parent)?)?;
}
Expand All @@ -140,10 +139,10 @@ impl ManifestConfig {
for ingredient in ingredients {
let path = self.fix_relative_path(ingredient);
if !path.exists() {
return Err(anyhow!("Ingredient file not found {:#?}", path));
bail!("ingredient file not found {:#?}", path);
}
let ingredient = Ingredient::from_file(&path)
.map_err(|e| anyhow!("error loading ingredient {:?} {:?}", &path, e))?;
.with_context(|| format!("loading ingredient {:?}", &path))?;
manifest.add_ingredient(ingredient);
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn tool_not_found() -> Result<(), Box<dyn std::error::Error>> {
cmd.arg("test/file/notfound.jpg");
cmd.assert()
.failure()
.stderr(predicate::str::contains("File not found"));
.stderr(predicate::str::contains("No such file "));
Ok(())
}

Expand Down