Skip to content
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
3 changes: 2 additions & 1 deletion graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,8 @@ type Mutation {
destroyFiles(ids: [ID!]!): Boolean!

fileSetFingerprints(input: FileSetFingerprintsInput!): Boolean!
"Reveal the file in the system file manager"
fileSetVRMetadata(input: FileSetVRMetadataInput!): Boolean!
"Reveal file system file manager"
revealFileInFileManager(id: ID!): Boolean!
"Reveal the folder in the system file manager"
revealFolderInFileManager(id: ID!): Boolean!
Expand Down
29 changes: 26 additions & 3 deletions graphql/schema/types/file.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ type VideoFile implements BaseFile {
frame_rate: Float!
bit_rate: Int!

projection: String
stereo_mode: String
Comment on lines +96 to +97

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've added enum types to the models package, but haven't defined them in the graphql schema and aren't using them in the applicable fields.

vr_corrections: VRCorrections

scenes: [Scene!]!

created_at: Time!
Expand Down Expand Up @@ -170,9 +174,28 @@ input SetFingerprintsInput {
}

input FileSetFingerprintsInput {
id: ID!
"only supplied fingerprint types will be modified"
fingerprints: [SetFingerprintsInput!]!
id: ID!
"only supplied fingerprint types modified"
fingerprints: [SetFingerprintsInput!]!
}

type VRCorrections {
horizontal_offset: Float
vertical_offset: Float
alpha_mode: String
}

input FileSetVRMetadataInput {
id: ID!
projection: String
stereo_mode: String
vr_corrections: VRCorrectionsInput
}

input VRCorrectionsInput {
horizontal_offset: Float
vertical_offset: Float
alpha_mode: String
}

type FindFilesResultType {
Expand Down
35 changes: 35 additions & 0 deletions internal/api/resolver_mutation_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,41 @@ func (r *mutationResolver) FileSetFingerprints(ctx context.Context, input FileSe
return true, nil
}

func (r *mutationResolver) FileSetVRMetadata(ctx context.Context, input FileSetVRMetadataInput) (bool, error) {
fileIDInt, err := strconv.Atoi(input.ID)
if err != nil {
return false, fmt.Errorf("converting id: %w", err)
}
fileID := models.FileID(fileIDInt)

var projection *models.ProjectionEnum
if input.Projection != nil && *input.Projection != "" {
p := models.ProjectionEnum(*input.Projection)
projection = &p
}

var stereoMode *models.StereoModeEnum
if input.StereoMode != nil && *input.StereoMode != "" {
sm := models.StereoModeEnum(*input.StereoMode)
stereoMode = &sm
}

var vrCorrections *models.VRCorrections
if input.VrCorrections != nil {
vrCorrections = &models.VRCorrections{
HorizontalOffset: input.VrCorrections.HorizontalOffset,
VerticalOffset: input.VrCorrections.VerticalOffset,
AlphaMode: input.VrCorrections.AlphaMode,
}
}

if err := r.repository.File.ModifyVideoFileMetadata(ctx, fileID, projection, stereoMode, vrCorrections); err != nil {
return false, err
}

return true, nil
}

func (r *mutationResolver) RevealFileInFileManager(ctx context.Context, id string) (bool, error) {
// disallow if request did not come from localhost
if !session.IsLocalRequest(ctx) {
Expand Down
30 changes: 30 additions & 0 deletions pkg/file/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ func (i *Importer) fileJSONToFile(ctx context.Context, fileJSON jsonschema.DirEn
BitRate: ff.BitRate,
Interactive: ff.Interactive,
InteractiveSpeed: ff.InteractiveSpeed,
Projection: projectionPtr(ff.Projection),
StereoMode: stereoModePtr(ff.StereoMode),
VRCorrections: vrCorrectionsFromJSON(ff.VRCorrections),
}, nil
case *jsonschema.ImageFile:
baseFile, err := i.baseFileJSONToBaseFile(ctx, ff.BaseFile)
Expand Down Expand Up @@ -302,3 +305,30 @@ func (i *Importer) Update(ctx context.Context, id int) error {
// update not supported
return nil
}

func projectionPtr(s *string) *models.ProjectionEnum {
if s == nil {
return nil
}
p := models.ProjectionEnum(*s)
return &p
}

func stereoModePtr(s *string) *models.StereoModeEnum {
if s == nil {
return nil
}
m := models.StereoModeEnum(*s)
return &m
}

func vrCorrectionsFromJSON(jc *jsonschema.VRCorrections) *models.VRCorrections {
if jc == nil {
return nil
}
return &models.VRCorrections{
HorizontalOffset: jc.HorizontalOffset,
VerticalOffset: jc.VerticalOffset,
AlphaMode: jc.AlphaMode,
}
}
125 changes: 125 additions & 0 deletions pkg/models/file_vr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package models

import (
"fmt"
"io"
"strconv"
)

// ProjectionEnum describes how a video frame is mapped onto a sphere
// or other projection surface. Generic naming; agnostic of any
// particular player or device.
type ProjectionEnum string

const (
ProjectionEnumFlat ProjectionEnum = "FLAT"
ProjectionEnumEquirectangular ProjectionEnum = "EQUIRECTANGULAR"
ProjectionEnumFisheye ProjectionEnum = "FISHEYE"
ProjectionEnumMKX200 ProjectionEnum = "MKX200"
ProjectionEnumRF52 ProjectionEnum = "RF52"
ProjectionEnumDome ProjectionEnum = "DOME"
ProjectionEnumCubemap ProjectionEnum = "CUBEMAP"
ProjectionEnumRectilinear ProjectionEnum = "RECTILINEAR"
)

var AllProjectionEnum = []ProjectionEnum{
ProjectionEnumFlat,
ProjectionEnumEquirectangular,
ProjectionEnumFisheye,
ProjectionEnumMKX200,
ProjectionEnumRF52,
ProjectionEnumDome,
ProjectionEnumCubemap,
ProjectionEnumRectilinear,
}

func (e ProjectionEnum) IsValid() bool {
switch e {
case ProjectionEnumFlat, ProjectionEnumEquirectangular, ProjectionEnumFisheye,
ProjectionEnumMKX200, ProjectionEnumRF52, ProjectionEnumDome,
ProjectionEnumCubemap, ProjectionEnumRectilinear:
return true
}
return false
}

func (e ProjectionEnum) String() string {
return string(e)
}

func (e *ProjectionEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ProjectionEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ProjectionEnum", str)
}
return nil
}

func (e ProjectionEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

// StereoModeEnum describes how a stereoscopic video is laid out
// across the frame. Generic naming.
type StereoModeEnum string

const (
StereoModeEnumMono StereoModeEnum = "MONO"
StereoModeEnumSBS StereoModeEnum = "SBS"
StereoModeEnumTB StereoModeEnum = "TB"
StereoModeEnumCUV StereoModeEnum = "CUV"
StereoModeEnumAlternatingFrames StereoModeEnum = "AF"
StereoModeEnumInterleavedRows StereoModeEnum = "INTERLEAVED_ROWS"
)

var AllStereoModeEnum = []StereoModeEnum{
StereoModeEnumMono,
StereoModeEnumSBS,
StereoModeEnumTB,
StereoModeEnumCUV,
StereoModeEnumAlternatingFrames,
StereoModeEnumInterleavedRows,
}

func (e StereoModeEnum) IsValid() bool {
switch e {
case StereoModeEnumMono, StereoModeEnumSBS, StereoModeEnumTB, StereoModeEnumCUV,
StereoModeEnumAlternatingFrames, StereoModeEnumInterleavedRows:
return true
}
return false
}

func (e StereoModeEnum) String() string {
return string(e)
}

func (e *StereoModeEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = StereoModeEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid StereoModeEnum", str)
}
return nil
}

func (e StereoModeEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

// VRCorrections holds optional per-video corrections commonly
// applied when projecting stereoscopic or 360-degree content.
// All fields are optional; nil means "unset".
// AlphaMode follows DeoVR's alpha channel specification.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs better documentation than just referencing DeoVR. What is the alpha channel specification?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs better documentation than just referencing DeoVR. What is the alpha channel specification?

DeoVR now has 3 passthrough modes: Chroma Key, Alpha Packing and AI Alpha.
https://deovr.com/blog/136-testing-the-different-types-of-passthrough-at-deovr

The ones we can commonly use are Chroma Key and Alpha Packing.
For Chroma Key, we need 5 key/value pairs: Hue, Saturation, Brightness, Color Range and Falloff. All of them are int numbers.
For Alpha Packing, the video needs to be Fisheye SBS 3D. No value is needed.

type VRCorrections struct {
HorizontalOffset *float64 `json:"horizontal_offset"`
VerticalOffset *float64 `json:"vertical_offset"`
AlphaMode *string `json:"alpha_mode"`
}
11 changes: 11 additions & 0 deletions pkg/models/jsonschema/file_folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ type VideoFile struct {

Interactive bool `json:"interactive,omitempty"`
InteractiveSpeed *int `json:"interactive_speed,omitempty"`

Projection *string `json:"projection,omitempty"`
StereoMode *string `json:"stereo_mode,omitempty"`
VRCorrections *VRCorrections `json:"vr_corrections,omitempty"`
}

// VRCorrections mirrors models.VRCorrections for JSON import/export.
type VRCorrections struct {
HorizontalOffset *float64 `json:"horizontal_offset,omitempty"`
VerticalOffset *float64 `json:"vertical_offset,omitempty"`
AlphaMode *string `json:"alpha_mode,omitempty"`
}

type ImageFile struct {
Expand Down
14 changes: 14 additions & 0 deletions pkg/models/mocks/FileReaderWriter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/models/model_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ type VideoFile struct {

Interactive bool `json:"interactive"`
InteractiveSpeed *int `json:"interactive_speed"`

Projection *ProjectionEnum `json:"projection"`
StereoMode *StereoModeEnum `json:"stereo_mode"`
VRCorrections *VRCorrections `json:"vr_corrections"`
}

func (f VideoFile) GetWidth() int {
Expand Down
16 changes: 16 additions & 0 deletions pkg/models/repository_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,28 @@ type FileFingerprintWriter interface {
DestroyFingerprints(ctx context.Context, fileID FileID, types []string) error
}

// FileVRMetadataWriter provides methods to update VR-related
// metadata fields (projection, stereo_mode, vr_corrections)
// on a video file. Each pointer argument is optional: a nil
// value means "do not change this field". A non-nil zero value
// means "set this field to zero / null".
type FileVRMetadataWriter interface {
ModifyVideoFileMetadata(
ctx context.Context,
fileID FileID,
projection *ProjectionEnum,
stereoMode *StereoModeEnum,
vrCorrections *VRCorrections,
) error
}

// FileWriter provides all methods to modify files.
type FileWriter interface {
FileCreator
FileUpdater
FileDestroyer
FileFingerprintWriter
FileVRMetadataWriter

UpdateCaptions(ctx context.Context, fileID FileID, captions []*VideoCaption) error
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sqlite/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const (
cacheSizeEnv = "STASH_SQLITE_CACHE_SIZE"
)

var appSchemaVersion uint = 85
var appSchemaVersion uint = 86

//go:embed migrations/*.sql
var migrationsBox embed.FS
Expand Down
Loading
Loading