diff options
Diffstat (limited to 'media')
| -rw-r--r-- | media/library.go | 122 | ||||
| -rw-r--r-- | media/media.go | 2 | ||||
| -rw-r--r-- | media/path.go | 7 | ||||
| -rw-r--r-- | media/playlist.go | 19 | ||||
| -rw-r--r-- | media/video.go | 77 |
5 files changed, 227 insertions, 0 deletions
diff --git a/media/library.go b/media/library.go new file mode 100644 index 0000000..87fb223 --- /dev/null +++ b/media/library.go @@ -0,0 +1,122 @@ +package media + +import ( + "errors" + "io/ioutil" + "log" + "path" + "path/filepath" + "sort" + "strings" + "sync" +) + +// Library manages importing and retrieving video data. +type Library struct { + mu sync.RWMutex + Paths map[string]*Path + Videos map[string]*Video +} + +// NewLibrary returns new instance of Library. +func NewLibrary() *Library { + lib := &Library{ + Paths: make(map[string]*Path), + Videos: make(map[string]*Video), + } + return lib +} + +// AddPath adds a media path to the library. +func (lib *Library) AddPath(p *Path) error { + lib.mu.Lock() + defer lib.mu.Unlock() + // make sure new path doesn't collide with existing ones + for _, p2 := range lib.Paths { + if p.Path == p2.Path { + return errors.New("media: duplicate library path") + } + if p.Prefix == p2.Prefix { + return errors.New("media: duplicate library prefix") + } + } + lib.Paths[p.Path] = p + return nil +} + +// Import adds all valid videos from a given path. +func (lib *Library) Import(p *Path) error { + files, err := ioutil.ReadDir(p.Path) + if err != nil { + return err + } + for _, info := range files { + err = lib.Add(path.Join(p.Path, info.Name())) + if err != nil { + // Ignore files that can't be parsed + continue + } + } + return nil +} + +// Add adds a single video from a given file path. +func (lib *Library) Add(fp string) error { + lib.mu.Lock() + defer lib.mu.Unlock() + fp = filepath.ToSlash(fp) + d := path.Dir(fp) + p, ok := lib.Paths[d] + if !ok { + return errors.New("media: path not found") + } + n := path.Base(fp) + v, err := ParseVideo(p, n) + if err != nil { + return err + } + lib.Videos[v.ID] = v + log.Println("Added:", v.Path) + return nil +} + +// Remove removes a single video from a given file path. +func (lib *Library) Remove(fp string) { + lib.mu.Lock() + defer lib.mu.Unlock() + fp = filepath.ToSlash(fp) + d := path.Dir(fp) + p, ok := lib.Paths[d] + if !ok { + return + } + n := path.Base(fp) + // ID is name without extension + idx := strings.LastIndex(n, ".") + if idx == -1 { + idx = len(n) + } + id := n[:idx] + if len(p.Prefix) > 0 { + id = path.Join(p.Prefix, id) + } + v, ok := lib.Videos[id] + if ok { + delete(lib.Videos, id) + log.Println("Removed:", v.Path) + } +} + +// Playlist returns a sorted Playlist of all videos. +func (lib *Library) Playlist() Playlist { + lib.mu.RLock() + defer lib.mu.RUnlock() + pl := make(Playlist, len(lib.Videos)) + i := 0 + for _, v := range lib.Videos { + pl[i] = v + i++ + } + sort.Sort(pl) + return pl +} diff --git a/media/media.go b/media/media.go new file mode 100644 index 0000000..6fa6725 --- /dev/null +++ b/media/media.go @@ -0,0 +1,2 @@ +// Package media manages video library functionality. +package media diff --git a/media/path.go b/media/path.go new file mode 100644 index 0000000..1248ed5 --- /dev/null +++ b/media/path.go @@ -0,0 +1,7 @@ +package media + +// Path represents a media library path. +type Path struct { + Path string + Prefix string +} diff --git a/media/playlist.go b/media/playlist.go new file mode 100644 index 0000000..cf8edcf --- /dev/null +++ b/media/playlist.go @@ -0,0 +1,19 @@ +package media + +// Playlist holds an array of videos capable of sorting by Timestamp. +type Playlist []*Video + +// Len returns length of array (for sorting). +func (p Playlist) Len() int { + return len(p) +} + +// Swap swaps two values in array by index (for sorting). +func (p Playlist) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +// Less returns true if p[i] Timestamp is after p[j] (for sorting). +func (p Playlist) Less(i, j int) bool { + return p[i].Timestamp.After(p[j].Timestamp) +} diff --git a/media/video.go b/media/video.go new file mode 100644 index 0000000..9340e86 --- /dev/null +++ b/media/video.go @@ -0,0 +1,77 @@ +package media + +import ( + "os" + "path" + "strings" + "time" + + "github.com/dhowden/tag" +) + +// Video represents metadata for a single video. +type Video struct { + ID string + Title string + Album string + Description string + Thumb []byte + ThumbType string + Modified string + Size int64 + Path string + Timestamp time.Time +} + +// ParseVideo parses a video file's metadata and returns a Video. +func ParseVideo(p *Path, name string) (*Video, error) { + pth := path.Join(p.Path, name) + f, err := os.Open(pth) + if err != nil { + return nil, err + } + defer f.Close() + info, err := f.Stat() + if err != nil { + return nil, err + } + size := info.Size() + timestamp := info.ModTime() + modified := timestamp.Format("2006-01-02 03:04 PM") + // ID is name without extension + idx := strings.LastIndex(name, ".") + if idx == -1 { + idx = len(name) + } + id := name[:idx] + if len(p.Prefix) > 0 { + // if there's a prefix prepend it to the ID + id = path.Join(p.Prefix, name[:idx]) + } + m, err := tag.ReadFrom(f) + if err != nil { + return nil, err + } + title := m.Title() + // Default title is filename + if title == "" { + title = name + } + v := &Video{ + ID: id, + Title: title, + Album: m.Album(), + Description: m.Comment(), + Modified: modified, + Size: size, + Path: pth, + Timestamp: timestamp, + } + // Add thumbnail (if exists) + pic := m.Picture() + if pic != nil { + v.Thumb = pic.Data + v.ThumbType = pic.MIMEType + } + return v, nil +} |
