A card was detected only with M4ROOT at its root, and asking for two or more stores returned one too few. A mount point now counts when its root holds a DCIM or PRIVATE/M4ROOT directory, and the count is exact. The check and the counting are split from the partition listing so tests can hand them temporary directories. Model: opus-5-5
141 lines
4.4 KiB
Markdown
141 lines
4.4 KiB
Markdown
# dcf
|
|
|
|
## Description
|
|
|
|
`dcf` is a WTFPL-licensed Go library by [@sneak](https://sneak.berlin) that
|
|
reads DCF memory cards from digital cameras.
|
|
|
|
DCF is the "Design rule for Camera File system". It is also JEITA specification
|
|
number CP-3461. JEITA is the Japan Electronics and Information Technology
|
|
Industries Association. Wikipedia describes it at
|
|
[Design rule for Camera File system](https://en.wikipedia.org/wiki/Design_rule_for_Camera_File_system).
|
|
|
|
The DCF specification is why your digital camera puts images and videos in
|
|
`DCIM` and `PRIVATE/M4ROOT` directories on the memory card.
|
|
|
|
`dcf` is incomplete and under development. It does not work yet.
|
|
|
|
## Getting Started
|
|
|
|
Add the library to your Go module:
|
|
|
|
```sh
|
|
go get sneak.berlin/go/dcf
|
|
```
|
|
|
|
Then list the DCF stores on the mounted filesystems:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"sneak.berlin/go/dcf/pkg/dcf"
|
|
)
|
|
|
|
func main() {
|
|
stores, err := dcf.GetDCFStores(0)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
for _, store := range *stores {
|
|
fmt.Println(store.String())
|
|
}
|
|
}
|
|
```
|
|
|
|
To work on the library itself, you need docker and git:
|
|
|
|
```sh
|
|
git clone git@git.eeqj.de:sneak/dcf.git
|
|
cd dcf
|
|
script/setup
|
|
make check
|
|
```
|
|
|
|
## Entrypoints
|
|
|
|
This repo adheres to the
|
|
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
|
standard. Each entrypoint is a POSIX sh script in `script/`. Use these instead
|
|
of running the underlying tools directly.
|
|
|
|
- `script/bootstrap` (`make bootstrap`) installs the tools the host needs. It
|
|
does not install docker or the linter. It installs:
|
|
- `make` and git
|
|
- Go into `~/.local/go`, at the version in the `Dockerfile`, unless the `go`
|
|
on the `PATH` is already that version
|
|
- the Go modules
|
|
- node and yarn
|
|
- `prettier`
|
|
- `script/setup` (`make setup`) runs `script/bootstrap` and then installs the
|
|
git pre-commit hook.
|
|
- `script/test` (`make test`) runs the tests by building the `test` stage of the
|
|
`Dockerfile`.
|
|
- `script/lint` (`make lint`) checks the Go code by building the `lint` stage of
|
|
the `Dockerfile`.
|
|
- `script/fmt` (`make fmt`) rewrites the Go code with `go fmt` and the Markdown
|
|
files with `prettier`.
|
|
- `script/fmt-check` (`make fmt-check`) checks the same formatting and changes
|
|
nothing.
|
|
- `script/check` (`make check`) runs the tests, then the linter, then the
|
|
formatting check.
|
|
- `script/build` (`make build`) compiles every package. It needs the Go that
|
|
`script/bootstrap` installs.
|
|
- `script/docker` (`make docker`) builds the development image, tagged `dcf`.
|
|
- `script/cibuild` runs the whole CI build. The Gitea workflow runs it on every
|
|
push.
|
|
- `script/precommit` is what the git pre-commit hook runs. It runs
|
|
`script/check`.
|
|
- `script/install-precommit` (`make hooks`) installs the git pre-commit hook.
|
|
- `script/projectname` prints the project name for the other scripts.
|
|
|
|
The development image cannot be built unless the `lint` and `test` stages pass.
|
|
Run `docker run --rm -it dcf` for a shell inside it, with Go and `make`.
|
|
|
|
## Rationale
|
|
|
|
I wanted to copy images off my memory cards for processing. I also like
|
|
overengineering and reusable code.
|
|
|
|
## Design
|
|
|
|
All the code is in the `pkg/dcf` package. The repo has no commands.
|
|
|
|
- `dcf.go` holds the public API. `GetDCFStores` finds the DCF stores and lists
|
|
the files on them.
|
|
- `helpers.go` finds the mount points that hold a DCF store.
|
|
|
|
The main types are:
|
|
|
|
- `Store` is one DCF store on one mounted filesystem.
|
|
- `Object` is one file in a store.
|
|
- `Image` and `Video` embed `Object`. The file extension decides which one a
|
|
file becomes.
|
|
|
|
A mounted filesystem is a DCF store when its root holds a `DCIM` or
|
|
`PRIVATE/M4ROOT` directory. `dcf` never mounts a filesystem.
|
|
|
|
## TODO
|
|
|
|
- Handle DCF file groups, where multiple objects share the same index number.
|
|
`dcf` does not handle these yet. If you need this, send me a link to a zip or
|
|
tar of a memory card that uses them, and I'll see what I can do.
|
|
- Parse the DCF directory and file names, rather than listing every media file
|
|
on the filesystem.
|
|
- Read stores through `fs.FS`, with a `context.Context` to cancel a walk.
|
|
- Return a plain slice from `GetDCFStores`, not a pointer to one.
|
|
- Test the walk of a store. It only runs against real mounted media today.
|
|
- Copy files off a card, and verify each copy against its SHA256 hash.
|
|
|
|
## License
|
|
|
|
WTFPL. See [LICENSE](LICENSE).
|
|
|
|
## Author
|
|
|
|
[@sneak](https://sneak.berlin) <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)>
|