implement full load of videos and startdata

modify api where necessary
This commit is contained in:
2021-02-23 16:01:29 +00:00
parent 2967aee16d
commit f2b5fb6587
69 changed files with 14016 additions and 21001 deletions

72
apiGo/api/Actors.go Normal file
View File

@ -0,0 +1,72 @@
package api
import (
"fmt"
"openmediacenter/apiGo/api/types"
"openmediacenter/apiGo/database"
)
func AddActorsHandlers() {
saveActorsToDB()
getActorsFromDB()
}
func getActorsFromDB() {
AddHandler("getAllActors", ActorNode, nil, func() []byte {
query := "SELECT actor_id, name, thumbnail FROM actors"
return jsonify(readActorsFromResultset(database.Query(query)))
})
var gaov struct {
MovieId int
}
AddHandler("getActorsOfVideo", ActorNode, &gaov, func() []byte {
query := fmt.Sprintf(`SELECT a.actor_id, name, thumbnail FROM actors_videos
JOIN actors a on actors_videos.actor_id = a.actor_id
WHERE actors_videos.video_id=%d`, gaov.MovieId)
return jsonify(readActorsFromResultset(database.Query(query)))
})
var gai struct {
ActorId int
}
AddHandler("getActorInfo", ActorNode, &gai, func() []byte {
query := fmt.Sprintf(`SELECT movie_id, movie_name FROM actors_videos
JOIN videos v on v.movie_id = actors_videos.video_id
WHERE actors_videos.actor_id=%d`, gai.ActorId)
videos := readVideosFromResultset(database.Query(query))
query = fmt.Sprintf("SELECT actor_id, name, thumbnail FROM actors WHERE actor_id=%d", gai.ActorId)
actor := readActorsFromResultset(database.Query(query))[0]
var result = struct {
Videos []types.VideoUnloadedType
Info types.Actor
}{
Videos: videos,
Info: actor,
}
return jsonify(result)
})
}
func saveActorsToDB() {
var ca struct {
ActorName string
}
AddHandler("createActor", ActorNode, &ca, func() []byte {
query := "INSERT IGNORE INTO actors (name) VALUES (?)"
return database.SuccessQuery(query, ca.ActorName)
})
var aatv struct {
ActorId int
MovieId int
}
AddHandler("addActorToVideo", ActorNode, &aatv, func() []byte {
query := fmt.Sprintf("INSERT IGNORE INTO actors_videos (actor_id, video_id) VALUES (%d,%d)", aatv.ActorId, aatv.MovieId)
return database.SuccessQuery(query)
})
}

101
apiGo/api/ApiBase.go Normal file
View File

@ -0,0 +1,101 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)
const APIPREFIX = "/api"
const (
VideoNode = iota
TagNode = iota
SettingsNode = iota
ActorNode = iota
)
type actionStruct struct {
Action string
}
type Handler struct {
action string
handler func() []byte
arguments interface{}
apiNode int
}
var handlers []Handler
func AddHandler(action string, apiNode int, n interface{}, h func() []byte) {
// append new handler to the handlers
handlers = append(handlers, Handler{action, h, n, apiNode})
}
func ServerInit(port uint16) {
http.Handle(APIPREFIX+"/video", http.HandlerFunc(videoHandler))
http.Handle(APIPREFIX+"/tags", http.HandlerFunc(tagHandler))
http.Handle(APIPREFIX+"/settings", http.HandlerFunc(settingsHandler))
http.Handle(APIPREFIX+"/actor", http.HandlerFunc(actorHandler))
fmt.Printf("OpenMediacenter server up and running on port %d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
func handleAPICall(action string, requestBody string, apiNode int) []byte {
for i := range handlers {
if handlers[i].action == action && handlers[i].apiNode == apiNode {
// call the handler and return
if handlers[i].arguments != nil {
// decode the arguments to the corresponding arguments object
err := json.Unmarshal([]byte(requestBody), &handlers[i].arguments)
if err != nil {
fmt.Printf("failed to decode arguments of action %s :: %s\n", action, requestBody)
}
}
return handlers[i].handler()
}
}
fmt.Printf("no handler found for Action: %d/%s\n", apiNode, action)
return nil
}
func actorHandler(rw http.ResponseWriter, req *http.Request) {
handlefunc(rw, req, ActorNode)
}
func videoHandler(rw http.ResponseWriter, req *http.Request) {
handlefunc(rw, req, VideoNode)
}
func tagHandler(rw http.ResponseWriter, req *http.Request) {
handlefunc(rw, req, TagNode)
}
func settingsHandler(rw http.ResponseWriter, req *http.Request) {
handlefunc(rw, req, SettingsNode)
}
func handlefunc(rw http.ResponseWriter, req *http.Request, node int) {
// only allow post requests
if req.Method != "POST" {
return
}
buf := new(bytes.Buffer)
buf.ReadFrom(req.Body)
body := buf.String()
var t actionStruct
err := json.Unmarshal([]byte(body), &t)
if err != nil {
fmt.Println("failed to read action from request! :: " + body)
}
rw.Write(handleAPICall(t.Action, body, node))
}

66
apiGo/api/ApiBase_test.go Normal file
View File

@ -0,0 +1,66 @@
package api
import (
"testing"
)
func cleanUp() {
handlers = nil
}
func TestAddHandler(t *testing.T) {
cleanUp()
AddHandler("test", ActorNode, nil, func() []byte {
return nil
})
if len(handlers) != 1 {
t.Errorf("Handler insertion failed, got: %d handlers, want: %d.", len(handlers), 1)
}
}
func TestCallOfHandler(t *testing.T) {
cleanUp()
i := 0
AddHandler("test", ActorNode, nil, func() []byte {
i++
return nil
})
// simulate the call of the api
handleAPICall("test", "", ActorNode)
if i != 1 {
t.Errorf("Unexpected number of Lambda calls : %d/1", i)
}
}
func TestDecodingOfArguments(t *testing.T) {
cleanUp()
var myvar struct {
Test string
TestInt int
}
AddHandler("test", ActorNode, &myvar, func() []byte {
return nil
})
// simulate the call of the api
handleAPICall("test", `{"Test":"myString","TestInt":42}`, ActorNode)
if myvar.TestInt != 42 || myvar.Test != "myString" {
t.Errorf("Wrong parsing of argument parameters : %d/42 - %s/myString", myvar.TestInt, myvar.Test)
}
}
func TestNoHandlerCovers(t *testing.T) {
cleanUp()
ret := handleAPICall("test", "", ActorNode)
if ret != nil {
t.Error("Expect nil return within unhandled api action")
}
}

71
apiGo/api/Helpers.go Normal file
View File

@ -0,0 +1,71 @@
package api
import (
"database/sql"
"encoding/json"
"fmt"
"openmediacenter/apiGo/api/types"
)
// MovieId - MovieName : pay attention to the order!
func readVideosFromResultset(rows *sql.Rows) []types.VideoUnloadedType {
result := []types.VideoUnloadedType{}
for rows.Next() {
var vid types.VideoUnloadedType
err := rows.Scan(&vid.MovieId, &vid.MovieName)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
result = append(result, vid)
}
rows.Close()
return result
}
// TagID - TagName : pay attention to the order!
func readTagsFromResultset(rows *sql.Rows) []types.Tag {
// initialize with empty array!
result := []types.Tag{}
for rows.Next() {
var tag types.Tag
err := rows.Scan(&tag.TagId, &tag.TagName)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
result = append(result, tag)
}
rows.Close()
return result
}
// ActorId - ActorName - Thumbnail : pay attention to the order!
func readActorsFromResultset(rows *sql.Rows) []types.Actor {
var result []types.Actor
for rows.Next() {
var actor types.Actor
var thumbnail []byte
err := rows.Scan(&actor.ActorId, &actor.Name, &thumbnail)
if len(thumbnail) != 0 {
actor.Thumbnail = string(thumbnail)
}
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
result = append(result, actor)
}
rows.Close()
return result
}
func jsonify(v interface{}) []byte {
// jsonify results
str, err := json.Marshal(v)
if err != nil {
fmt.Println("Error while Jsonifying return object: " + err.Error())
}
return str
}

94
apiGo/api/Settings.go Normal file
View File

@ -0,0 +1,94 @@
package api
import (
"encoding/json"
"fmt"
"openmediacenter/apiGo/api/types"
"openmediacenter/apiGo/database"
"openmediacenter/apiGo/videoparser"
)
func AddSettingsHandlers() {
saveSettingsToDB()
getSettingsFromDB()
reIndexHandling()
}
func getSettingsFromDB() {
AddHandler("loadInitialData", SettingsNode, nil, func() []byte {
query := "SELECT DarkMode, password, mediacenter_name, video_path from settings"
type InitialDataType struct {
DarkMode int
Pasword int
Mediacenter_name string
VideoPath string
}
result := InitialDataType{}
err := database.QueryRow(query).Scan(&result.DarkMode, &result.Pasword, &result.Mediacenter_name, &result.VideoPath)
if err != nil {
fmt.Println("error while parsing db data: " + err.Error())
}
type InitialDataTypeResponse struct {
DarkMode bool
Pasword bool
Mediacenter_name string
VideoPath string
}
res := InitialDataTypeResponse{
DarkMode: result.DarkMode != 0,
Pasword: result.Pasword != -1,
Mediacenter_name: result.Mediacenter_name,
VideoPath: result.VideoPath,
}
str, _ := json.Marshal(res)
return str
})
AddHandler("loadGeneralSettings", SettingsNode, nil, func() []byte {
result := database.GetSettings()
return jsonify(result)
})
}
func saveSettingsToDB() {
var sgs struct {
Settings types.SettingsType
}
AddHandler("saveGeneralSettings", SettingsNode, &sgs, func() []byte {
query := `
UPDATE settings SET
video_path=?,
episode_path=?,
password=?,
mediacenter_name=?,
TMDB_grabbing=?,
DarkMode=?
WHERE 1`
return database.SuccessQuery(query,
sgs.Settings.VideoPath, sgs.Settings.EpisodePath, sgs.Settings.Password,
sgs.Settings.MediacenterName, sgs.Settings.TMDBGrabbing, sgs.Settings.DarkMode)
})
}
// methods for handling reindexing and cleanup of db gravity
func reIndexHandling() {
AddHandler("startReindex", SettingsNode, nil, func() []byte {
videoparser.StartReindex()
return database.ManualSuccessResponse(nil)
})
AddHandler("cleanupGravity", SettingsNode, nil, func() []byte {
videoparser.StartCleanup()
return nil
})
AddHandler("getStatusMessage", SettingsNode, nil, func() []byte {
return jsonify(videoparser.GetStatusMessage())
})
}

74
apiGo/api/Tags.go Normal file
View File

@ -0,0 +1,74 @@
package api
import (
"fmt"
"openmediacenter/apiGo/database"
"regexp"
)
func AddTagHandlers() {
getFromDB()
addToDB()
deleteFromDB()
}
func deleteFromDB() {
var dT struct {
TagId int
Force bool
}
AddHandler("deleteTag", TagNode, &dT, func() []byte {
// delete key constraints first
if dT.Force {
query := fmt.Sprintf("DELETE FROM video_tags WHERE tag_id=%d", dT.TagId)
err := database.Edit(query)
// respond only if result not successful
if err != nil {
return database.ManualSuccessResponse(err)
}
}
query := fmt.Sprintf("DELETE FROM tags WHERE tag_id=%d", dT.TagId)
err := database.Edit(query)
if err == nil {
// return if successful
return database.ManualSuccessResponse(err)
} else {
// check with regex if its the key constraint error
r, _ := regexp.Compile("^.*a foreign key constraint fails.*$")
if r.MatchString(err.Error()) {
return []byte(`{"result":"not empty tag"}`)
} else {
return database.ManualSuccessResponse(err)
}
}
})
}
func getFromDB() {
AddHandler("getAllTags", TagNode, nil, func() []byte {
query := "SELECT tag_id,tag_name from tags"
return jsonify(readTagsFromResultset(database.Query(query)))
})
}
func addToDB() {
var ct struct {
TagName string
}
AddHandler("createTag", TagNode, &ct, func() []byte {
query := "INSERT IGNORE INTO tags (tag_name) VALUES (?)"
return database.SuccessQuery(query, ct.TagName)
})
var at struct {
MovieId int
TagId int
}
AddHandler("addTag", TagNode, &at, func() []byte {
query := "INSERT IGNORE INTO video_tags(tag_id, video_id) VALUES (?,?)"
return database.SuccessQuery(query, at.TagId, at.MovieId)
})
}

233
apiGo/api/Video.go Normal file
View File

@ -0,0 +1,233 @@
package api
import (
"encoding/json"
"fmt"
"net/url"
"openmediacenter/apiGo/api/types"
"openmediacenter/apiGo/database"
"strconv"
)
func AddVideoHandlers() {
getVideoHandlers()
loadVideosHandlers()
addToVideoHandlers()
}
func getVideoHandlers() {
var mrq struct {
Tag int
}
AddHandler("getMovies", VideoNode, &mrq, func() []byte {
var query string
// 1 is the id of the ALL tag
if mrq.Tag != 1 {
query = fmt.Sprintf(`SELECT movie_id,movie_name FROM videos
INNER JOIN video_tags vt on videos.movie_id = vt.video_id
INNER JOIN tags t on vt.tag_id = t.tag_id
WHERE t.tag_id = '%d'
ORDER BY likes DESC, create_date, movie_name`, mrq.Tag)
} else {
query = "SELECT movie_id,movie_name FROM videos ORDER BY create_date DESC, movie_name"
}
result := readVideosFromResultset(database.Query(query))
// jsonify results
str, _ := json.Marshal(result)
return str
})
var rtn struct {
Movieid int
}
AddHandler("readThumbnail", VideoNode, &rtn, func() []byte {
var pic []byte
query := fmt.Sprintf("SELECT thumbnail FROM videos WHERE movie_id='%d'", rtn.Movieid)
err := database.QueryRow(query).Scan(&pic)
if err != nil {
fmt.Printf("the thumbnail of movie id %d couldn't be found", rtn.Movieid)
return nil
}
return pic
})
var grm struct {
Number int
}
AddHandler("getRandomMovies", VideoNode, &grm, func() []byte {
var result struct {
Tags []types.Tag
Videos []types.VideoUnloadedType
}
query := fmt.Sprintf("SELECT movie_id,movie_name FROM videos ORDER BY RAND() LIMIT %d", grm.Number)
result.Videos = readVideosFromResultset(database.Query(query))
var ids string
for i := range result.Videos {
ids += "video_tags.video_id=" + strconv.Itoa(result.Videos[i].MovieId)
if i < len(result.Videos)-1 {
ids += " OR "
}
}
// add the corresponding tags
query = fmt.Sprintf(`SELECT t.tag_name,t.tag_id FROM video_tags
INNER JOIN tags t on video_tags.tag_id = t.tag_id
WHERE %s
GROUP BY t.tag_id`, ids)
rows := database.Query(query)
for rows.Next() {
var tag types.Tag
err := rows.Scan(&tag.TagName, &tag.TagId)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
// append to final array
result.Tags = append(result.Tags, tag)
}
// jsonify results
str, _ := json.Marshal(result)
return str
})
var gsk struct {
KeyWord string
}
AddHandler("getSearchKeyWord", VideoNode, &gsk, func() []byte {
query := fmt.Sprintf(`SELECT movie_id,movie_name FROM videos
WHERE movie_name LIKE '%%%s%%'
ORDER BY likes DESC, create_date DESC, movie_name`, gsk.KeyWord)
result := readVideosFromResultset(database.Query(query))
// jsonify results
str, _ := json.Marshal(result)
return str
})
}
// function to handle stuff for loading specific videos and startdata
func loadVideosHandlers() {
var lv struct {
MovieId int
}
AddHandler("loadVideo", VideoNode, &lv, func() []byte {
query := fmt.Sprintf(`SELECT movie_name,movie_url,movie_id,thumbnail,poster,likes,quality,length
FROM videos WHERE movie_id=%d`, lv.MovieId)
var res types.FullVideoType
var poster []byte
var thumbnail []byte
err := database.QueryRow(query).Scan(&res.MovieName, &res.MovieUrl, &res.MovieId, &thumbnail, &poster, &res.Likes, &res.Quality, &res.Length)
if err != nil {
fmt.Printf("error getting full data list of videoid - %d", lv.MovieId)
fmt.Println(err.Error())
return nil
}
// we ned to urlencode the movieurl
res.MovieUrl = url.PathEscape(res.MovieUrl)
// we need to stringify the pic byte array
res.Poster = string(poster)
// if poster in db is empty we use the thumbnail
if res.Poster == "" {
res.Poster = string(thumbnail)
}
// now add the tags of this video
query = fmt.Sprintf(`SELECT t.tag_id, t.tag_name FROM video_tags
INNER JOIN tags t on video_tags.tag_id = t.tag_id
WHERE video_tags.video_id=%d
GROUP BY t.tag_id`, lv.MovieId)
res.Tags = readTagsFromResultset(database.Query(query))
query = fmt.Sprintf(`SELECT * FROM tags
WHERE tag_id NOT IN (
SELECT video_tags.tag_id FROM video_tags
WHERE video_id=%d)
ORDER BY rand()
LIMIT 5`, lv.MovieId)
res.SuggestedTag = readTagsFromResultset(database.Query(query))
// query the actors corresponding to video
query = fmt.Sprintf(`SELECT a.actor_id, name, thumbnail FROM actors_videos
JOIN actors a on actors_videos.actor_id = a.actor_id
WHERE actors_videos.video_id=%d`, lv.MovieId)
res.Actors = readActorsFromResultset(database.Query(query))
// jsonify results
str, _ := json.Marshal(res)
return str
})
AddHandler("getStartData", VideoNode, nil, func() []byte {
var result types.StartData
// query settings and infotile values
query := `
SELECT (
SELECT COUNT(*) FROM videos
) AS videonr,
(
SELECT COUNT(*) FROM videos
INNER JOIN video_tags vt on videos.movie_id = vt.video_id
INNER JOIN tags t on vt.tag_id = t.tag_id
) AS tagged,
(
SELECT COUNT(*) FROM video_tags as vt
INNER JOIN tags t on vt.tag_id = t.tag_id
WHERE t.tag_name='hd'
) AS hd,
(
SELECT COUNT(*) FROM video_tags as vt
INNER JOIN tags t on vt.tag_id = t.tag_id
WHERE t.tag_name='fullhd'
) AS fullhd,
(
SELECT COUNT(*) FROM video_tags as vt
INNER JOIN tags t on vt.tag_id = t.tag_id
WHERE t.tag_name='lowquality'
) AS lq,
(
SELECT COUNT(*) as nr FROM tags
) as tags
LIMIT 1`
_ = database.QueryRow(query).Scan(&result.VideoNr, &result.Tagged, &result.HDNr, &result.FullHdNr, &result.SDNr, &result.DifferentTags)
// jsonify results
str, _ := json.Marshal(result)
return str
})
}
func addToVideoHandlers() {
var al struct {
MovieId int
}
AddHandler("addLike", VideoNode, &al, func() []byte {
query := fmt.Sprintf("update videos set likes = likes + 1 where movie_id = %d", al.MovieId)
return database.SuccessQuery(query)
})
var dv struct {
MovieId int
}
AddHandler("deleteVideo", VideoNode, &dv, func() []byte {
query := fmt.Sprintf("DELETE FROM videos WHERE movie_id=%d", dv.MovieId)
return database.SuccessQuery(query)
})
}

56
apiGo/api/types/Types.go Normal file
View File

@ -0,0 +1,56 @@
package types
type VideoUnloadedType struct {
MovieId int
MovieName string
}
type FullVideoType struct {
MovieName string
MovieId int
MovieUrl string
Poster string
Likes int
Quality int
Length int
Tags []Tag
SuggestedTag []Tag
Actors []Actor
}
type Tag struct {
TagName string
TagId int
}
type Actor struct {
ActorId int
Name string
Thumbnail string
}
type StartData struct {
VideoNr int
FullHdNr int
HDNr int
SDNr int
DifferentTags int
Tagged int
}
type SettingsType struct {
VideoPath string
EpisodePath string
MediacenterName string
Password string
PasswordEnabled bool
TMDBGrabbing bool
DarkMode bool
VideoNr int
DBSize float32
DifferentTags int
TagsAdded int
PathPrefix string
}

136
apiGo/database/Database.go Normal file
View File

@ -0,0 +1,136 @@
package database
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"openmediacenter/apiGo/api/types"
)
var db *sql.DB
var DBName string
// store the command line parameter for Videoprefix
var SettingsVideoPrefix = ""
type DatabaseConfig struct {
DBHost string
DBPort int
DBUser string
DBPassword string
DBName string
}
func InitDB(dbconf *DatabaseConfig) {
DBName = dbconf.DBName
// Open up our database connection.
var err error
db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", dbconf.DBUser, dbconf.DBPassword, dbconf.DBHost, dbconf.DBPort, dbconf.DBName))
// if there is an error opening the connection, handle it
if err != nil {
fmt.Printf("Error while connecting to database! - %s\n", err.Error())
}
if db != nil {
ping := db.Ping()
if ping != nil {
fmt.Printf("Error while connecting to database! - %s\n", ping.Error())
}
}
}
func Query(query string, args ...interface{}) *sql.Rows {
// perform a db.Query insert
res, err := db.Query(query, args...)
// if there is an error inserting, handle it
if err != nil {
fmt.Printf("Error while requesting data! - %s\n", err.Error())
}
return res
}
func QueryRow(SQL string, args ...interface{}) *sql.Row {
return db.QueryRow(SQL, args...)
}
// edit something in the DB and give only an error response
func Edit(query string, args ...interface{}) error {
_, err := db.Exec(query, args...)
return err
}
// insert/edit a query and return last insert id
func Insert(query string, args ...interface{}) (error, int64) {
resp, err := db.Exec(query, args...)
var id int64 = 0
if err == nil {
id, err = resp.LastInsertId()
}
return err, id
}
func SuccessQuery(query string, args ...interface{}) []byte {
return ManualSuccessResponse(Edit(query, args...))
}
func ManualSuccessResponse(err error) []byte {
if err == nil {
return []byte(`{"result":"success"}`)
} else {
return []byte(fmt.Sprintf(`{"result":"%s"}`, err.Error()))
}
}
func Close() {
db.Close()
}
func GetSettings() types.SettingsType {
var result types.SettingsType
// query settings and infotile values
query := fmt.Sprintf(`
SELECT (
SELECT COUNT(*)
FROM videos
) AS videonr,
(
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS Size
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = '%s'
GROUP BY table_schema
) AS dbsize,
(
SELECT COUNT(*)
FROM tags
) AS difftagnr,
(
SELECT COUNT(*)
FROM video_tags
) AS tagsadded,
video_path, episode_path, password, mediacenter_name, TMDB_grabbing, DarkMode
FROM settings
LIMIT 1`, DBName)
var DarkMode int
var TMDBGrabbing int
err := QueryRow(query).Scan(&result.VideoNr, &result.DBSize, &result.DifferentTags, &result.TagsAdded,
&result.VideoPath, &result.EpisodePath, &result.Password, &result.MediacenterName, &TMDBGrabbing, &DarkMode)
if err != nil {
fmt.Println(err.Error())
}
result.TMDBGrabbing = TMDBGrabbing != 0
result.PasswordEnabled = result.Password != "-1"
result.DarkMode = DarkMode != 0
result.PathPrefix = SettingsVideoPrefix
return result
}

5
apiGo/go.mod Normal file
View File

@ -0,0 +1,5 @@
module openmediacenter/apiGo
go 1.16
require github.com/go-sql-driver/mysql v1.5.0

2
apiGo/go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=

53
apiGo/main.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"flag"
"fmt"
"openmediacenter/apiGo/api"
"openmediacenter/apiGo/database"
)
func main() {
fmt.Println("init OpenMediaCenter server")
db, verbose, pathPrefix := handleCommandLineArguments()
// todo some verbosity logger or sth
fmt.Printf("Use verbose output: %t\n", verbose)
fmt.Printf("Videopath prefix: %s\n", *pathPrefix)
// set pathprefix in database settings object
database.SettingsVideoPrefix = *pathPrefix
database.InitDB(db)
defer database.Close()
api.AddVideoHandlers()
api.AddSettingsHandlers()
api.AddTagHandlers()
api.AddActorsHandlers()
api.ServerInit(8081)
}
func handleCommandLineArguments() (*database.DatabaseConfig, bool, *string) {
dbhostPtr := flag.String("DBHost", "127.0.0.1", "database host name")
dbPortPtr := flag.Int("DBPort", 3306, "database port")
dbUserPtr := flag.String("DBUser", "mediacenteruser", "database username")
dbPassPtr := flag.String("DBPassword", "mediapassword", "database username")
dbNamePtr := flag.String("DBName", "mediacenter", "database name")
verbosePtr := flag.Bool("v", true, "Verbose log output")
pathPrefix := flag.String("ReindexPrefix", "/var/www/openmediacenter", "Prefix path for videos to reindex")
flag.Parse()
return &database.DatabaseConfig{
DBHost: *dbhostPtr,
DBPort: *dbPortPtr,
DBUser: *dbUserPtr,
DBPassword: *dbPassPtr,
DBName: *dbNamePtr,
}, *verbosePtr, pathPrefix
}

View File

@ -0,0 +1 @@
package videoparser

View File

@ -0,0 +1,294 @@
package videoparser
import (
"database/sql"
"encoding/base64"
"encoding/json"
"fmt"
"openmediacenter/apiGo/api/types"
"openmediacenter/apiGo/database"
"openmediacenter/apiGo/videoparser/tmdb"
"os/exec"
"regexp"
"strconv"
)
var mSettings types.SettingsType
var mExtDepsAvailable *ExtDependencySupport
// default Tag ids
const (
FullHd = 2
Hd = 4
LowQuality = 3
)
type ExtDependencySupport struct {
FFMpeg bool
MediaInfo bool
}
type VideoAttributes struct {
Duration float32
FileSize uint
Width uint
}
func ReIndexVideos(path []string, sett types.SettingsType) {
mSettings = sett
// check if the extern dependencies are available
mExtDepsAvailable = checkExtDependencySupport()
fmt.Printf("FFMPEG support: %t\n", mExtDepsAvailable.FFMpeg)
fmt.Printf("MediaInfo support: %t\n", mExtDepsAvailable.MediaInfo)
for _, s := range path {
processVideo(s)
}
AppendMessageBuffer("reindex finished successfully!")
contentAvailable = false
fmt.Println("Reindexing finished!")
}
func processVideo(fileNameOrig string) {
fmt.Printf("Processing %s video-", fileNameOrig)
// match the file extension
r, _ := regexp.Compile(`\.[a-zA-Z0-9]+$`)
fileName := r.ReplaceAllString(fileNameOrig, "")
year, fileName := matchYear(fileName)
// now we should look if this video already exists in db
query := "SELECT * FROM videos WHERE movie_name = ?"
err := database.QueryRow(query, fileName).Scan()
if err == sql.ErrNoRows {
fmt.Printf("The Video %s does't exist! Adding it to database.\n", fileName)
addVideo(fileName, fileNameOrig, year)
} else {
fmt.Println(" :existing!")
}
}
// add a video to the database
func addVideo(videoName string, fileName string, year int) {
var ppic *string
var poster *string
var tmdbData *tmdb.VideoTMDB
var err error
// initialize defaults
vidAtr := &VideoAttributes{
Duration: 0,
FileSize: 0,
Width: 0,
}
if mExtDepsAvailable.FFMpeg {
ppic, err = parseFFmpegPic(fileName)
if err != nil {
fmt.Printf("FFmpeg error occured: %s\n", err.Error())
} else {
fmt.Println("successfully extracted thumbnail!!")
}
}
if mExtDepsAvailable.MediaInfo {
atr := getVideoAttributes(fileName)
if atr != nil {
vidAtr = atr
}
}
// if TMDB grabbing is enabled serach in api for video...
if mSettings.TMDBGrabbing {
tmdbData = tmdb.SearchVideo(videoName, year)
if tmdbData != nil {
// reassign parsed pic as poster
poster = ppic
// and tmdb pic as thumbnail
ppic = &tmdbData.Thumbnail
}
}
query := `INSERT INTO videos(movie_name,movie_url,poster,thumbnail,quality,length) VALUES (?,?,?,?,?,?)`
err, insertId := database.Insert(query, videoName, fileName, poster, ppic, vidAtr.Width, vidAtr.Duration)
if err != nil {
fmt.Printf("Failed to insert video into db: %s\n", err.Error())
return
}
// add default tags
if vidAtr.Width != 0 {
insertSizeTag(vidAtr.Width, uint(insertId))
}
// add tmdb tags
if mSettings.TMDBGrabbing && tmdbData != nil {
insertTMDBTags(tmdbData.GenreIds, insertId)
}
AppendMessageBuffer(fmt.Sprintf("%s - added!", videoName))
}
func matchYear(fileName string) (int, string) {
r, _ := regexp.Compile(`\([0-9]{4}?\)`)
years := r.FindAllString(fileName, -1)
if len(years) == 0 {
return -1, fileName
}
year, err := strconv.Atoi(years[len(years)-1])
if err != nil {
return -1, fileName
}
// cut out year from filename
return year, r.ReplaceAllString(fileName, "")
}
// parse the thumbail picture from video file
func parseFFmpegPic(fileName string) (*string, error) {
app := "ffmpeg"
cmd := exec.Command(app,
"-hide_banner",
"-loglevel", "panic",
"-ss", "00:04:00",
"-i", mSettings.VideoPath+fileName,
"-vframes", "1",
"-q:v", "2",
"-f", "singlejpeg",
"pipe:1")
stdout, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
fmt.Println(string(err.(*exec.ExitError).Stderr))
return nil, err
}
backpic64 := "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(stdout)
return &backpic64, nil
}
func getVideoAttributes(fileName string) *VideoAttributes {
app := "mediainfo"
arg0 := mSettings.VideoPath + fileName
arg1 := "--Output=JSON"
cmd := exec.Command(app, arg1, "-f", arg0)
stdout, err := cmd.Output()
var t struct {
Media struct {
Track []struct {
Duration string
FileSize string
Width string
}
}
}
err = json.Unmarshal(stdout, &t)
if err != nil {
fmt.Println(err.Error())
return nil
}
duration, err := strconv.ParseFloat(t.Media.Track[0].Duration, 32)
filesize, err := strconv.Atoi(t.Media.Track[0].FileSize)
width, err := strconv.Atoi(t.Media.Track[1].Width)
ret := VideoAttributes{
Duration: float32(duration),
FileSize: uint(filesize),
Width: uint(width),
}
return &ret
}
func AppendMessageBuffer(message string) {
messageBuffer = append(messageBuffer, message)
}
// ext dependency support check
func checkExtDependencySupport() *ExtDependencySupport {
var extDepsAvailable ExtDependencySupport
extDepsAvailable.FFMpeg = commandExists("ffmpeg")
extDepsAvailable.MediaInfo = commandExists("mediainfo")
return &extDepsAvailable
}
// check if a specific system command is available
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
// insert the default size tags to corresponding video
func insertSizeTag(width uint, videoId uint) {
var tagType uint
if width >= 1080 {
tagType = FullHd
} else if width >= 720 {
tagType = Hd
} else {
tagType = LowQuality
}
query := fmt.Sprintf("INSERT INTO video_tags(video_id,tag_id) VALUES (%d,%d)", videoId, tagType)
err := database.Edit(query)
if err != nil {
fmt.Printf("Eror occured while adding default Tag: %s\n", err.Error())
}
}
// insert id array of tmdb geners to database
func insertTMDBTags(ids []int, videoId int64) {
genres := tmdb.GetGenres()
for _, id := range ids {
var idGenre *tmdb.TMDBGenre
for _, genre := range *genres {
if genre.Id == id {
idGenre = &genre
break
}
}
// skip tag if name couldn't be found
if idGenre == nil {
continue
}
// now we check if the tag we want to add already exists
tagId := createTagToDB(idGenre.Name)
// now we add the tag
query := fmt.Sprintf("INSERT INTO video_tags(video_id,tag_id) VALUES (%d,%d)", videoId, tagId)
_ = database.Edit(query)
}
}
// returns id of tag or creates it if not existing
func createTagToDB(tagName string) int64 {
query := fmt.Sprintf("SELECT tag_id FROM tags WHERE tag_name = %s", tagName)
var id int64
err := database.QueryRow(query).Scan(&id)
if err == sql.ErrNoRows {
// tag doesn't exist -- add it
query = fmt.Sprintf("INSERT INTO tags (tag_name) VALUES (%s)", tagName)
err, id = database.Insert(query)
}
return id
}

View File

@ -0,0 +1,70 @@
package videoparser
import (
"fmt"
"openmediacenter/apiGo/database"
"os"
"path/filepath"
"strings"
)
var messageBuffer []string
var contentAvailable = false
type StatusMessage struct {
Messages []string
ContentAvailable bool
}
func StartReindex() bool {
messageBuffer = []string{}
contentAvailable = true
fmt.Println("starting reindex..")
mSettings := database.GetSettings()
// add the path prefix to videopath
mSettings.VideoPath = mSettings.PathPrefix + mSettings.VideoPath
// check if path even exists
if _, err := os.Stat(mSettings.VideoPath); os.IsNotExist(err) {
fmt.Println("Reindex path doesn't exist!")
return false
}
var files []string
err := filepath.Walk(mSettings.VideoPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err.Error())
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".mp4") {
files = append(files, info.Name())
}
return nil
})
if err != nil {
fmt.Println(err.Error())
}
// start reindex process
AppendMessageBuffer("Starting Reindexing!")
go ReIndexVideos(files, mSettings)
return true
}
func GetStatusMessage() *StatusMessage {
msg := StatusMessage{
Messages: messageBuffer,
ContentAvailable: contentAvailable,
}
messageBuffer = []string{}
return &msg
}
func StartCleanup() {
// todo start cleanup
}

View File

@ -0,0 +1,144 @@
package tmdb
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"regexp"
)
const apiKey = "9fd90530b11447f5646f8e6fb4733fb4"
const baseUrl = "https://api.themoviedb.org/3/"
const pictureBase = "https://image.tmdb.org/t/p/w500"
type VideoTMDB struct {
Thumbnail string
Overview string
Title string
GenreIds []int
}
type tmdbVidResult struct {
Poster_path string
Adult bool
Overview string
Release_date string
Genre_ids []int
Id int
Original_title string
Original_language string
Title string
Backdrop_path string
Popularity int
Vote_count int
Video bool
Vote_average int
}
type TMDBGenre struct {
Id int
Name string
}
func SearchVideo(MovieName string, year int) *VideoTMDB {
url := fmt.Sprintf("%ssearch/movie?api_key=%s&query=%s", baseUrl, apiKey, MovieName)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err.Error())
return nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return nil
}
var t struct {
Results []tmdbVidResult
}
err = json.Unmarshal(body, &t)
fmt.Println(len(t.Results))
var tmdbVid tmdbVidResult
if year != -1 {
for _, result := range t.Results {
r, _ := regexp.Compile(fmt.Sprintf(`^%d-[0-9]{2}?-[0-9]{2}?$`, year))
if r.MatchString(result.Release_date) {
tmdbVid = result
// continue parsing
goto cont
}
}
// if there is no match use first one
tmdbVid = t.Results[0]
} else {
tmdbVid = t.Results[0]
}
// continue label
cont:
thumbnail := fetchPoster(tmdbVid)
result := VideoTMDB{
Thumbnail: *thumbnail,
Overview: tmdbVid.Overview,
Title: tmdbVid.Title,
GenreIds: tmdbVid.Genre_ids,
}
return &result
}
func fetchPoster(vid tmdbVidResult) *string {
url := fmt.Sprintf("%s%s", pictureBase, vid.Poster_path)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err.Error())
return nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return nil
}
backpic64 := "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(body)
return &backpic64
}
var tmdbGenres *[]TMDBGenre
func fetchGenres() *[]TMDBGenre {
url := fmt.Sprintf("%sgenre/movie/list?api_key=%s", baseUrl, apiKey)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err.Error())
return nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return nil
}
var t []TMDBGenre
err = json.Unmarshal(body, &t)
return &t
}
func GetGenres() *[]TMDBGenre {
// if generes are nil fetch them once
if tmdbGenres == nil {
tmdbGenres = fetchGenres()
}
return tmdbGenres
}