Add video metadata
This commit is contained in:
parent
035aabb852
commit
0e9d37ca77
|
@ -9,3 +9,9 @@ ALTER TABLE photo_url CHANGE COLUMN media_name photo_name varchar(512) NOT NULL;
|
||||||
ALTER TABLE share_token CHANGE COLUMN media_id photo_id int;
|
ALTER TABLE share_token CHANGE COLUMN media_id photo_id int;
|
||||||
|
|
||||||
ALTER TABLE photo DROP COLUMN media_type;
|
ALTER TABLE photo DROP COLUMN media_type;
|
||||||
|
|
||||||
|
ALTER TABLE photo
|
||||||
|
DROP FOREIGN KEY photo_ibfk_3,
|
||||||
|
DROP COLUMN video_metadata_id;
|
||||||
|
|
||||||
|
DROP TABLE video_metadata;
|
|
@ -4,8 +4,27 @@ ALTER TABLE photo_url RENAME TO media_url;
|
||||||
ALTER TABLE photo_exif RENAME TO media_exif;
|
ALTER TABLE photo_exif RENAME TO media_exif;
|
||||||
|
|
||||||
ALTER TABLE media CHANGE COLUMN photo_id media_id int NOT NULL AUTO_INCREMENT;
|
ALTER TABLE media CHANGE COLUMN photo_id media_id int NOT NULL AUTO_INCREMENT;
|
||||||
ALTER TABLE media_url CHANGE COLUMN photo_id media_id int NOT NULL;
|
ALTER TABLE media_url
|
||||||
ALTER TABLE media_url CHANGE COLUMN photo_name media_name varchar(512) NOT NULL;
|
CHANGE COLUMN photo_id media_id int NOT NULL,
|
||||||
|
CHANGE COLUMN photo_name media_name varchar(512) NOT NULL;
|
||||||
ALTER TABLE share_token CHANGE COLUMN photo_id media_id int;
|
ALTER TABLE share_token CHANGE COLUMN photo_id media_id int;
|
||||||
|
|
||||||
ALTER TABLE media ADD COLUMN media_type varchar(64) NOT NULL DEFAULT "photo";
|
CREATE TABLE video_metadata (
|
||||||
|
metadata_id int NOT NULL AUTO_INCREMENT,
|
||||||
|
|
||||||
|
width int(6) NOT NULL,
|
||||||
|
height int(6) NOT NULL,
|
||||||
|
duration double NOT NULL,
|
||||||
|
codec varchar(128),
|
||||||
|
framerate double,
|
||||||
|
bitrate int(24),
|
||||||
|
color_profile varchar(128),
|
||||||
|
audio varchar(128),
|
||||||
|
|
||||||
|
PRIMARY KEY (metadata_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE media
|
||||||
|
ADD COLUMN media_type varchar(64) NOT NULL DEFAULT "photo",
|
||||||
|
ADD COLUMN video_metadata_id int,
|
||||||
|
ADD FOREIGN KEY (video_metadata_id) REFERENCES video_metadata(metadata_id);
|
||||||
|
|
|
@ -25,7 +25,7 @@ require (
|
||||||
github.com/urfave/cli v1.22.4 // indirect
|
github.com/urfave/cli v1.22.4 // indirect
|
||||||
github.com/urfave/cli/v2 v2.2.0 // indirect
|
github.com/urfave/cli/v2 v2.2.0 // indirect
|
||||||
github.com/vektah/dataloaden v0.3.0 // indirect
|
github.com/vektah/dataloaden v0.3.0 // indirect
|
||||||
github.com/vektah/gqlparser v1.3.1
|
github.com/vektah/gqlparser v1.3.1 // indirect
|
||||||
github.com/vektah/gqlparser/v2 v2.0.1
|
github.com/vektah/gqlparser/v2 v2.0.1
|
||||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||||
github.com/xor-gate/goexif2 v1.1.0
|
github.com/xor-gate/goexif2 v1.1.0
|
||||||
|
|
|
@ -165,6 +165,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
|
@ -30,6 +30,8 @@ models:
|
||||||
model: github.com/viktorstrate/photoview/api/graphql/models.MediaURL
|
model: github.com/viktorstrate/photoview/api/graphql/models.MediaURL
|
||||||
MediaEXIF:
|
MediaEXIF:
|
||||||
model: github.com/viktorstrate/photoview/api/graphql/models.MediaEXIF
|
model: github.com/viktorstrate/photoview/api/graphql/models.MediaEXIF
|
||||||
|
VideoMetadata:
|
||||||
|
model: github.com/viktorstrate/photoview/api/graphql/models.VideoMetadata
|
||||||
Album:
|
Album:
|
||||||
model: github.com/viktorstrate/photoview/api/graphql/models.Album
|
model: github.com/viktorstrate/photoview/api/graphql/models.Album
|
||||||
ShareToken:
|
ShareToken:
|
||||||
|
|
|
@ -71,18 +71,19 @@ type ComplexityRoot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
Media struct {
|
Media struct {
|
||||||
Album func(childComplexity int) int
|
Album func(childComplexity int) int
|
||||||
Downloads func(childComplexity int) int
|
Downloads func(childComplexity int) int
|
||||||
Exif func(childComplexity int) int
|
Exif func(childComplexity int) int
|
||||||
Favorite func(childComplexity int) int
|
Favorite func(childComplexity int) int
|
||||||
HighRes func(childComplexity int) int
|
HighRes func(childComplexity int) int
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
Path func(childComplexity int) int
|
Path func(childComplexity int) int
|
||||||
Shares func(childComplexity int) int
|
Shares func(childComplexity int) int
|
||||||
Thumbnail func(childComplexity int) int
|
Thumbnail func(childComplexity int) int
|
||||||
Title func(childComplexity int) int
|
Title func(childComplexity int) int
|
||||||
Type func(childComplexity int) int
|
Type func(childComplexity int) int
|
||||||
VideoWeb func(childComplexity int) int
|
VideoMetadata func(childComplexity int) int
|
||||||
|
VideoWeb func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaDownload struct {
|
MediaDownload struct {
|
||||||
|
@ -190,6 +191,19 @@ type ComplexityRoot struct {
|
||||||
RootPath func(childComplexity int) int
|
RootPath func(childComplexity int) int
|
||||||
Username func(childComplexity int) int
|
Username func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoMetadata struct {
|
||||||
|
Audio func(childComplexity int) int
|
||||||
|
Bitrate func(childComplexity int) int
|
||||||
|
Codec func(childComplexity int) int
|
||||||
|
ColorProfile func(childComplexity int) int
|
||||||
|
Duration func(childComplexity int) int
|
||||||
|
Framerate func(childComplexity int) int
|
||||||
|
Height func(childComplexity int) int
|
||||||
|
ID func(childComplexity int) int
|
||||||
|
Media func(childComplexity int) int
|
||||||
|
Width func(childComplexity int) int
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlbumResolver interface {
|
type AlbumResolver interface {
|
||||||
|
@ -208,6 +222,7 @@ type MediaResolver interface {
|
||||||
VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
|
VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
|
||||||
Album(ctx context.Context, obj *models.Media) (*models.Album, error)
|
Album(ctx context.Context, obj *models.Media) (*models.Album, error)
|
||||||
Exif(ctx context.Context, obj *models.Media) (*models.MediaEXIF, error)
|
Exif(ctx context.Context, obj *models.Media) (*models.MediaEXIF, error)
|
||||||
|
VideoMetadata(ctx context.Context, obj *models.Media) (*models.VideoMetadata, error)
|
||||||
|
|
||||||
Shares(ctx context.Context, obj *models.Media) ([]*models.ShareToken, error)
|
Shares(ctx context.Context, obj *models.Media) ([]*models.ShareToken, error)
|
||||||
Downloads(ctx context.Context, obj *models.Media) ([]*models.MediaDownload, error)
|
Downloads(ctx context.Context, obj *models.Media) ([]*models.MediaDownload, error)
|
||||||
|
@ -443,6 +458,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.Media.Type(childComplexity), true
|
return e.complexity.Media.Type(childComplexity), true
|
||||||
|
|
||||||
|
case "Media.videoMetadata":
|
||||||
|
if e.complexity.Media.VideoMetadata == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Media.VideoMetadata(childComplexity), true
|
||||||
|
|
||||||
case "Media.videoWeb":
|
case "Media.videoWeb":
|
||||||
if e.complexity.Media.VideoWeb == nil {
|
if e.complexity.Media.VideoWeb == nil {
|
||||||
break
|
break
|
||||||
|
@ -1040,6 +1062,76 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.User.Username(childComplexity), true
|
return e.complexity.User.Username(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.audio":
|
||||||
|
if e.complexity.VideoMetadata.Audio == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Audio(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.bitrate":
|
||||||
|
if e.complexity.VideoMetadata.Bitrate == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Bitrate(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.codec":
|
||||||
|
if e.complexity.VideoMetadata.Codec == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Codec(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.colorProfile":
|
||||||
|
if e.complexity.VideoMetadata.ColorProfile == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.ColorProfile(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.duration":
|
||||||
|
if e.complexity.VideoMetadata.Duration == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Duration(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.framerate":
|
||||||
|
if e.complexity.VideoMetadata.Framerate == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Framerate(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.height":
|
||||||
|
if e.complexity.VideoMetadata.Height == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Height(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.id":
|
||||||
|
if e.complexity.VideoMetadata.ID == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.ID(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.media":
|
||||||
|
if e.complexity.VideoMetadata.Media == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Media(childComplexity), true
|
||||||
|
|
||||||
|
case "VideoMetadata.width":
|
||||||
|
if e.complexity.VideoMetadata.Width == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.VideoMetadata.Width(childComplexity), true
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
@ -1340,6 +1432,7 @@ type Media {
|
||||||
"The album that holds the media"
|
"The album that holds the media"
|
||||||
album: Album!
|
album: Album!
|
||||||
exif: MediaEXIF
|
exif: MediaEXIF
|
||||||
|
videoMetadata: VideoMetadata
|
||||||
favorite: Boolean!
|
favorite: Boolean!
|
||||||
type: MediaType!
|
type: MediaType!
|
||||||
|
|
||||||
|
@ -1372,6 +1465,19 @@ type MediaEXIF {
|
||||||
exposureProgram: Int
|
exposureProgram: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VideoMetadata {
|
||||||
|
id: Int!
|
||||||
|
media: Media!
|
||||||
|
width: Int!
|
||||||
|
height: Int!
|
||||||
|
duration: Float!
|
||||||
|
codec: String
|
||||||
|
framerate: Float
|
||||||
|
bitrate: Int
|
||||||
|
colorProfile: String
|
||||||
|
audio: String
|
||||||
|
}
|
||||||
|
|
||||||
type SearchResult {
|
type SearchResult {
|
||||||
query: String!
|
query: String!
|
||||||
albums: [Album!]!
|
albums: [Album!]!
|
||||||
|
@ -2642,6 +2748,37 @@ func (ec *executionContext) _Media_exif(ctx context.Context, field graphql.Colle
|
||||||
return ec.marshalOMediaEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMediaEXIF(ctx, field.Selections, res)
|
return ec.marshalOMediaEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMediaEXIF(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Media_videoMetadata(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "Media",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return ec.resolvers.Media().VideoMetadata(rctx, obj)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*models.VideoMetadata)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOVideoMetadata2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐVideoMetadata(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Media_favorite(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Media_favorite(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
@ -5419,6 +5556,331 @@ func (ec *executionContext) _User_admin(ctx context.Context, field graphql.Colle
|
||||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_id(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.ID(), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_media(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Media(), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*models.Media)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNMedia2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_width(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Width, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_height(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Height, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_duration(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Duration, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(float64)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNFloat2float64(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_codec(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Codec, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*string)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_framerate(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Framerate, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*float64)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_bitrate(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Bitrate, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_colorProfile(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.ColorProfile, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*string)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata_audio(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "VideoMetadata",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Audio, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*string)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
|
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
@ -6765,6 +7227,17 @@ func (ec *executionContext) _Media(ctx context.Context, sel ast.SelectionSet, ob
|
||||||
res = ec._Media_exif(ctx, field, obj)
|
res = ec._Media_exif(ctx, field, obj)
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
case "videoMetadata":
|
||||||
|
field := field
|
||||||
|
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
res = ec._Media_videoMetadata(ctx, field, obj)
|
||||||
|
return res
|
||||||
|
})
|
||||||
case "favorite":
|
case "favorite":
|
||||||
out.Values[i] = ec._Media_favorite(ctx, field, obj)
|
out.Values[i] = ec._Media_favorite(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
|
@ -7481,6 +7954,63 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var videoMetadataImplementors = []string{"VideoMetadata"}
|
||||||
|
|
||||||
|
func (ec *executionContext) _VideoMetadata(ctx context.Context, sel ast.SelectionSet, obj *models.VideoMetadata) graphql.Marshaler {
|
||||||
|
fields := graphql.CollectFields(ec.OperationContext, sel, videoMetadataImplementors)
|
||||||
|
|
||||||
|
out := graphql.NewFieldSet(fields)
|
||||||
|
var invalids uint32
|
||||||
|
for i, field := range fields {
|
||||||
|
switch field.Name {
|
||||||
|
case "__typename":
|
||||||
|
out.Values[i] = graphql.MarshalString("VideoMetadata")
|
||||||
|
case "id":
|
||||||
|
out.Values[i] = ec._VideoMetadata_id(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "media":
|
||||||
|
out.Values[i] = ec._VideoMetadata_media(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "width":
|
||||||
|
out.Values[i] = ec._VideoMetadata_width(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "height":
|
||||||
|
out.Values[i] = ec._VideoMetadata_height(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "duration":
|
||||||
|
out.Values[i] = ec._VideoMetadata_duration(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "codec":
|
||||||
|
out.Values[i] = ec._VideoMetadata_codec(ctx, field, obj)
|
||||||
|
case "framerate":
|
||||||
|
out.Values[i] = ec._VideoMetadata_framerate(ctx, field, obj)
|
||||||
|
case "bitrate":
|
||||||
|
out.Values[i] = ec._VideoMetadata_bitrate(ctx, field, obj)
|
||||||
|
case "colorProfile":
|
||||||
|
out.Values[i] = ec._VideoMetadata_colorProfile(ctx, field, obj)
|
||||||
|
case "audio":
|
||||||
|
out.Values[i] = ec._VideoMetadata_audio(ctx, field, obj)
|
||||||
|
default:
|
||||||
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.Dispatch()
|
||||||
|
if invalids > 0 {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
var __DirectiveImplementors = []string{"__Directive"}
|
var __DirectiveImplementors = []string{"__Directive"}
|
||||||
|
|
||||||
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
|
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
|
||||||
|
@ -7805,6 +8335,20 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v interface{}) (float64, error) {
|
||||||
|
return graphql.UnmarshalFloat(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.SelectionSet, v float64) graphql.Marshaler {
|
||||||
|
res := graphql.MarshalFloat(v)
|
||||||
|
if res == graphql.Null {
|
||||||
|
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) {
|
func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) {
|
||||||
return graphql.UnmarshalInt(v)
|
return graphql.UnmarshalInt(v)
|
||||||
}
|
}
|
||||||
|
@ -8619,6 +9163,17 @@ func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotov
|
||||||
return ec._User(ctx, sel, v)
|
return ec._User(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOVideoMetadata2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐVideoMetadata(ctx context.Context, sel ast.SelectionSet, v models.VideoMetadata) graphql.Marshaler {
|
||||||
|
return ec._VideoMetadata(ctx, sel, &v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOVideoMetadata2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐVideoMetadata(ctx context.Context, sel ast.SelectionSet, v *models.VideoMetadata) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._VideoMetadata(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
|
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
|
|
|
@ -8,14 +8,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Media struct {
|
type Media struct {
|
||||||
MediaID int
|
MediaID int
|
||||||
Title string
|
Title string
|
||||||
Path string
|
Path string
|
||||||
PathHash string
|
PathHash string
|
||||||
AlbumId int
|
AlbumId int
|
||||||
ExifId *int
|
ExifId *int
|
||||||
Favorite bool
|
Favorite bool
|
||||||
Type MediaType
|
Type MediaType
|
||||||
|
VideoMetadataId *int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Media) ID() int {
|
func (p *Media) ID() int {
|
||||||
|
@ -45,7 +46,7 @@ type MediaURL struct {
|
||||||
func NewMediaFromRow(row *sql.Row) (*Media, error) {
|
func NewMediaFromRow(row *sql.Row) (*Media, error) {
|
||||||
media := Media{}
|
media := Media{}
|
||||||
|
|
||||||
if err := row.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.Favorite, &media.Type); err != nil {
|
if err := row.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.Favorite, &media.Type, &media.VideoMetadataId); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ func NewMediaFromRows(rows *sql.Rows) ([]*Media, error) {
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var media Media
|
var media Media
|
||||||
if err := rows.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.Favorite, &media.Type); err != nil {
|
if err := rows.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.Favorite, &media.Type, &media.VideoMetadataId); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
medias = append(medias, &media)
|
medias = append(medias, &media)
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "database/sql"
|
||||||
|
|
||||||
|
type VideoMetadata struct {
|
||||||
|
MetadataID int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
Duration float64
|
||||||
|
Codec *string
|
||||||
|
Framerate *float64
|
||||||
|
Bitrate *int
|
||||||
|
ColorProfile *string
|
||||||
|
Audio *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (metadata *VideoMetadata) ID() int {
|
||||||
|
return metadata.MetadataID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (metadata *VideoMetadata) Media() *Media {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVideoMetadataFromRow(row *sql.Row) (*VideoMetadata, error) {
|
||||||
|
meta := VideoMetadata{}
|
||||||
|
|
||||||
|
if err := row.Scan(&meta.MetadataID, &meta.Width, &meta.Height, &meta.Duration, &meta.Codec, &meta.Framerate, &meta.Bitrate, &meta.ColorProfile, &meta.Audio); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &meta, nil
|
||||||
|
}
|
|
@ -192,6 +192,21 @@ func (r *mediaResolver) Exif(ctx context.Context, obj *models.Media) (*models.Me
|
||||||
return exif, nil
|
return exif, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mediaResolver) VideoMetadata(ctx context.Context, obj *models.Media) (*models.VideoMetadata, error) {
|
||||||
|
row := r.Database.QueryRow("SELECT video_metadata.* FROM media JOIN video_metadata ON media.video_metadata_id = video_metadata.metadata_id WHERE media.media_id = ?", obj.MediaID)
|
||||||
|
|
||||||
|
metadata, err := models.NewVideoMetadataFromRow(row)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return nil, errors.Wrapf(err, "could not get video metadata of media from database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) FavoriteMedia(ctx context.Context, mediaID int, favorite bool) (*models.Media, error) {
|
func (r *mutationResolver) FavoriteMedia(ctx context.Context, mediaID int, favorite bool) (*models.Media, error) {
|
||||||
|
|
||||||
user := auth.UserFromContext(ctx)
|
user := auth.UserFromContext(ctx)
|
||||||
|
|
|
@ -217,6 +217,7 @@ type Media {
|
||||||
"The album that holds the media"
|
"The album that holds the media"
|
||||||
album: Album!
|
album: Album!
|
||||||
exif: MediaEXIF
|
exif: MediaEXIF
|
||||||
|
videoMetadata: VideoMetadata
|
||||||
favorite: Boolean!
|
favorite: Boolean!
|
||||||
type: MediaType!
|
type: MediaType!
|
||||||
|
|
||||||
|
@ -249,6 +250,19 @@ type MediaEXIF {
|
||||||
exposureProgram: Int
|
exposureProgram: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VideoMetadata {
|
||||||
|
id: Int!
|
||||||
|
media: Media!
|
||||||
|
width: Int!
|
||||||
|
height: Int!
|
||||||
|
duration: Float!
|
||||||
|
codec: String
|
||||||
|
framerate: Float
|
||||||
|
bitrate: Int
|
||||||
|
colorProfile: String
|
||||||
|
audio: String
|
||||||
|
}
|
||||||
|
|
||||||
type SearchResult {
|
type SearchResult {
|
||||||
query: String!
|
query: String!
|
||||||
albums: [Album!]!
|
albums: [Album!]!
|
||||||
|
|
|
@ -106,7 +106,7 @@ func (enc *EncodeMediaData) VideoMetadata() (*ffprobe.ProbeData, error) {
|
||||||
return enc._videoMetadata, nil
|
return enc._videoMetadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readVideoStreamMetadata(videoPath string) (*ffprobe.Stream, error) {
|
func readVideoMetadata(videoPath string) (*ffprobe.ProbeData, error) {
|
||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
|
|
||||||
|
@ -115,6 +115,15 @@ func readVideoStreamMetadata(videoPath string) (*ffprobe.Stream, error) {
|
||||||
return nil, errors.Wrapf(err, "could not read video metadata (%s)", path.Base(videoPath))
|
return nil, errors.Wrapf(err, "could not read video metadata (%s)", path.Base(videoPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readVideoStreamMetadata(videoPath string) (*ffprobe.Stream, error) {
|
||||||
|
data, err := readVideoMetadata(videoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "read video stream metadata")
|
||||||
|
}
|
||||||
|
|
||||||
stream := data.FirstVideoStream()
|
stream := data.FirstVideoStream()
|
||||||
if stream == nil {
|
if stream == nil {
|
||||||
return nil, errors.Wrapf(err, "could not get stream from file metadata (%s)", path.Base(videoPath))
|
return nil, errors.Wrapf(err, "could not get stream from file metadata (%s)", path.Base(videoPath))
|
||||||
|
|
|
@ -50,15 +50,21 @@ func ScanMedia(tx *sql.Tx, mediaPath string, albumId int, cache *AlbumScannerCac
|
||||||
}
|
}
|
||||||
|
|
||||||
row := tx.QueryRow("SELECT * FROM media WHERE media_id = ?", media_id)
|
row := tx.QueryRow("SELECT * FROM media WHERE media_id = ?", media_id)
|
||||||
photo, err := models.NewMediaFromRow(row)
|
media, err := models.NewMediaFromRow(row)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, errors.Wrap(err, "failed to get media by id from database")
|
return nil, false, errors.Wrap(err, "failed to get media by id from database")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ScanEXIF(tx, photo)
|
_, err = ScanEXIF(tx, media)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("WARN: ScanEXIF for %s failed: %s\n", mediaName, err)
|
log.Printf("WARN: ScanEXIF for %s failed: %s\n", mediaName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return photo, true, nil
|
if media.Type == models.MediaTypeVideo {
|
||||||
|
if err = ScanVideoMetadata(tx, media); err != nil {
|
||||||
|
log.Printf("WARN: ScanVideoMetadata for %s failed: %s\n", mediaName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return media, true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ScanVideoMetadata(tx *sql.Tx, video *models.Media) error {
|
||||||
|
|
||||||
|
data, err := readVideoMetadata(video.Path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "scan video metadata failed (%s)", video.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := data.FirstVideoStream()
|
||||||
|
if stream == nil {
|
||||||
|
return errors.New(fmt.Sprintf("could not get video stream from metadata (%s)", video.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
audio := data.FirstAudioStream()
|
||||||
|
var audioText string
|
||||||
|
if audio == nil {
|
||||||
|
audioText = "No audio"
|
||||||
|
} else {
|
||||||
|
switch audio.Channels {
|
||||||
|
case 0:
|
||||||
|
audioText = "No audio"
|
||||||
|
case 1:
|
||||||
|
audioText = "Mono audio"
|
||||||
|
case 2:
|
||||||
|
audioText = "Stereo audio"
|
||||||
|
default:
|
||||||
|
audioText = fmt.Sprintf("Audio (%d channels)", audio.Channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var framerate *float64 = nil
|
||||||
|
if stream.AvgFrameRate != "" {
|
||||||
|
parts := strings.Split(stream.AvgFrameRate, "/")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
if numerator, err := strconv.ParseInt(parts[0], 10, 64); err == nil {
|
||||||
|
if denominator, err := strconv.ParseInt(parts[1], 10, 64); err == nil {
|
||||||
|
result := float64(numerator) / float64(denominator)
|
||||||
|
framerate = &result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := tx.Exec("INSERT INTO video_metadata (width, height, duration, codec, framerate, bitrate, color_profile, audio) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", stream.Width, stream.Height, data.Format.DurationSeconds, stream.CodecLongName, framerate, stream.BitRate, stream.Profile, audioText)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to insert video metadata into database (%s)", video.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata_id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = tx.Exec("UPDATE media SET video_metadata_id = ? WHERE media_id = ?", metadata_id, video.MediaID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -29,6 +29,17 @@ const mediaQuery = gql`
|
||||||
width
|
width
|
||||||
height
|
height
|
||||||
}
|
}
|
||||||
|
videoMetadata {
|
||||||
|
id
|
||||||
|
width
|
||||||
|
height
|
||||||
|
duration
|
||||||
|
codec
|
||||||
|
framerate
|
||||||
|
bitrate
|
||||||
|
colorProfile
|
||||||
|
audio
|
||||||
|
}
|
||||||
exif {
|
exif {
|
||||||
camera
|
camera
|
||||||
maker
|
maker
|
||||||
|
@ -96,7 +107,7 @@ const Name = styled.div`
|
||||||
margin: 0.75rem 0 1rem;
|
margin: 0.75rem 0 1rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ExifInfo = styled.div`
|
const MetadataInfo = styled.div`
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -154,11 +165,33 @@ const SidebarContent = ({ media, hidePreview }) => {
|
||||||
exif.focalLength = `${exif.focalLength}mm`
|
exif.focalLength = `${exif.focalLength}mm`
|
||||||
}
|
}
|
||||||
|
|
||||||
exif.exposureProgram = exifItems = exifKeys.map(key => (
|
exifItems = exifKeys.map(key => (
|
||||||
<SidebarItem key={key} name={exifNameLookup[key]} value={exif[key]} />
|
<SidebarItem key={key} name={exifNameLookup[key]} value={exif[key]} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let videoMetadataItems = []
|
||||||
|
if (media && media.videoMetadata) {
|
||||||
|
let metadata = Object.keys(media.videoMetadata)
|
||||||
|
.filter(x => !['id', '__typename', 'width', 'height'].includes(x))
|
||||||
|
.reduce(
|
||||||
|
(prev, curr) => ({
|
||||||
|
...prev,
|
||||||
|
[curr]: media.videoMetadata[curr],
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
dimensions: `${media.videoMetadata.width}x${media.videoMetadata.height}`,
|
||||||
|
...metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
videoMetadataItems = Object.keys(metadata).map(key => (
|
||||||
|
<SidebarItem key={key} name={key} value={metadata[key]} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
let previewImage = null
|
let previewImage = null
|
||||||
if (media) {
|
if (media) {
|
||||||
if (media.highRes) previewImage = media.highRes
|
if (media.highRes) previewImage = media.highRes
|
||||||
|
@ -175,7 +208,8 @@ const SidebarContent = ({ media, hidePreview }) => {
|
||||||
</PreviewImageWrapper>
|
</PreviewImageWrapper>
|
||||||
)}
|
)}
|
||||||
<Name>{media && media.title}</Name>
|
<Name>{media && media.title}</Name>
|
||||||
<ExifInfo>{exifItems}</ExifInfo>
|
<MetadataInfo>{videoMetadataItems}</MetadataInfo>
|
||||||
|
<MetadataInfo>{exifItems}</MetadataInfo>
|
||||||
<SidebarDownload photo={media} />
|
<SidebarDownload photo={media} />
|
||||||
<SidebarShare photo={media} />
|
<SidebarShare photo={media} />
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue