diff options
-rw-r--r-- | cmd/midipak/main.go | 68 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | pkg/midipak/midipak.go | 55 |
4 files changed, 129 insertions, 0 deletions
diff --git a/cmd/midipak/main.go b/cmd/midipak/main.go new file mode 100644 index 0000000..3db1e7e --- /dev/null +++ b/cmd/midipak/main.go @@ -0,0 +1,68 @@ +// Command midipak creates a compressed pack from +// input MIDI files. +// +// midipak OUTPUT INPUTS... +package main + +import ( + "context" + "database/sql" + "fmt" + "io/ioutil" + "log" + "os" + "path" + + "github.com/klauspost/compress/zstd" + _ "github.com/mattn/go-sqlite3" + "go.terinstock.com/midifi/pkg/midipak" +) + +func pack(ctx context.Context, output string, files []string) error { + fa := make([]midipak.File, 0, len(files)) + for _, file := range files { + name := path.Base(file) + p, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + fa = append(fa, midipak.File{Name: name, Data: p}) + } + + enc, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) + if err != nil { + return err + } + defer enc.Close() + + db, err := sql.Open("sqlite3", fmt.Sprintf("%s", output)) + if err != nil { + return err + } + defer db.Close() + + if err := midipak.Create(ctx, db); err != nil { + return err + } + + if err := midipak.Pack(ctx, db, enc, fa); err != nil { + return err + } + + if _, err = db.Exec("VACUUM"); err != nil { + return err + } + + return nil +} + +func main() { + ctx := context.Background() + output := os.Args[1] + files := os.Args[2:] + + if err := pack(ctx, output, files); err != nil { + log.Print(err) + } +} diff --git a/go.mod b/go.mod index 3096343..bc93c25 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.14 require ( github.com/google/go-cmp v0.2.0 // indirect + github.com/klauspost/compress v1.11.7 + github.com/mattn/go-sqlite3 v1.14.6 github.com/pkg/errors v0.8.0 // indirect github.com/pkg/term v0.0.0-20180423043932-cda20d4ac917 github.com/tarm/serial v0.0.0-20180114052751-eaafced92e96 diff --git a/go.sum b/go.sum index 1af2417..308030b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v0.0.0-20180423043932-cda20d4ac917 h1:BinR73QvQveJdQ8uYZK/8MOjLADpZbI2qs/2+5rnhzQ= diff --git a/pkg/midipak/midipak.go b/pkg/midipak/midipak.go new file mode 100644 index 0000000..19ccde3 --- /dev/null +++ b/pkg/midipak/midipak.go @@ -0,0 +1,55 @@ +// midipak implements compressing and appending MIDI flles +// to a midipak file. +package midipak + +import ( + "context" + "database/sql" +) + +// Encoder compresses each of the input files, +// and is expected to be goroutine safe. +type Encoder interface { + EncodeAll(src, dst []byte) []byte +} + +// File stores the name and data of a file. +type File struct { + Name string + Data []byte +} + +// Create initializes a new midipak. +func Create(ctx context.Context, db *sql.DB) error { + sqlStmt := "CREATE TABLE files (name TEXT PRIMARY KEY, sz INTEGER NOT NULL, data BLOB);" + + _, err := db.ExecContext(ctx, sqlStmt) + return err +} + +// Pack appends the files to the midipak, after being compressed +// with the provided encoder. Attempting to pack files that already +// exist is an error. +func Pack(ctx context.Context, db *sql.DB, enc Encoder, files []File) error { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return err + } + + stmt, err := tx.Prepare("INSERT INTO files(name, sz, data) VALUES (?, ?, ?)") + if err != nil { + return err + } + defer stmt.Close() + + for _, file := range files { + sz := len(file.Data) + + _, err = stmt.Exec(file.Name, sz, enc.EncodeAll(file.Data, make([]byte, 0, sz))) + if err != nil { + return err + } + } + + return tx.Commit() +} |