11package api
22
33import (
4+ "encoding/base64"
45 "encoding/json"
56 "fmt"
7+ "io/ioutil"
68 "math"
79 "net/http"
10+ "path/filepath"
811 "sort"
912 "strconv"
1013 "strings"
14+ "sync"
1115
1216 "github.com/dustin/go-humanize"
1317 "github.com/emicklei/go-restful"
1418 restfulspec "github.com/emicklei/go-restful-openapi"
1519 "github.com/markphelps/optional"
1620 "github.com/xbapps/xbvr/pkg/config"
1721 "github.com/xbapps/xbvr/pkg/models"
22+ "github.com/xbapps/xbvr/pkg/tasks"
1823 "golang.org/x/crypto/bcrypt"
1924)
2025
@@ -47,6 +52,10 @@ type HeresphereVideo struct {
4752 Scripts []HeresphereScript `json:"scripts,omitempty"`
4853 Tags []HeresphereTag `json:"tags,omitempty"`
4954 Media []HeresphereMedia `json:"media"`
55+ WriteFavorite bool `json:"writeFavorite"`
56+ WriteRating bool `json:"writeRating"`
57+ WriteTags bool `json:"writeTags"`
58+ WriteHSP bool `json:"writeHSP"`
5059}
5160
5261type HeresphereScript struct {
@@ -55,10 +64,10 @@ type HeresphereScript struct {
5564}
5665
5766type HeresphereTag struct {
58- Name string `json:"name"`
59- StartMilliseconds int `json:"start,omitempty"`
60- EndMilliseconds int `json:"end,omitempty"`
61- Track * int `json:"track,omitempty"`
67+ Name string `json:"name"`
68+ StartMilliseconds float64 `json:"start,omitempty"`
69+ EndMilliseconds float64 `json:"end,omitempty"`
70+ Track * int `json:"track,omitempty"`
6271}
6372
6473type HeresphereMedia struct {
@@ -75,8 +84,13 @@ type HeresphereSource struct {
7584}
7685
7786type HereSphereAuthRequest struct {
78- Username string `json:"username"`
79- Password string `json:"password"`
87+ Username string `json:"username"`
88+ Password string `json:"password"`
89+ Rating * float64 `json:"rating"`
90+ IsFavorite * bool `json:"isFavorite"`
91+ Hsp * string `json:"hsp"`
92+ Tags * []HeresphereTag `json:"tags"`
93+ DeleteFiles * bool `json:"deleteFile"`
8094}
8195
8296func HeresphereAuthFilter (req * restful.Request , resp * restful.Response , chain * restful.FilterChain ) {
@@ -157,6 +171,12 @@ func (i HeresphereResource) getHeresphereFile(req *restful.Request, resp *restfu
157171 return
158172 }
159173
174+ var requestData HereSphereAuthRequest
175+ if err := json .NewDecoder (req .Request .Body ).Decode (& requestData ); err != nil {
176+ log .Errorf ("Error decoding heresphere api POST request: %v" , err )
177+ return
178+ }
179+
160180 db , _ := models .GetDB ()
161181 defer db .Close ()
162182
@@ -199,6 +219,9 @@ func (i HeresphereResource) getHeresphereFile(req *restful.Request, resp *restfu
199219 DurationMilliseconds : uint (file .VideoDuration * 1000 ),
200220 Media : media ,
201221 }
222+ if requestData .DeleteFiles != nil && config .Config .Interfaces .Heresphere .AllowFileDeletes {
223+ removeFileByFileId (file .ID )
224+ }
202225
203226 resp .WriteHeaderAndEntity (http .StatusOK , video )
204227}
@@ -208,6 +231,12 @@ func (i HeresphereResource) getHeresphereScene(req *restful.Request, resp *restf
208231 return
209232 }
210233
234+ var requestData HereSphereAuthRequest
235+ if err := json .NewDecoder (req .Request .Body ).Decode (& requestData ); err != nil {
236+ log .Errorf ("Error decoding heresphere api POST request: %v" , err )
237+ return
238+ }
239+
211240 sceneID := req .PathParameter ("scene-id" )
212241 if sceneID == "" {
213242 return
@@ -220,12 +249,22 @@ func (i HeresphereResource) getHeresphereScene(req *restful.Request, resp *restf
220249 err := db .Preload ("Cast" ).
221250 Preload ("Tags" ).
222251 Preload ("Cuepoints" ).
252+ Preload ("Files" ).
223253 Where ("id = ?" , sceneID ).First (& scene ).Error
224254 if err != nil {
225255 log .Error (err )
226256 return
227257 }
228258
259+ var videoFiles []models.File
260+ videoFiles , err = scene .GetVideoFiles ()
261+ if err != nil {
262+ log .Error (err )
263+ return
264+ }
265+
266+ ProcessHeresphereUpdates (& scene , requestData , videoFiles [0 ])
267+
229268 features := make (map [string ]bool , 30 )
230269 addFeatureTag := func (feature string ) {
231270 if ! features [feature ] {
@@ -235,12 +274,6 @@ func (i HeresphereResource) getHeresphereScene(req *restful.Request, resp *restf
235274
236275 var media []HeresphereMedia
237276
238- var videoFiles []models.File
239- videoFiles , err = scene .GetVideoFiles ()
240- if err != nil {
241- log .Error (err )
242- return
243- }
244277 videoLength := float64 (scene .Duration )
245278
246279 for i , file := range videoFiles {
@@ -302,8 +335,8 @@ func (i HeresphereResource) getHeresphereScene(req *restful.Request, resp *restf
302335 }
303336 tags = append (tags , HeresphereTag {
304337 Name : scene .Cuepoints [i ].Name ,
305- StartMilliseconds : start ,
306- EndMilliseconds : end ,
338+ StartMilliseconds : float64 ( start ) ,
339+ EndMilliseconds : float64 ( end ) ,
307340 Track : & track ,
308341 })
309342 }
@@ -475,15 +508,180 @@ func (i HeresphereResource) getHeresphereScene(req *restful.Request, resp *restf
475508 Scripts : heresphereScriptFiles ,
476509 Tags : tags ,
477510 Media : media ,
511+ WriteFavorite : config .Config .Interfaces .Heresphere .AllowFavoriteUpdates ,
512+ WriteRating : config .Config .Interfaces .Heresphere .AllowRatingUpdates ,
513+ WriteTags : config .Config .Interfaces .Heresphere .AllowTagUpdates || config .Config .Interfaces .Heresphere .AllowCuepointUpdates || config .Config .Interfaces .Heresphere .AllowWatchlistUpdates ,
514+ WriteHSP : config .Config .Interfaces .Heresphere .AllowHspData ,
478515 }
479516
480517 if scene .HasVideoPreview {
481518 video .ThumbnailVideo = fmt .Sprintf ("http://%v/api/dms/preview/%v" , req .Request .Host , scene .SceneID )
482519 }
483-
484520 resp .WriteHeaderAndEntity (http .StatusOK , video )
485521}
486522
523+ var lockHeresphereUpdates sync.Mutex
524+
525+ func ProcessHeresphereUpdates (scene * models.Scene , requestData HereSphereAuthRequest , videoFile models.File ) {
526+
527+ db , _ := models .GetDB ()
528+ defer db .Close ()
529+
530+ updateReqd := false
531+ if requestData .IsFavorite != nil && * requestData .IsFavorite != scene .Favourite && config .Config .Interfaces .Heresphere .AllowFavoriteUpdates {
532+ scene .Favourite = * requestData .IsFavorite
533+ updateReqd = true
534+ }
535+ if requestData .Rating != nil && * requestData .Rating != scene .StarRating && config .Config .Interfaces .Heresphere .AllowRatingUpdates {
536+ scene .StarRating = * requestData .Rating
537+ updateReqd = true
538+ }
539+
540+ if requestData .Tags != nil && (config .Config .Interfaces .Heresphere .AllowTagUpdates || config .Config .Interfaces .Heresphere .AllowCuepointUpdates || config .Config .Interfaces .Heresphere .AllowWatchlistUpdates ) {
541+ // need lock, heresphere can send a second post too soon
542+ lockHeresphereUpdates .Lock ()
543+ defer lockHeresphereUpdates .Unlock ()
544+ }
545+ if requestData .Tags != nil && config .Config .Interfaces .Heresphere .AllowTagUpdates {
546+ var newTags []string
547+
548+ // need to reread the tags, to handle muti threading issues and the scene record may have changed
549+ // just preload the tags, preload all associations and the scene, does not reread the tags?, so just get them and update the scene
550+ var tmp models.Scene
551+ db .Preload ("Tags" ).Where ("id = ?" , scene .ID ).First (& tmp )
552+ scene .Tags = tmp .Tags
553+
554+ for _ , tag := range * requestData .Tags {
555+ if strings .HasPrefix (strings .ToLower (tag .Name ), "category:" ) {
556+ newTags = append (newTags , tag .Name [9 :])
557+ }
558+ }
559+ ProcessTagChanges (scene , & newTags , db )
560+ updateReqd = true
561+ }
562+
563+ if requestData .Tags != nil && config .Config .Interfaces .Heresphere .AllowWatchlistUpdates {
564+ // need to reread the tags, to handle muti threading issues and the scene record may have changed
565+ // just preload the tags, preload all associations and the scene, does not reread the tags?, so just get them and update the scene
566+ var tmp models.Scene
567+ db .Preload ("Tags" ).Where ("id = ?" , scene .ID ).First (& tmp )
568+ scene .Tags = tmp .Tags
569+
570+ watchlist := false
571+ for _ , tag := range * requestData .Tags {
572+ if strings .HasPrefix (strings .ToLower (tag .Name ), "feature:watchlist" ) {
573+ watchlist = true
574+ }
575+ }
576+ if scene .Watchlist != watchlist {
577+ scene .Watchlist = watchlist
578+ updateReqd = true
579+ }
580+ }
581+
582+ if requestData .Tags != nil && config .Config .Interfaces .Heresphere .AllowCuepointUpdates {
583+ // need to reread the cuepoints, to handle muti threading issues and the scene record may have changed
584+ // just preload the cuepoint, preload all associations and the scene, does not reread the cuepoint?, so just get them and update the scene
585+ var tmp models.Scene
586+ db .Preload ("Cuepoints" ).Where ("id = ?" , scene .ID ).First (& tmp )
587+ scene .Cuepoints = tmp .Cuepoints
588+
589+ var replacementCuepoints []models.SceneCuepoint
590+ endpos := findEndPos (requestData )
591+ firstTrack := findTheMainTrack (requestData )
592+ for _ , tag := range * requestData .Tags {
593+ if ! strings .Contains (tag .Name , ":" ) {
594+ if * tag .Track == firstTrack {
595+ replacementCuepoints = append (replacementCuepoints , models.SceneCuepoint {SceneID : scene .ID , TimeStart : float64 (tag .StartMilliseconds ) / 1000 , Name : tag .Name })
596+ } else {
597+ //allow for multi track, merge into the main cuepoint name
598+ if tag .StartMilliseconds > 0 || tag .EndMilliseconds < endpos {
599+ for idx , newtag := range replacementCuepoints {
600+ // allow 5 seconds lewway to align manually entered tags
601+ if math .Abs ((newtag .TimeStart )- tag .StartMilliseconds / 1000 ) < 5 {
602+ replacementCuepoints [idx ].Name = tag .Name + "-" + replacementCuepoints [idx ].Name
603+ }
604+ }
605+ }
606+ }
607+ }
608+ }
609+ db .Model (& scene ).Association ("Cuepoints" ).Replace (& replacementCuepoints )
610+
611+ updateReqd = true
612+ }
613+
614+ if requestData .DeleteFiles != nil && config .Config .Interfaces .Heresphere .AllowFileDeletes {
615+ for _ , sceneFile := range scene .Files {
616+ removeFileByFileId (sceneFile .ID )
617+ }
618+ }
619+
620+ if requestData .Hsp != nil && config .Config .Interfaces .Heresphere .AllowHspData {
621+ hspContent , err := base64 .StdEncoding .DecodeString (* requestData .Hsp )
622+ if err != nil {
623+ log .Error ("Error decoding heresphere hsp data %v" , err )
624+ }
625+
626+ fName := filepath .Join (scene .Files [0 ].Path , strings .TrimSuffix (scene .Files [0 ].Filename , filepath .Ext (videoFile .Filename ))+ ".hsp" )
627+ ioutil .WriteFile (fName , hspContent , 0644 )
628+
629+ tasks .ScanLocalHspFile (fName , videoFile .VolumeID , scene .ID )
630+ }
631+
632+ if updateReqd {
633+ scene .Save ()
634+ }
635+ }
636+ func findTheMainTrack (requestData HereSphereAuthRequest ) int {
637+ // 99% of the time we want Track 0, but the user may have deleted and added whole track
638+
639+ // find the max duration
640+ endpos := findEndPos (requestData )
641+ for _ , tag := range * requestData .Tags {
642+ if endpos < tag .EndMilliseconds {
643+ endpos = tag .EndMilliseconds
644+ }
645+ }
646+
647+ // find the best track
648+ likelyTrack := 9999
649+ alternateTrack := 9999
650+
651+ for _ , tag := range * requestData .Tags {
652+ if (tag .StartMilliseconds > 0 || tag .EndMilliseconds < endpos ) && ! strings .Contains (tag .Name , ":" ) {
653+ return * tag .Track
654+ }
655+
656+ if (tag .StartMilliseconds > 0 || tag .EndMilliseconds < endpos ) && likelyTrack > * tag .Track {
657+ likelyTrack = * tag .Track
658+ }
659+ if ! strings .Contains (tag .Name , ":" ) && alternateTrack > * tag .Track {
660+ alternateTrack = * tag .Track
661+ }
662+ }
663+
664+ if likelyTrack < 9999 {
665+ return likelyTrack
666+ }
667+
668+ if alternateTrack < 9999 {
669+ return likelyTrack
670+ }
671+
672+ return - 1
673+ }
674+ func findEndPos (requestData HereSphereAuthRequest ) float64 {
675+ // find the max duration
676+ endpos := float64 (0 )
677+ for _ , tag := range * requestData .Tags {
678+ if endpos < tag .EndMilliseconds {
679+ endpos = tag .EndMilliseconds
680+ }
681+ }
682+ return endpos
683+ }
684+
487685func (i HeresphereResource ) getHeresphereLibrary (req * restful.Request , resp * restful.Response ) {
488686 if ! config .Config .Interfaces .DeoVR .Enabled {
489687 return
0 commit comments