initial, not sure if it works

This commit is contained in:
Jeffrey Paul 2024-05-12 09:37:34 -07:00
commit e4ca1a2ec2
9 changed files with 303 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dcfinfo

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
default: test
test:
go test -v ./...
run: build
./dcfinfo
build:
cd cmd/dcfinfo && go build -o ../../dcfinfo

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# dcf
Golang reader implementation of the so-called "Design rule for Camera File
system" or DCF, aka JEITA (Japan Electronics and Information Technology
Industries Association) specification number CP-3461.
[wikipedia.org/wiki/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.
# why
I wanted to copy images off my memory cards for processing and like
overengineering and reusable code.
# author
sneak <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)>
# license
WTFPL

169
dcf.go Normal file
View File

@ -0,0 +1,169 @@
package dcf
import (
"os"
"path/filepath"
"strings"
"github.com/dustin/go-humanize"
"github.com/shirou/gopsutil/disk"
)
var imageExtensions = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".raw", ".arw"}
var videoExtensions = []string{".mp4", ".mov", ".avi", ".mkv", ".wmv"}
// DCFStore represents a DCF store, which is a directory sturcture that contains
// images and videos in the root of a mounted filesystem.
type DCFStore struct {
RootDirectory string
Images []Image
Videos []Video
}
// DCFObject represents a file in a DCF store. It is embedded in Image and Video types.
type DCFObject {
DCFStoreRoot string
Path string
Size int64
Extension string
}
// Image represents an image file on a DCF store.
type Image struct {
DCFObject
}
// Video represents a video file on a DCF store.
type Video struct {
DCFObject
}
func (d *DCFObject) FullFilePath() {
return filepath.Join(d.DCFStoreRoot, d.Path)
}
// VideosCount returns the number of videos in the DCF store.
func (d *DCFStore) VideosCount() int {
return len(d.Videos)
}
// ImagesCount returns the number of images in the DCF store.
func (d *DCFStore) ImagesCount() int {
return len(d.Images)
}
func (d *DCFStore) TotalImageSize() int64 {
totalSize := int64(0)
for _, image := range d.Images {
totalSize += image.Size
}
return totalSize
}
func (d *DCFStore) TotalVideoSize() int64 {
totalSize := int64(0)
for _, video := range d.Videos {
totalSize += video.Size
}
return totalSize
}
func (d *DCFStore) TotalSize() int64 {
return d.TotalImageSize() + d.TotalVideoSize()
}
func (d *DCFStore) String() string {
return fmt.Sprintf("DCFStore{RootDirectory: %s, Images: %d, Videos: %d, TotalSize: %s}", d.RootDirectory, d.ImagesCount(), d.VideosCount(), humanize.Bytes(uint64(d.TotalSize()))
}
func (v *Video) String() string {
return fmt.Sprintf("Video{Path: %s, Size: %d, Extension: %s}", v.Path, v.Size, v.Extension)
}
func (i *Image) String() string {
return fmt.Sprintf("Image{Path: %s, Size: %d, Extension: %s}", i.Path, i.Size, i.Extension)
}
// Hash() returns the SHA256 hash of the file as a hex string.
func (d *DCFObject) Hash() (string, error) {
return pathToSHA256(i.FullFilePath())
}
function pathToSHA256 (path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
// GetDCFStores returns a list of DCF stores found on the system in the root of any mounted filesystems.
// It does not mount filesystems or search outside of filesystem root directories. It does, however,
// walk the filesystems to find images and videos and populate the returned DCFStores.
func GetDCFStores() (*[]DCFStore, error) {
dcfStorePaths, err := findDCFMountPoints()
if err != nil {
return nil, err
}
dcfStores := []DCFStore{}
for _, dcfStorePath := range dcfStorePaths {
dcfStore := DCFStore{
RootDirectory: dcfStorePath,
Images: []Image{},
Videos: []Video{},
}
err := filepath.Walk(dcfStorePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && isImageFile(path) {
dcfStore.Images = append(dcfStore.Images, Image{
Path: strings.TrimPrefix(path, dcfStorePath+"/"),
Size: info.Size(),
Extension: filepath.Ext(path),
DCFStoreRoot: dcfStorePath,
})
}
if !info.IsDir() && isVideoFile(path) {
dcfStore.Videos = append(dcfStore.Videos, Video{
Path: strings.TrimPrefix(path, dcfStorePath+"/")
Size: info.Size(),
Extension: filepath.Ext(path),
DCFStoreRoot: dcfStorePath,
})
}
return nil
})
if err != nil {
return nil, err
}
dcfStores = append(dcfStores, dcfStore)
}
return &dcfStores, nil
}
func isImageFile(path string) bool {
ext := strings.ToLower(filepath.Ext(path))
for _, imageExt := range imageExtensions {
if ext == imageExt {
return true
}
}
return false
}
func isVideoFile(path string) bool {
ext := strings.ToLower(filepath.Ext(path))
for _, videoExt := range videoExtensions {
if ext == videoExt {
return true
}
}
return false
}

10
dcf_test.go Normal file
View File

@ -0,0 +1,10 @@
package dcf
import (
"testing"
)
func TestCompile(t *testing.T) {
// This is a placeholder test to ensure that the module compiles successfully.
// You can add more meaningful tests here once you start implementing your code.
}

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module git.eeqj.de/sneak/dcf
go 1.22.1
require github.com/shirou/gopsutil v3.21.11+incompatible
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.20.0 // indirect
)

9
go.sum Normal file
View File

@ -0,0 +1,9 @@
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

55
helpers.go Normal file
View File

@ -0,0 +1,55 @@
package dcf
import (
"os"
"path/filepath"
"github.com/shirou/gopsutil/disk"
)
func findAllMountPoints() ([]string, error) {
mountpoints := []string{}
partitions, err := disk.Partitions(false) // physical devices only, so false
if err != nil {
return nil, err
}
for _, partition := range partitions {
if !contains(mountpoints, partition.Mountpoint) {
mountpoints = append(mountpoints, partition.Mountpoint)
}
}
return mountpoints, nil
}
func findDCFMountPoints() ([]string, error) {
filteredMountpoints := []string{}
var err error
mountpoints, err := findAllMountPoints()
if err != nil {
return nil, err
}
for _, mountpoint := range mountpoints {
dcimPath := filepath.Join(mountpoint, "DCIM")
privatePath := filepath.Join(mountpoint, "PRIVATE")
shouldKeep := false
if _, err := os.Stat(dcimPath); err == nil {
shouldKeep = true
}
if _, err := os.Stat(privatePath); err == nil {
shouldKeep = true
}
if shouldKeep {
filteredMountpoints = append(filteredMountpoints, mountpoint)
}
}
return filteredMountpoints, nil
}
func contains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}