summaryrefslogtreecommitdiff
path: root/media/library.go
blob: 7de68e0f3678b7eb864d143a1af09a6c73e781db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package media

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"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")
		}
	}
	if err := os.MkdirAll(p.Path, 0755); err != nil {
		return fmt.Errorf("error creating library path %s: %w", p.Path, err)
	}
	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 {
		if strings.ContainsAny(info.Name(), "#") {
			// ignore resized videos e.g: #240p.mp4
			continue
		}
		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++
	}
	By(SortByTimestamp).Sort(pl)
	return pl
}