added primative support for png, gif others.

This commit is contained in:
Stephen McQuay 2016-05-18 21:11:18 -07:00
parent 126a948fd4
commit 57d3e5fa1f
No known key found for this signature in database
GPG Key ID: 1ABF428F71BAFC3D
4 changed files with 133 additions and 108 deletions

View File

@ -3,7 +3,9 @@ package arrange
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"image/gif"
"image/jpeg" "image/jpeg"
"image/png"
"io" "io"
"log" "log"
"os" "os"
@ -30,8 +32,13 @@ func init() {
} }
} }
type File interface { func mtime(path string) (time.Time, error) {
Move(root string) error ti := time.Time{}
s, err := os.Stat(path)
if err != nil {
return ti, fmt.Errorf("failure to collect times from stat: %v", err)
}
return s.ModTime(), nil
} }
func PrepOutput(root string) error { func PrepOutput(root string) error {
@ -74,8 +81,8 @@ func Source(root string) <-chan string {
return out return out
} }
func Parse(in <-chan string) <-chan File { func Parse(in <-chan string) <-chan Media {
out := make(chan File) out := make(chan Media)
go func() { go func() {
for path := range in { for path := range in {
f, err := _parse(path) f, err := _parse(path)
@ -97,7 +104,7 @@ func Parse(in <-chan string) <-chan File {
return out return out
} }
func Move(in <-chan File, root string) <-chan error { func Move(in <-chan Media, root string) <-chan error {
out := make(chan error) out := make(chan error)
go func() { go func() {
for i := range in { for i := range in {
@ -108,28 +115,30 @@ func Move(in <-chan File, root string) <-chan error {
return out return out
} }
func _parse(path string) (File, error) { func _parse(path string) (Media, error) {
ext := strings.ToLower(filepath.Ext(path)) ext := strings.ToLower(filepath.Ext(path))
var r File var r Media
switch ext { hash := md5.New()
default: var t time.Time
return nil, NotMedia{path}
case ".jpg", ".jpeg":
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("problem opening file: %v", err) return r, fmt.Errorf("problem opening file: %v", err)
} }
defer f.Close() defer f.Close()
switch ext {
default:
return r, NotMedia{path}
case ".jpg", ".jpeg":
if _, err := jpeg.DecodeConfig(f); err != nil { if _, err := jpeg.DecodeConfig(f); err != nil {
return nil, NotMedia{path} return r, NotMedia{path}
} }
if _, err := f.Seek(0, 0); err != nil { if _, err := f.Seek(0, 0); err != nil {
return nil, fmt.Errorf("couldn't seek back in file: %v", err) return r, fmt.Errorf("couldn't seek back in file: %v", err)
} }
// try a few things for a time value // try a few things for a time value
var t time.Time
{ {
success := false success := false
if t, err = parseExif(f); err == nil { if t, err = parseExif(f); err == nil {
@ -139,34 +148,59 @@ func _parse(path string) (File, error) {
t, err = mtime(path) t, err = mtime(path)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to calculate reasonble time for jpg %q: %v", path, err) return r, fmt.Errorf("unable to calculate reasonble time for jpg %q: %v", path, err)
}
}
case ".png":
if _, err := png.DecodeConfig(f); err != nil {
return r, NotMedia{path}
}
if _, err := f.Seek(0, 0); err != nil {
return r, fmt.Errorf("couldn't seek back in file: %v", err)
}
t, err = mtime(path)
if err != nil {
return r, fmt.Errorf("unable to calculate reasonble time for media %q: %v", path, err)
}
case ".gif":
if _, err := gif.DecodeConfig(f); err != nil {
return r, NotMedia{path}
}
if _, err := f.Seek(0, 0); err != nil {
return r, fmt.Errorf("couldn't seek back in file: %v", err)
}
t, err = mtime(path)
if err != nil {
return r, fmt.Errorf("unable to calculate reasonble time for media %q: %v", path, err)
}
case ".mov", ".mp4", ".m4v":
t, err = mtime(path)
if err != nil {
return r, fmt.Errorf("unable to calculate reasonble time for media %q: %v", path, err)
} }
} }
if _, err := f.Seek(0, 0); err != nil { if _, err := f.Seek(0, 0); err != nil {
return nil, fmt.Errorf("couldn't seek back in file: %v", err) return r, fmt.Errorf("couldn't seek back in file: %v", err)
} }
hash := md5.New()
if _, err := io.Copy(hash, f); err != nil { if _, err := io.Copy(hash, f); err != nil {
return nil, fmt.Errorf("problem calculating checksum on %q: %v", path, err) return r, fmt.Errorf("problem calculating checksum on %q: %v", path, err)
} }
r = Image{ r = Media{
Path: path, Path: path,
Hash: fmt.Sprintf("%x", hash.Sum(nil)), Hash: fmt.Sprintf("%x", hash.Sum(nil)),
Extension: ext,
Time: t, Time: t,
} }
case ".png":
return nil, fmt.Errorf("NYI: %q", path)
case ".mov", ".mp4", ".m4v":
return nil, fmt.Errorf("NYI: %q", path)
}
return r, nil return r, nil
} }
func Merge(cs []<-chan File) <-chan File { func Merge(cs []<-chan Media) <-chan Media {
out := make(chan File) out := make(chan Media)
var wg sync.WaitGroup var wg sync.WaitGroup
output := func(c <-chan File) { output := func(c <-chan Media) {
for n := range c { for n := range c {
out <- n out <- n
} }

View File

@ -35,7 +35,7 @@ func main() {
} }
work := arrange.Source(in) work := arrange.Source(in)
streams := []<-chan arrange.File{} streams := []<-chan arrange.Media{}
workers := runtime.NumCPU() workers := runtime.NumCPU()
if *cores != 0 { if *cores != 0 {

View File

@ -1,75 +1,13 @@
package arrange package arrange
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath"
"time" "time"
"github.com/rwcarlsen/goexif/exif" "github.com/rwcarlsen/goexif/exif"
) )
type Media struct {
Path string
}
func (m Media) Move(root string) error {
return errors.New("NYI")
}
type Image struct {
Path string
Hash string
Time time.Time
}
func (im Image) Move(root string) error {
f, err := os.Open(im.Path)
if err != nil {
return fmt.Errorf("problem opening jpg file: %v", err)
}
defer f.Close()
content := filepath.Join(root, "content", im.Hash[:2], im.Hash[2:]+".jpg")
if _, err := os.Stat(content); !os.IsNotExist(err) {
return Dup{content}
}
out, err := os.Create(content)
if err != nil {
return fmt.Errorf("could not create output file: %v", err)
}
defer out.Close()
if _, err := io.Copy(out, f); err != nil {
return fmt.Errorf("trouble copying file: %v", err)
}
year := fmt.Sprintf("%04d", im.Time.Year())
month := fmt.Sprintf("%02d", im.Time.Month())
ts := fmt.Sprintf("%d", im.Time.UnixNano())
if err := os.MkdirAll(filepath.Join(root, "date", year, month), 0755); err != nil {
return fmt.Errorf("problem creating date directory: %v", err)
}
date := filepath.Join(root, "date", year, month, ts)
name := date + ".jpg"
for i := 0; i < 10000; i++ {
if _, err := os.Stat(name); os.IsNotExist(err) {
break
}
name = fmt.Sprintf("%s_%04d.jpg", date, i)
}
// TODO: or maybe symlinking? (issue #2)
// rel := filepath.Join("..", "..", "..", "content", j.hash[:2], j.hash[2:]+".jpg")
// return os.Symlink(rel, name)
return os.Link(content, name)
}
func parseExif(f io.Reader) (time.Time, error) { func parseExif(f io.Reader) (time.Time, error) {
ti := time.Time{} ti := time.Time{}
x, err := exif.Decode(f) x, err := exif.Decode(f)
@ -84,12 +22,3 @@ func parseExif(f io.Reader) (time.Time, error) {
} }
return tm, nil return tm, nil
} }
func mtime(path string) (time.Time, error) {
ti := time.Time{}
s, err := os.Stat(path)
if err != nil {
return ti, fmt.Errorf("failure to collect times from stat: %v", err)
}
return s.ModTime(), nil
}

62
media.go Normal file
View File

@ -0,0 +1,62 @@
package arrange
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
)
type Media struct {
Path string
Hash string
Extension string
Time time.Time
}
func (m Media) Move(root string) error {
f, err := os.Open(m.Path)
if err != nil {
return fmt.Errorf("problem opening file %q: %v", m.Path, err)
}
defer f.Close()
content := filepath.Join(root, "content", m.Hash[:2], m.Hash[2:]+m.Extension)
if _, err := os.Stat(content); !os.IsNotExist(err) {
return Dup{content}
}
out, err := os.Create(content)
if err != nil {
return fmt.Errorf("could not create output file: %v", err)
}
defer out.Close()
if _, err := io.Copy(out, f); err != nil {
return fmt.Errorf("trouble copying file: %v", err)
}
year := fmt.Sprintf("%04d", m.Time.Year())
month := fmt.Sprintf("%02d", m.Time.Month())
ts := fmt.Sprintf("%d", m.Time.UnixNano())
if err := os.MkdirAll(filepath.Join(root, "date", year, month), 0755); err != nil {
return fmt.Errorf("problem creating date directory: %v", err)
}
date := filepath.Join(root, "date", year, month, ts)
name := date + m.Extension
for i := 0; i < 10000; i++ {
if _, err := os.Stat(name); os.IsNotExist(err) {
break
}
name = fmt.Sprintf("%s_%04d%s", date, i, m.Extension)
}
// TODO: or maybe symlinking? (issue #2)
// rel := filepath.Join("..", "..", "..", "content", j.hash[:2], j.hash[2:]+m.Extension)
// return os.Symlink(rel, name)
return os.Link(content, name)
}