318 lines
7.7 KiB
Go
318 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"image"
|
|
"image/color"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
var baseHTML = filepath.Join("templates", "base.html")
|
|
var indexHTML = filepath.Join("templates", "index.html")
|
|
var addStreamHTML = filepath.Join("templates", "add_stream.html")
|
|
var streamHTML = filepath.Join("templates", "stream.html")
|
|
var momentsHTML = filepath.Join("templates", "moments.html")
|
|
|
|
// Server is the main application struct
|
|
// It holds a map[string]*Stream that contains all the Stream objects
|
|
type Server struct {
|
|
Streams map[string]*Stream
|
|
}
|
|
|
|
// index shows a table of available streams or when visited with a 'name' param, the stream with that name.
|
|
func (server Server) index(w http.ResponseWriter, r *http.Request) {
|
|
if r.FormValue("name") == "" {
|
|
indexTemplate, err := template.ParseFiles(indexHTML, baseHTML)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
indexTemplate.Execute(w, server)
|
|
return
|
|
}
|
|
name := r.FormValue("name")
|
|
stream, exists := server.Streams[name]
|
|
if !exists {
|
|
return
|
|
}
|
|
streamTemplate, err := template.ParseFiles(streamHTML, baseHTML)
|
|
|
|
watchAreasJSON, _ := json.Marshal(stream.WatchAreas)
|
|
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
streamTemplate.Execute(w, struct {
|
|
Streams map[string]*Stream
|
|
Stream *Stream
|
|
WatchAreasJSON string
|
|
}{
|
|
server.Streams,
|
|
stream,
|
|
string(watchAreasJSON[:]),
|
|
})
|
|
}
|
|
|
|
// addStream adds the POSTed Stream to Server.Streams
|
|
func (server Server) addStream(w http.ResponseWriter, r *http.Request) {
|
|
nameMessage := ""
|
|
URLMessage := ""
|
|
intervalMessage := ""
|
|
motionIntervalMessage := ""
|
|
if r.Method == "POST" {
|
|
name := r.FormValue("name")
|
|
URL := r.FormValue("URL")
|
|
interval := r.FormValue("interval")
|
|
motionInterval := r.FormValue("motion_interval")
|
|
if name == "" {
|
|
nameMessage = "Name is required"
|
|
}
|
|
if URL == "" {
|
|
URLMessage = "URL is required"
|
|
}
|
|
if interval == "" {
|
|
intervalMessage = "Interval is required"
|
|
}
|
|
if motionInterval == "" {
|
|
motionIntervalMessage = "MotionInterval is required"
|
|
}
|
|
_, exists := server.Streams[name]
|
|
if exists {
|
|
nameMessage = "Name already used"
|
|
}
|
|
if !exists && name != "" && URL != "" && interval != "" && motionInterval != "" {
|
|
intInterval, err := strconv.ParseInt(interval, 10, 64)
|
|
if err != nil {
|
|
log.Println("Illigal value for Interval: ", interval)
|
|
intInterval = 5000
|
|
}
|
|
intMotionInterval, err := strconv.ParseInt(motionInterval, 10, 64)
|
|
if err != nil {
|
|
log.Println("Illigal value for MotionInterval: ", motionInterval)
|
|
intMotionInterval = 1000
|
|
}
|
|
server.Streams[name] = NewStream(
|
|
name,
|
|
URL,
|
|
(int)(intInterval),
|
|
(int)(intMotionInterval),
|
|
)
|
|
go server.Streams[name].UpdateInterval()
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
}
|
|
}
|
|
|
|
addStreamTemplate, err := template.ParseFiles(addStreamHTML, baseHTML)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
addStreamTemplate.Execute(w, struct {
|
|
Streams map[string]*Stream
|
|
NameMessage string
|
|
URLMessage string
|
|
IntervalMessage string
|
|
MotionIntervalMessage string
|
|
}{
|
|
server.Streams,
|
|
nameMessage,
|
|
URLMessage,
|
|
intervalMessage,
|
|
motionIntervalMessage,
|
|
})
|
|
}
|
|
|
|
// addWatchArea adds the POSTed WatchArea to Stream.WatchAreas
|
|
func (server Server) addWatchArea(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
return
|
|
}
|
|
streamName := r.FormValue("streamName")
|
|
if streamName == "" {
|
|
return
|
|
}
|
|
stream, exists := server.Streams[streamName]
|
|
if !exists {
|
|
return
|
|
}
|
|
name := r.FormValue("name")
|
|
if name == "" {
|
|
return
|
|
}
|
|
x0 := r.FormValue("x0")
|
|
if x0 == "" {
|
|
return
|
|
}
|
|
y0 := r.FormValue("y0")
|
|
if y0 == "" {
|
|
return
|
|
}
|
|
x1 := r.FormValue("x1")
|
|
if x1 == "" {
|
|
return
|
|
}
|
|
y1 := r.FormValue("y1")
|
|
if y1 == "" {
|
|
return
|
|
}
|
|
R := r.FormValue("R")
|
|
if R == "" {
|
|
return
|
|
}
|
|
G := r.FormValue("G")
|
|
if G == "" {
|
|
return
|
|
}
|
|
B := r.FormValue("B")
|
|
if B == "" {
|
|
return
|
|
}
|
|
stream.AddWatchArea(
|
|
name,
|
|
image.Rect(
|
|
StrToInt(x0),
|
|
StrToInt(y0),
|
|
StrToInt(x1),
|
|
StrToInt(y1),
|
|
),
|
|
color.RGBA{
|
|
uint8(StrToInt(R)),
|
|
uint8(StrToInt(G)),
|
|
uint8(StrToInt(B)),
|
|
255,
|
|
},
|
|
)
|
|
http.Redirect(w, r, "/?name="+streamName, http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
// streamRecordings shows a table of 'moments', a continuous series of motion detected instants
|
|
func (server Server) streamRecordings(w http.ResponseWriter, r *http.Request) {
|
|
streamName := r.FormValue("stream")
|
|
if streamName == "" {
|
|
return
|
|
}
|
|
watchAreaName := r.FormValue("watchArea")
|
|
if watchAreaName == "" {
|
|
return
|
|
}
|
|
|
|
stream, exists := server.Streams[streamName]
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
watchArea, exists := stream.GetWatchAreaByName(watchAreaName)
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
images, err := ioutil.ReadDir(watchArea.GetWatchAreaStoreDir(*stream))
|
|
if err != nil {
|
|
log.Println("Could not read watchArea directory", err)
|
|
return
|
|
}
|
|
moments := make([][]string, 0)
|
|
currentMoment := make([]string, 0)
|
|
momentIndex := 0
|
|
foundMoment := false
|
|
|
|
for i := range images {
|
|
if i == 0 { // Skip first for images[i-1] after this if
|
|
continue
|
|
}
|
|
previousImage := images[i-1]
|
|
previousTime, err := time.Parse(timeLayout, previousImage.Name())
|
|
if err != nil {
|
|
log.Println("Can't parse: ", previousImage.Name())
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
currentImage := images[i]
|
|
currentTime, err := time.Parse(timeLayout, currentImage.Name())
|
|
if err != nil {
|
|
log.Println("Can't parse: ", currentImage.Name())
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
|
|
// if the time difference is lower then the Stream.Interval, it means the Stream.MotionInterval
|
|
// Was used, and motion was therefore detected.
|
|
timeDifference := currentTime.Sub(previousTime)
|
|
lessThenInterval := timeDifference < (time.Duration(stream.Interval) * time.Millisecond)
|
|
if lessThenInterval {
|
|
foundMoment = true
|
|
currentMoment = append(currentMoment, previousImage.Name())
|
|
}
|
|
if !lessThenInterval || i == len(images) {
|
|
if foundMoment {
|
|
currentMoment = append(currentMoment, currentImage.Name())
|
|
moments = append(moments, currentMoment)
|
|
currentMoment = make([]string, 0)
|
|
momentIndex++
|
|
}
|
|
foundMoment = false
|
|
}
|
|
}
|
|
momentsTemplate, err := template.ParseFiles(momentsHTML, baseHTML)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
momentsJSON, _ := json.Marshal(moments)
|
|
momentsTemplate.Execute(w, struct {
|
|
Streams map[string]*Stream
|
|
MomentsJSON string
|
|
Moments [][]string
|
|
Stream *Stream
|
|
WatchArea *WatchArea
|
|
}{
|
|
server.Streams,
|
|
string(momentsJSON[:]),
|
|
moments,
|
|
stream,
|
|
watchArea,
|
|
})
|
|
}
|
|
|
|
// main sets up the HTTP server and reconstructs the Streams, if available.
|
|
func main() {
|
|
staticFileServer := http.FileServer(http.Dir("./static"))
|
|
http.Handle("/static/", http.StripPrefix("/static/", staticFileServer))
|
|
|
|
streamFileServer := http.FileServer(http.Dir("./streams"))
|
|
http.Handle("/streams/", http.StripPrefix("/streams/", streamFileServer))
|
|
|
|
server := Server{
|
|
Streams: make(map[string]*Stream),
|
|
}
|
|
streams, err := ioutil.ReadDir(GetStreamDirPath())
|
|
if err != nil {
|
|
log.Fatal("Could not read Streamdir")
|
|
}
|
|
for _, streamStoreDir := range streams {
|
|
if !streamStoreDir.IsDir() {
|
|
continue
|
|
}
|
|
|
|
// UnMarshal the stream.json into a Stream struct and start a goroutine for UpdateInterval
|
|
streamJSONPath := filepath.Join(GetStreamDirPath(), streamStoreDir.Name(), "stream.json")
|
|
stream := StreamFromJSON(streamJSONPath)
|
|
server.Streams[stream.Name] = stream
|
|
go stream.UpdateInterval()
|
|
}
|
|
|
|
http.HandleFunc("/", server.index)
|
|
http.HandleFunc("/addStream", server.addStream)
|
|
http.HandleFunc("/addWatchArea", server.addWatchArea)
|
|
http.HandleFunc("/streamRecordings", server.streamRecordings)
|
|
log.Println("Starting StreamWatcher Server on :8080")
|
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
}
|