Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add funcsript speed features #622

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pkg/api/deovr.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,11 @@ func (i DeoVRResource) getDeoScene(req *restful.Request, resp *restful.Response)
thumbnailURL := session.DeoRequestHost + "/img/700x/" + strings.Replace(scene.CoverURL, "://", ":/", -1)

if scene.IsScripted {
title = scene.GetFunscriptTitle()
if config.Config.Interfaces.DeoVR.FunscriptSpeeds {
title = fmt.Sprintf("%d - %s", scene.FunscriptSpeed, scene.Title)
} else {
title = scene.GetFunscriptTitle()
}
if config.Config.Interfaces.DeoVR.RenderHeatmaps {
thumbnailURL = session.DeoRequestHost + "/imghm/" + fmt.Sprint(scene.ID) + "/" + strings.Replace(scene.CoverURL, "://", ":/", -1)
}
Expand Down
14 changes: 8 additions & 6 deletions pkg/api/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ type RequestSaveOptionsDLNA struct {
}

type RequestSaveOptionsDeoVR struct {
Enabled bool `json:"enabled"`
AuthEnabled bool `json:"auth_enabled"`
Username string `json:"username"`
Password string `json:"password"`
RemoteEnabled bool `json:"remote_enabled"`
RenderHeatmaps bool `json:"render_heatmaps"`
Enabled bool `json:"enabled"`
AuthEnabled bool `json:"auth_enabled"`
Username string `json:"username"`
Password string `json:"password"`
RemoteEnabled bool `json:"remote_enabled"`
RenderHeatmaps bool `json:"render_heatmaps"`
FunscriptSpeeds bool `json:"funscript_speeds"`
}

type RequestSaveOptionsPreviews struct {
Expand Down Expand Up @@ -231,6 +232,7 @@ func (i ConfigResource) saveOptionsDeoVR(req *restful.Request, resp *restful.Res
config.Config.Interfaces.DeoVR.Enabled = r.Enabled
config.Config.Interfaces.DeoVR.AuthEnabled = r.AuthEnabled
config.Config.Interfaces.DeoVR.RenderHeatmaps = r.RenderHeatmaps
config.Config.Interfaces.DeoVR.FunscriptSpeeds = r.FunscriptSpeeds
config.Config.Interfaces.DeoVR.RemoteEnabled = r.RemoteEnabled
config.Config.Interfaces.DeoVR.Username = r.Username
if r.Password != config.Config.Interfaces.DeoVR.Password && r.Password != "" {
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/scenes.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ func (i SceneResource) selectScript(req *restful.Request, resp *restful.Response

var scene models.Scene
var files []models.File
var newFunscriptSpeed int
db, _ := models.GetDB()
err = scene.GetIfExistByPK(uint(sceneId))
if err == nil {
Expand All @@ -421,6 +422,7 @@ func (i SceneResource) selectScript(req *restful.Request, resp *restful.Response
if file.ID == r.FileID && !file.IsSelectedScript {
file.IsSelectedScript = true
file.Save()
newFunscriptSpeed = file.FunscriptSpeed
} else if file.ID != r.FileID && file.IsSelectedScript {
file.IsSelectedScript = false
file.Save()
Expand All @@ -429,6 +431,10 @@ func (i SceneResource) selectScript(req *restful.Request, resp *restful.Response
}
err = scene.GetIfExistByPK(uint(sceneId))
}
if newFunscriptSpeed > 0 {
scene.FunscriptSpeed = newFunscriptSpeed
scene.Save()
}
db.Close()

resp.WriteHeaderAndEntity(http.StatusOK, scene)
Expand Down
13 changes: 7 additions & 6 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ type ObjectConfig struct {
AllowedIP []string `default:"[]" json:"allowedIp"`
} `json:"dlna"`
DeoVR struct {
Enabled bool `default:"true" json:"enabled"`
AuthEnabled bool `default:"false" json:"auth_enabled"`
RenderHeatmaps bool `default:"false" json:"render_heatmaps"`
RemoteEnabled bool `default:"false" json:"remote_enabled"`
Username string `default:"" json:"username"`
Password string `default:"" json:"password"`
Enabled bool `default:"true" json:"enabled"`
AuthEnabled bool `default:"false" json:"auth_enabled"`
RenderHeatmaps bool `default:"false" json:"render_heatmaps"`
FunscriptSpeeds bool `default:"false" json:"funscript_speeds"`
RemoteEnabled bool `default:"false" json:"remote_enabled"`
Username string `default:"" json:"username"`
Password string `default:"" json:"password"`
} `json:"deovr"`
} `json:"interfaces"`
Library struct {
Expand Down
45 changes: 45 additions & 0 deletions pkg/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/jinzhu/gorm"
"github.com/markphelps/optional"
"github.com/mozillazg/go-slugify"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/xbapps/xbvr/pkg/common"
"github.com/xbapps/xbvr/pkg/models"
Expand All @@ -38,6 +39,8 @@ type RequestSceneList struct {
Volume optional.Int `json:"volume"`
Released optional.String `json:"releaseMonth"`
Sort optional.String `json:"sort"`
MinFunscript optional.Int `json:"minFunscript"`
MaxFunscript optional.Int `json:"maxFunscript"`
}

func (i *RequestSceneList) ToJSON() string {
Expand Down Expand Up @@ -573,6 +576,48 @@ func Migrate() {
// since scenes have new IDs, we need to re-index them
tasks.SearchIndex()

return nil
},
},
{
ID: "0028-file-funscript-speed",
Migrate: func(tx *gorm.DB) error {
type File struct {
FunscriptSpeed int `json:"funscript_speed" gorm:"default:0"`
}
err := tx.AutoMigrate(File{}).Error
if err != nil {
return err
}
tlog := common.Log.WithFields(logrus.Fields{"task": "migrate"})
tasks.GenerateFunscriptSpeeds(tlog)
return nil
},
},
{
ID: "0029-scene-funscript-speed",
Migrate: func(tx *gorm.DB) error {
type Scene struct {
FunscriptSpeed int `json:"funscript_speed" gorm:"default:0"`
}
err := tx.AutoMigrate(Scene{}).Error
if err != nil {
return err
}

var scenes []models.Scene

db.Model(&models.Scene{}).Find(&scenes)

tlog := common.Log.WithFields(logrus.Fields{"task": "migrate"})

for i := range scenes {
scenes[i].UpdateStatus()
if (i % 70) == 0 {
tlog.Infof("Update status of Scenes (%v/%v)", i+1, len(scenes))
}
}

return nil
},
},
Expand Down
1 change: 1 addition & 0 deletions pkg/models/model_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type File struct {
VideoProjection string `json:"projection"`

HasHeatmap bool `json:"has_heatmap"`
FunscriptSpeed int `json:"funscript_speed" gorm:"default:0"`
IsSelectedScript bool `json:"is_selected_script"`
IsExported bool `json:"is_exported"`
}
Expand Down
58 changes: 58 additions & 0 deletions pkg/models/model_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type Scene struct {
IsAccessible bool `json:"is_accessible" gorm:"default:false"`
IsWatched bool `json:"is_watched" gorm:"default:false"`
IsScripted bool `json:"is_scripted" gorm:"default:false"`
FunscriptSpeed int `json:"funscript_speed" gorm:"default:0"`
Cuepoints []SceneCuepoint `json:"cuepoints"`
History []History `json:"history"`
AddedDate time.Time `json:"added_date"`
Expand Down Expand Up @@ -227,6 +228,37 @@ func (o *Scene) GetScriptFiles() ([]File, error) {
return files, nil
}

func (o *Scene) GetScriptSpeed() (int, error) {
fmt.Printf("getting script speed for %s", o.Title)
files, err := o.GetScriptFiles()
if err != nil {
return 0, err
}
if len(files) == 0 {
return 0, fmt.Errorf("no scripts found for %s", o.Title)
}
return files[0].FunscriptSpeed, nil
}

func (o *Scene) GetScriptSpeedFromFiles(files []File) int {
var newestScriptDate time.Time
var newestScript File

for j := range files {
if files[j].Type == "script" {
if files[j].Exists() && files[j].IsSelectedScript {
return files[j].FunscriptSpeed
}
if files[j].Exists() && (files[j].CreatedTime.After(newestScriptDate) || newestScriptDate.IsZero()) {
newestScriptDate = files[j].CreatedTime
newestScript = files[j]
}
}
}

return newestScript.FunscriptSpeed
}

func (o *Scene) PreviewExists() bool {
if _, err := os.Stat(filepath.Join(common.VideoPreviewDir, fmt.Sprintf("%v.mp4", o.SceneID))); os.IsNotExist(err) {
return false
Expand All @@ -248,6 +280,7 @@ func (o *Scene) UpdateStatus() {
if len(files) > 0 {
var newestFileDate time.Time
var totalFileSize int64
var newestScriptDate time.Time
for j := range files {
totalFileSize = totalFileSize + files[j].Size

Expand All @@ -257,6 +290,10 @@ func (o *Scene) UpdateStatus() {
if files[j].Exists() && (files[j].CreatedTime.After(newestFileDate) || newestFileDate.IsZero()) {
newestFileDate = files[j].CreatedTime
}

if files[j].Exists() && (files[j].CreatedTime.After(newestScriptDate) || newestScriptDate.IsZero()) {
newestScriptDate = files[j].CreatedTime
}
}

if files[j].Type == "video" {
Expand Down Expand Up @@ -291,6 +328,12 @@ func (o *Scene) UpdateStatus() {

if scripts == 0 && o.IsScripted == true {
o.IsScripted = false
o.FunscriptSpeed = 0
changed = true
}

if !newestScriptDate.Equal(o.AddedDate) || o.FunscriptSpeed == 0 && o.IsScripted {
o.FunscriptSpeed = o.GetScriptSpeedFromFiles(files)
changed = true
}

Expand All @@ -316,6 +359,7 @@ func (o *Scene) UpdateStatus() {

if o.IsScripted == true {
o.IsScripted = false
o.FunscriptSpeed = 0
changed = true
}
}
Expand Down Expand Up @@ -444,6 +488,8 @@ type RequestSceneList struct {
Volume optional.Int `json:"volume"`
Released optional.String `json:"releaseMonth"`
Sort optional.String `json:"sort"`
MinFunscript optional.Int `json:"minFunscript"`
MaxFunscript optional.Int `json:"maxFunscript"`
}

type ResponseSceneList struct {
Expand Down Expand Up @@ -498,6 +544,14 @@ func QueryScenes(r RequestSceneList, enablePreload bool) ResponseSceneList {
tx = tx.Where("is_watched = ?", r.IsWatched.OrElse(true))
}

if r.MinFunscript.Present() {
tx = tx.Where("funscript_speed > ?", r.MinFunscript.OrElse(0))
}

if r.MaxFunscript.Present() {
tx = tx.Where("funscript_speed < ?", r.MaxFunscript.OrElse(0))
}

if r.Volume.Present() && r.Volume.OrElse(0) != 0 {
tx = tx.
Joins("left join files on files.scene_id=scenes.id").
Expand Down Expand Up @@ -598,6 +652,10 @@ func QueryScenes(r RequestSceneList, enablePreload bool) ResponseSceneList {
tx = tx.Order("created_at desc")
case "scene_updated_desc":
tx = tx.Order("updated_at desc")
case "funscript_speed_desc":
tx = tx.Order("funscript_speed desc")
case "funscript_speed_asc":
tx = tx.Order("funscript_speed asc")
case "random":
if dbConn.Driver == "mysql" {
tx = tx.Order("rand()")
Expand Down
74 changes: 74 additions & 0 deletions pkg/tasks/funscripts.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"archive/zip"
"fmt"
"io"
"math"
"net/http"
"os"
"sort"

"github.com/sirupsen/logrus"
"github.com/xbapps/xbvr/pkg/models"
)

Expand Down Expand Up @@ -96,3 +99,74 @@ func AddFileToZip(zipWriter *zip.Writer, srcfilename, zipfilename string) error
_, err = io.Copy(writer, fileToZip)
return err
}

func GenerateFunscriptSpeeds(tlog *logrus.Entry) {
if !models.CheckLock("funscript_speeds") {
models.CreateLock("funscript_speeds")

db, _ := models.GetDB()
defer db.Close()

var scriptfiles []models.File
db.Model(&models.File{}).Preload("Volume").Where("type = ?", "script").Where("funscript_speed = ?", 0).Find(&scriptfiles)

for i, file := range scriptfiles {
if tlog != nil && (i%50) == 0 {
tlog.Infof("Generating funscript speeds (%v/%v)", i+1, len(scriptfiles))
}
if file.Exists() {
speed, err := CalculateFunscriptSpeed(file.GetPath())

if err == nil {
file.FunscriptSpeed = speed
file.Save()
} else {
log.Warn(err)
}
}
}
}

models.RemoveLock("funscript_speeds")
}

func CalculateFunscriptSpeed(inputFile string) (int, error) {
funscript, err := LoadFunscriptData(inputFile)
if err != nil {
return 0, err
}
funscript.UpdateSpeed()
return funscript.CalculateMedian(), nil
}

func (funscript *Script) UpdateSpeed() {
var t1, t2 int64
var p1, p2 int

for i := range funscript.Actions {
if i == 0 {
continue
}
t1 = funscript.Actions[i].At
t2 = funscript.Actions[i-1].At
p1 = funscript.Actions[i].Pos
p2 = funscript.Actions[i-1].Pos

speed := math.Abs(float64(p1-p2)) / float64(t1-t2) * 1000
funscript.Actions[i].Speed = speed
}
}

func (funscript *Script) CalculateMedian() int {
sort.Slice(funscript.Actions, func(i, j int) bool {
return funscript.Actions[i].Speed < funscript.Actions[j].Speed
})

mNumber := len(funscript.Actions) / 2

if len(funscript.Actions)%2 != 0 {
return int(funscript.Actions[mNumber].Speed)
}

return int((funscript.Actions[mNumber-1].Speed + funscript.Actions[mNumber].Speed) / 2)
}
3 changes: 3 additions & 0 deletions pkg/tasks/heatmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type Action struct {

Slope float64
Intensity int64

// Used in funscript speed task
Speed float64
}

type GradientTable []struct {
Expand Down
3 changes: 3 additions & 0 deletions pkg/tasks/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ func RescanVolumes() {
}
}

tlog.Infof("Generating funscript speeds")
GenerateFunscriptSpeeds(tlog)

// Update scene statuses
tlog.Infof("Update status of Scenes")
db.Model(&models.Scene{}).Find(&scenes)
Expand Down
1 change: 1 addition & 0 deletions ui/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"Scene updated date": "Scene updated date",
"Rating": "Rating",
"Recently viewed": "Recently viewed",
"Funscript speed": "Funscript speed",
"Random": "Random",
"File": "File",
"Created": "Created",
Expand Down
Loading