SIP Creator packages your content files and their descriptive metadata into an E-ARK Submission Information Package (SIP), ready to hand to a digital archive.
A command-line tool that:
- builds a complete E-ARK SIP from the files in an input folder.
- takes descriptive metadata as a CSV or an XML file.
- creates a ZIP from the resulting SIP directory.
- checks an input folder before building.
A Go library that lets you:
- build E-ARK SIPs from your own Go programs and workflows.
- define profiles of your own.
Optional:
- PRONOM format identification from a pre-computed Siegfried report.
- Skip building a zip of the SIP directory.
CSIP, the common part of E-ARK, says what every package must contain. A profile adds the choices for one kind of content: the descriptive metadata standard, the representations it allows, and the values its METS declares. Three profiles for UGent Library's use are included. A program that uses the library can define profiles of its own (see Profiles of your own).
ugent/basic, for UGent Library's archival repository: digital resources the library has not necessarily catalogued, described in Simple Dublin Core;ugent/bibliographic, for UGent Library's archival repository: digital resources the library has catalogued, described in MODS from the catalogue record;meemoo/basic, for Meemoo's archive through its "Digitale Instroom", digitized or digital cultural heritage resources, as catalogued by the UGent Library.
This project requires Go 1.27 or later. There are no prebuilt binaries while the tool is experimental.
Install the command-line tool directly from the module. The binary lands in
$(go env GOPATH)/bin as sip-creator, so make sure that directory is on your
PATH:
go install github.com/ugent-library/sip-creator@latestsip-creator --version prints the version you installed. Every package the tool
builds states the same version in its METS.
Versions follow semantic versioning. While the major
version is 0, a minor version may change the command line, the library API or
the packages a profile builds. To install a given version, replace @latest
with its tag, such as @v0.1.0. CHANGELOG.md lists what each
version changed.
Or build from a clone. The examples in this README assume a clone and invoke
./bin/sip-creator. After go install, invoke sip-creator instead:
git clone https://github.com/ugent-library/sip-creator.git
cd sip-creator
go build -o bin/sip-creator .To use the library in your own Go program:
go get github.com/ugent-library/sip-creatorThe sip-creator command builds a package from an input folder you prepare.
The recording below runs check and create on the ugent/basic example
folder, then lists the package directory and the zip they leave behind.
Prepare an input folder as described under Input folder, and
set the submitting organization as described under
Configuration. To build the folder ./your-input into a
package in the sip-out directory:
./bin/sip-creator create --profile ugent/basic ./your-input sip-out
This writes the package directory sip-out/uuid-<uuid>/ and zips it
(uncompressed) to sip-out/uuid-<uuid>.zip. To try it before preparing your own
input, build one of the example folders:
./bin/sip-creator create --profile ugent/basic examples/ugent/basic sip-out.
Built from that example, the package directory looks like this (shown three levels deep):
sip-out/uuid-<uuid>/
├── documentation
│ └── README.txt
├── metadata
│ ├── descriptive
│ │ └── dc.xml
│ └── preservation
│ └── digitization-events.xml
├── METS.xml
├── representations
│ ├── access
│ │ ├── data
│ │ ├── metadata
│ │ └── METS.xml
│ └── preservation
│ ├── data
│ ├── documentation
│ ├── metadata
│ └── METS.xml
└── schemas
├── DILCISExtensionMETS.xsd
├── DILCISExtensionSIPMETS.xsd
├── mets1_12.xsd
├── simpledc.xsd
├── xlink.xsd
└── xml.xsd
METS.xmlis the package METS, the entry point of the package.metadata/holds the package's descriptive metadata (dc.xml, written fromdescription.csv) and the preservation files from the input folder'spremis/.representations/has one folder per representation folder of the input. Each has its ownMETS.xml, its content files underdata/, and its ownmetadata/anddocumentation/where the input supplied them.schemas/holds the XSDs the METS documents reference.documentation/is the input folder'sdocumentation/, copied as it is.
Further flags:
--content-categorysets the package's content category (mets/@TYPE, CSIP vocabulary), overridingSIP_CONTENT_CATEGORYand the profile default.--statussets the record status, one of the SIP3 vocabulary valuesnew,supplement,replacement,test,versionanddelete, orupdate. Without it the METS states no status, which the E-ARK SIP specification reads asnew. The UGent profiles allow onlynewandupdate, because RODA updates an AIP only onupdate(ADR-0038).--updates <identifier>names the earlier package this one updates. A status that updates an earlier package requires it. The original package's identifier becomes this package's identifier (mets/@OBJID). Build the update into a destination that does not hold the original. The tool refuses to write into an existing package directory or zip with that identifier.--no-zipskips the zip, for when the package directory itself is what you need.
The input folder has to follow the input specification.
check tests a folder without building anything.
./bin/sip-creator check --profile ugent/basic ./your-input
check reports whether the input folder is valid, and lists every problem it
found.
1 problem in ./your-input
premis/broken.xml: not well-formed XML: XML syntax error on line 2: unexpected EOF
Input folder: ./your-input
Profile: ugent/basic
Descriptive metadata: description.csv
Representations: 1 (1 with its own description)
Essence files: 1
Documentation files: 2
PREMIS files: 2
Format report: not supplied (files carry no format information)
FAILED: fix the problems listed at the top and run check again.
The exit status tells a script what happened:
| status | meaning |
|---|---|
| 0 | the folder meets the input specification and the profile's rules |
| 1 | the folder has problems; the report lists them |
| 2 | check could not check the folder: a wrong path, a file instead of a folder, an unknown profile, or a missing argument |
Configuration is read from the environment. Start from .env.example.
All environment variables are documented in CONFIG.md.
One folder is one package. Your content files live in a representation folder
under representations/, one folder per version of the content. The smallest
valid input is a description.csv plus one representation:
your-input/
├── description.csv
└── representations/
└── preservation/
├── scan-001.tif
└── scan-002.tif
When the content comes in several versions, such as the scans as delivered and an access copy, each version gets its own representation folder.
Every name the tool reads in an input folder:
| Name | Required | What it holds | Rules |
|---|---|---|---|
description.csv |
yes, or the profile's document | descriptive metadata as key,value rows |
§3 |
dc.xml, mods.xml |
instead of description.csv, where the profile accepts one |
a finished descriptive document | §3 |
representations/<name>/ |
under meemoo/basic, exactly one |
one folder per version of the content | §2 |
representations.csv |
no | a display label per representation folder | §2 |
documentation/ |
no | context material | §4 |
premis/ |
no | received preservation XML, copied as it is | §5 |
siegfried.json |
no | a format characterization report | below |
Content files live only in representation folders: anything else at the top
level is an error. meemoo/basic takes exactly one representation, under any
name. The UGent profiles take preservation, archival and access. A
representation folder can hold its own documentation/ and premis/, and under
the UGent profiles its own description. Folder names may use letters, digits and
._-.
examples/ has a complete input folder for each profile.
A two-column file with a key,value header row. Repeat a key for more values,
and add a language in square brackets where it matters (title[nl]). An unknown
key is an error. Which keys exist and which are required depends on the profile.
The smallest valid file per profile:
ugent/basic:
key,value
identifier,example-0001
title,Example photographugent/bibliographic:
key,value
identifier,example-0001
title[en],Example bookmeemoo/basic:
key,value
identifier,example-0001
title[nl],Voorbeeldfoto
description[nl],Een voorbeeldpakket met één afbeelding.
created,2026-01-15Instead of a description.csv file, you can also drop in an XML document per
profile:
ugent/basic:dc.xmlugent/bibliographic:mods.xml
This project only checks that it is XML with the root element the profile expects, and copies it as it is.
Optional. Gives each representation folder a display label, written into the
package's METS. An empty label means the folder name, and a label may hold any
text:
folder,label
preservation,Scanned master copies (TIFF)
access,Access copy (JPEG)When the file is present, every folder needs a row and every row must match a folder, so no content can drop out of the package unnoticed. The rows set the order of the representations in the package.
The tool adds format info (PRONOM identifiers) from a Siegfried report in
siegfried.json. Generate the report from the input folder, before building a
SIP.
cd ./your-input && report="$(sf -hash md5 -json .)" && printf '%s\n' "$report" > siegfried.jsonWithout the report, the package has no format info, and the tool computes every
checksum itself. With it, the build stops when the report is malformed, made
without -hash md5, or misses a content file. The report's MD5 is the checksum
the package declares for each file it covers. The tool does not re-verify the
checksum in the supplied siegfried.json file.
SIP Creator is also a Go library. Your program gives it a profile, the descriptive metadata and the paths of the content files. The library checks them against the profile's rules and writes the package directory. Use it when your application already manages the metadata and essence files. The command-line tool is built on the same library.
The API and runnable examples are on pkg.go.dev.
Resolve a profile, attach the submitter, and hand Build your descriptive terms
and content files:
import (
"log/slog"
"github.com/ugent-library/sip-creator/build"
"github.com/ugent-library/sip-creator/profiles"
"github.com/ugent-library/sip-creator/profiles/ugent"
)
def, ok := profiles.Get("ugent/basic")
if !ok {
// ...
}
// The second argument is the Meemoo OR-id. Only meemoo/basic requires it.
def, err := def.WithSubmitter("Example Organization", "")
if err != nil {
// ...
}
builder, err := build.New(&build.Config{
Profile: def,
Destination: "./out",
Logger: slog.Default(),
})
if err != nil {
// ...
}
pkg, err := builder.Build(&build.SourcePackage{
// The description's type belongs to the profile (see Profiles).
Description: ugent.Terms{
{Key: "identifier", Value: "example-0001"},
{Key: "title", Value: "Example photograph"},
{Key: "description", Value: "An example package with one image."},
{Key: "date", Value: "2026-01-15"},
},
Representations: []build.SourceRepresentation{{
Name: "preservation",
Files: []build.SourceFile{
{Source: "/data/scans/page-001.tif", Path: "page-001.tif"},
},
}},
})Buildchecks the source package against the profile's rules, writes the package directory underDestination, and returns the built package.- Zipping is a separate step, in the
archivepackage. - One builder serves one profile. Reuse it for every package you build to that
profile, and put what differs per package in the
SourcePackage.
Three of these have a runnable example in build/example_test.go, named in brackets:
- Documentation and PREMIS.
DocumentationandPremis, on theSourcePackageor on a representation, are the library's form of the input folder'sdocumentation/andpremis/. - A bibliographic record.
ugent/bibliographictakes augent.Recordinstead of terms: identifiers, titles, contributors and physical copies (ExampleBuilder_Build_mods). - A finished document. Where the profile accepts one, a
build.EncodedDescriptionnames adc.xmlormods.xmlto copy as it is (ExampleEncodedDescription). - Labels.
SourceRepresentation.Labelis the display name in the representation's METS, and defaults toName. Under the UGent profiles the type follows from the name and cannot be set. - Updates.
PackageIdentifier,RecordStatusandContentCategoryon theSourcePackagemake it update an earlier package. Under the UGent profiles an update may hold no representations (ExampleBuilder_Build_update). - Format information. Set
Characterizationto a Siegfried report decoded withcharacterization.DecodeSiegfried.
A program that embeds the library can define a profile of its own: a description
type, a metadata model that writes and types the document, and a
build.Definition. Hand the definition to build.New. It needs no entry in the
registry. Example_ownProfile builds a package this way, and the godoc of
build.MetadataModel and build.Definition lists what each part must do. The
library is written for UGent's use first, and makes no promises to other
institutions
(ADR-0033).
CONTRIBUTING.md has the tests, validation scripts and conventions for working on SIP Creator itself. docs/README.md says which document answers which question: the design, the decisions and the backlog.
