added primative support for png, gif others.
This commit is contained in:
parent
126a948fd4
commit
57d3e5fa1f
106
arrange.go
106
arrange.go
@ -3,7 +3,9 @@ package arrange
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@ -30,8 +32,13 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
type File interface {
|
||||
Move(root string) error
|
||||
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
|
||||
}
|
||||
|
||||
func PrepOutput(root string) error {
|
||||
@ -74,8 +81,8 @@ func Source(root string) <-chan string {
|
||||
return out
|
||||
}
|
||||
|
||||
func Parse(in <-chan string) <-chan File {
|
||||
out := make(chan File)
|
||||
func Parse(in <-chan string) <-chan Media {
|
||||
out := make(chan Media)
|
||||
go func() {
|
||||
for path := range in {
|
||||
f, err := _parse(path)
|
||||
@ -97,7 +104,7 @@ func Parse(in <-chan string) <-chan File {
|
||||
return out
|
||||
}
|
||||
|
||||
func Move(in <-chan File, root string) <-chan error {
|
||||
func Move(in <-chan Media, root string) <-chan error {
|
||||
out := make(chan error)
|
||||
go func() {
|
||||
for i := range in {
|
||||
@ -108,28 +115,30 @@ func Move(in <-chan File, root string) <-chan error {
|
||||
return out
|
||||
}
|
||||
|
||||
func _parse(path string) (File, error) {
|
||||
func _parse(path string) (Media, error) {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
var r File
|
||||
var r Media
|
||||
hash := md5.New()
|
||||
var t time.Time
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("problem opening file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
switch ext {
|
||||
default:
|
||||
return nil, NotMedia{path}
|
||||
return r, NotMedia{path}
|
||||
case ".jpg", ".jpeg":
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("problem opening file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := jpeg.DecodeConfig(f); err != nil {
|
||||
return nil, NotMedia{path}
|
||||
return r, NotMedia{path}
|
||||
}
|
||||
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
|
||||
var t time.Time
|
||||
{
|
||||
success := false
|
||||
if t, err = parseExif(f); err == nil {
|
||||
@ -139,34 +148,59 @@ func _parse(path string) (File, error) {
|
||||
t, err = mtime(path)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return nil, fmt.Errorf("couldn't seek back in file: %v", err)
|
||||
}
|
||||
hash := md5.New()
|
||||
if _, err := io.Copy(hash, f); err != nil {
|
||||
return nil, fmt.Errorf("problem calculating checksum on %q: %v", path, err)
|
||||
}
|
||||
r = Image{
|
||||
Path: path,
|
||||
Hash: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
Time: t,
|
||||
}
|
||||
case ".png":
|
||||
return nil, fmt.Errorf("NYI: %q", path)
|
||||
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":
|
||||
return nil, fmt.Errorf("NYI: %q", path)
|
||||
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 {
|
||||
return r, fmt.Errorf("couldn't seek back in file: %v", err)
|
||||
}
|
||||
if _, err := io.Copy(hash, f); err != nil {
|
||||
return r, fmt.Errorf("problem calculating checksum on %q: %v", path, err)
|
||||
}
|
||||
r = Media{
|
||||
Path: path,
|
||||
Hash: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
Extension: ext,
|
||||
Time: t,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func Merge(cs []<-chan File) <-chan File {
|
||||
out := make(chan File)
|
||||
func Merge(cs []<-chan Media) <-chan Media {
|
||||
out := make(chan Media)
|
||||
var wg sync.WaitGroup
|
||||
output := func(c <-chan File) {
|
||||
output := func(c <-chan Media) {
|
||||
for n := range c {
|
||||
out <- n
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func main() {
|
||||
}
|
||||
|
||||
work := arrange.Source(in)
|
||||
streams := []<-chan arrange.File{}
|
||||
streams := []<-chan arrange.Media{}
|
||||
|
||||
workers := runtime.NumCPU()
|
||||
if *cores != 0 {
|
||||
|
71
image.go
71
image.go
@ -1,75 +1,13 @@
|
||||
package arrange
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"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) {
|
||||
ti := time.Time{}
|
||||
x, err := exif.Decode(f)
|
||||
@ -84,12 +22,3 @@ func parseExif(f io.Reader) (time.Time, error) {
|
||||
}
|
||||
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
62
media.go
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user