parent
4fc389edcb
commit
deb6808c75
6 changed files with 126 additions and 38 deletions
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"library": "videos",
|
||||
"library": [
|
||||
{
|
||||
"path": "videos",
|
||||
"prefix": ""
|
||||
}
|
||||
],
|
||||
"server": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 0
|
||||
|
|
|
@ -56,8 +56,11 @@ func NewApp(cfg *Config) (*App, error) {
|
|||
r := mux.NewRouter().StrictSlash(true)
|
||||
r.HandleFunc("/", a.indexHandler).Methods("GET")
|
||||
r.HandleFunc("/v/{id}.mp4", a.videoHandler).Methods("GET")
|
||||
r.HandleFunc("/v/{prefix}/{id}.mp4", a.videoHandler).Methods("GET")
|
||||
r.HandleFunc("/t/{id}", a.thumbHandler).Methods("GET")
|
||||
r.HandleFunc("/t/{prefix}/{id}", a.thumbHandler).Methods("GET")
|
||||
r.HandleFunc("/v/{id}", a.pageHandler).Methods("GET")
|
||||
r.HandleFunc("/v/{prefix}/{id}", a.pageHandler).Methods("GET")
|
||||
r.HandleFunc("/feed.xml", a.rssHandler).Methods("GET")
|
||||
// Static file handler
|
||||
fsHandler := http.StripPrefix(
|
||||
|
@ -71,12 +74,21 @@ func NewApp(cfg *Config) (*App, error) {
|
|||
|
||||
// Run imports the library and starts server.
|
||||
func (a *App) Run() error {
|
||||
path := a.Config.LibraryPath
|
||||
err := a.Library.Import(path)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, pc := range a.Config.Library {
|
||||
p := &media.Path{
|
||||
Path: pc.Path,
|
||||
Prefix: pc.Prefix,
|
||||
}
|
||||
err := a.Library.AddPath(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = a.Library.Import(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Watcher.Add(p.Path)
|
||||
}
|
||||
a.Watcher.Add(path)
|
||||
go a.watch()
|
||||
return http.Serve(a.Listener, a.Router)
|
||||
}
|
||||
|
@ -124,6 +136,10 @@ func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
|
|||
func (a *App) pageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
prefix, ok := vars["prefix"]
|
||||
if ok {
|
||||
id = path.Join(prefix, id)
|
||||
}
|
||||
log.Printf("/v/%s", id)
|
||||
playing, ok := a.Library.Videos[id]
|
||||
if !ok {
|
||||
|
@ -150,6 +166,10 @@ func (a *App) pageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
func (a *App) videoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
prefix, ok := vars["prefix"]
|
||||
if ok {
|
||||
id = path.Join(prefix, id)
|
||||
}
|
||||
log.Printf("/v/%s", id)
|
||||
m, ok := a.Library.Videos[id]
|
||||
if !ok {
|
||||
|
@ -159,14 +179,17 @@ func (a *App) videoHandler(w http.ResponseWriter, r *http.Request) {
|
|||
disposition := "attachment; filename=\"" + title + ".mp4\""
|
||||
w.Header().Set("Content-Disposition", disposition)
|
||||
w.Header().Set("Content-Type", "video/mp4")
|
||||
path := a.Config.LibraryPath + "/" + id + ".mp4"
|
||||
http.ServeFile(w, r, path)
|
||||
http.ServeFile(w, r, m.Path)
|
||||
}
|
||||
|
||||
// HTTP handler for /t/id
|
||||
func (a *App) thumbHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
prefix, ok := vars["prefix"]
|
||||
if ok {
|
||||
id = path.Join(prefix, id)
|
||||
}
|
||||
log.Printf("/t/%s", id)
|
||||
m, ok := a.Library.Videos[id]
|
||||
if !ok {
|
||||
|
|
|
@ -7,9 +7,15 @@ import (
|
|||
|
||||
// Config settings for main App.
|
||||
type Config struct {
|
||||
LibraryPath string `json:"library"`
|
||||
Server *ServerConfig `json:"server"`
|
||||
Feed *FeedConfig `json:"feed"`
|
||||
Library []*PathConfig `json:"library"`
|
||||
Server *ServerConfig `json:"server"`
|
||||
Feed *FeedConfig `json:"feed"`
|
||||
}
|
||||
|
||||
// PathConfig settings for media library path.
|
||||
type PathConfig struct {
|
||||
Path string `json:"path"`
|
||||
Prefix string `json:"prefix"`
|
||||
}
|
||||
|
||||
// ServerConfig settings for App Server.
|
||||
|
@ -34,7 +40,12 @@ type FeedConfig struct {
|
|||
// DefaultConfig returns Config initialized with default values.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
LibraryPath: "videos",
|
||||
Library: []*PathConfig{
|
||||
&PathConfig{
|
||||
Path: "videos",
|
||||
Prefix: "",
|
||||
},
|
||||
},
|
||||
Server: &ServerConfig{
|
||||
Host: "127.0.0.1",
|
||||
Port: 0,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package media
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -12,25 +13,44 @@ import (
|
|||
// 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(path string) error {
|
||||
files, err := ioutil.ReadDir(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 + "/" + info.Name())
|
||||
err = lib.Add(path.Join(p.Path, info.Name()))
|
||||
if err != nil {
|
||||
// Ignore files that can't be parsed
|
||||
continue
|
||||
|
@ -40,33 +60,48 @@ func (lib *Library) Import(path string) error {
|
|||
}
|
||||
|
||||
// Add adds a single video from a given file path.
|
||||
func (lib *Library) Add(path string) error {
|
||||
v, err := ParseVideo(path)
|
||||
func (lib *Library) Add(filepath string) error {
|
||||
lib.mu.Lock()
|
||||
defer lib.mu.Unlock()
|
||||
d := path.Dir(filepath)
|
||||
p, ok := lib.Paths[d]
|
||||
if !ok {
|
||||
log.Println(d)
|
||||
return errors.New("media: path not found")
|
||||
}
|
||||
n := path.Base(filepath)
|
||||
v, err := ParseVideo(p, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lib.mu.Lock()
|
||||
defer lib.mu.Unlock()
|
||||
lib.Videos[v.ID] = v
|
||||
log.Println("Added:", path)
|
||||
log.Println("Added:", v.Path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a single video from a given file path.
|
||||
func (lib *Library) Remove(path string) {
|
||||
name := filepath.Base(path)
|
||||
// ID is name without extension
|
||||
idx := strings.LastIndex(name, ".")
|
||||
if idx == -1 {
|
||||
idx = len(name)
|
||||
}
|
||||
id := name[:idx]
|
||||
func (lib *Library) Remove(filepath string) {
|
||||
lib.mu.Lock()
|
||||
defer lib.mu.Unlock()
|
||||
_, ok := lib.Videos[id]
|
||||
d := path.Dir(filepath)
|
||||
p, ok := lib.Paths[d]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
n := path.Base(filepath)
|
||||
// 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:", path)
|
||||
log.Println("Removed:", v.Path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
7
pkg/media/path.go
Normal file
7
pkg/media/path.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package media
|
||||
|
||||
// Path represents a media library path.
|
||||
type Path struct {
|
||||
Path string
|
||||
Prefix string
|
||||
}
|
|
@ -2,6 +2,7 @@ package media
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -18,12 +19,14 @@ type Video struct {
|
|||
ThumbType string
|
||||
Modified string
|
||||
Size int64
|
||||
Path string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// ParseVideo parses a video file's metadata and returns a Video.
|
||||
func ParseVideo(path string) (*Video, error) {
|
||||
f, err := os.Open(path)
|
||||
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
|
||||
}
|
||||
|
@ -34,13 +37,16 @@ func ParseVideo(path string) (*Video, error) {
|
|||
size := info.Size()
|
||||
timestamp := info.ModTime()
|
||||
modified := timestamp.Format("2006-01-02 03:04 PM")
|
||||
name := info.Name()
|
||||
// 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
|
||||
|
@ -57,13 +63,14 @@ func ParseVideo(path string) (*Video, error) {
|
|||
Description: m.Comment(),
|
||||
Modified: modified,
|
||||
Size: size,
|
||||
Path: pth,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
// Add thumbnail (if exists)
|
||||
p := m.Picture()
|
||||
if p != nil {
|
||||
v.Thumb = p.Data
|
||||
v.ThumbType = p.MIMEType
|
||||
pic := m.Picture()
|
||||
if pic != nil {
|
||||
v.Thumb = pic.Data
|
||||
v.ThumbType = pic.MIMEType
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue