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 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 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 CHANGE COLUMN photo_name media_name varchar(512) NOT NULL;
|
||||
ALTER TABLE media_url
|
||||
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 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/v2 v2.2.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/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||
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-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-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
|
@ -30,6 +30,8 @@ models:
|
|||
model: github.com/viktorstrate/photoview/api/graphql/models.MediaURL
|
||||
MediaEXIF:
|
||||
model: github.com/viktorstrate/photoview/api/graphql/models.MediaEXIF
|
||||
VideoMetadata:
|
||||
model: github.com/viktorstrate/photoview/api/graphql/models.VideoMetadata
|
||||
Album:
|
||||
model: github.com/viktorstrate/photoview/api/graphql/models.Album
|
||||
ShareToken:
|
||||
|
|
|
@ -71,18 +71,19 @@ type ComplexityRoot struct {
|
|||
}
|
||||
|
||||
Media struct {
|
||||
Album func(childComplexity int) int
|
||||
Downloads func(childComplexity int) int
|
||||
Exif func(childComplexity int) int
|
||||
Favorite func(childComplexity int) int
|
||||
HighRes func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Path func(childComplexity int) int
|
||||
Shares func(childComplexity int) int
|
||||
Thumbnail func(childComplexity int) int
|
||||
Title func(childComplexity int) int
|
||||
Type func(childComplexity int) int
|
||||
VideoWeb func(childComplexity int) int
|
||||
Album func(childComplexity int) int
|
||||
Downloads func(childComplexity int) int
|
||||
Exif func(childComplexity int) int
|
||||
Favorite func(childComplexity int) int
|
||||
HighRes func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Path func(childComplexity int) int
|
||||
Shares func(childComplexity int) int
|
||||
Thumbnail func(childComplexity int) int
|
||||
Title func(childComplexity int) int
|
||||
Type func(childComplexity int) int
|
||||
VideoMetadata func(childComplexity int) int
|
||||
VideoWeb func(childComplexity int) int
|
||||
}
|
||||
|
||||
MediaDownload struct {
|
||||
|
@ -190,6 +191,19 @@ type ComplexityRoot struct {
|
|||
RootPath 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 {
|
||||
|
@ -208,6 +222,7 @@ type MediaResolver interface {
|
|||
VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
|
||||
Album(ctx context.Context, obj *models.Media) (*models.Album, 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)
|
||||
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
|
||||
|
||||
case "Media.videoMetadata":
|
||||
if e.complexity.Media.VideoMetadata == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Media.VideoMetadata(childComplexity), true
|
||||
|
||||
case "Media.videoWeb":
|
||||
if e.complexity.Media.VideoWeb == nil {
|
||||
break
|
||||
|
@ -1040,6 +1062,76 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
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
|
||||
}
|
||||
|
@ -1340,6 +1432,7 @@ type Media {
|
|||
"The album that holds the media"
|
||||
album: Album!
|
||||
exif: MediaEXIF
|
||||
videoMetadata: VideoMetadata
|
||||
favorite: Boolean!
|
||||
type: MediaType!
|
||||
|
||||
|
@ -1372,6 +1465,19 @@ type MediaEXIF {
|
|||
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 {
|
||||
query: String!
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
defer func() {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
defer func() {
|
||||
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)
|
||||
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":
|
||||
out.Values[i] = ec._Media_favorite(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
|
@ -7481,6 +7954,63 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
|
|||
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"}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
return graphql.UnmarshalInt(v)
|
||||
}
|
||||
|
@ -8619,6 +9163,17 @@ func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotov
|
|||
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 {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
|
|
|
@ -8,14 +8,15 @@ import (
|
|||
)
|
||||
|
||||
type Media struct {
|
||||
MediaID int
|
||||
Title string
|
||||
Path string
|
||||
PathHash string
|
||||
AlbumId int
|
||||
ExifId *int
|
||||
Favorite bool
|
||||
Type MediaType
|
||||
MediaID int
|
||||
Title string
|
||||
Path string
|
||||
PathHash string
|
||||
AlbumId int
|
||||
ExifId *int
|
||||
Favorite bool
|
||||
Type MediaType
|
||||
VideoMetadataId *int
|
||||
}
|
||||
|
||||
func (p *Media) ID() int {
|
||||
|
@ -45,7 +46,7 @@ type MediaURL struct {
|
|||
func NewMediaFromRow(row *sql.Row) (*Media, error) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,7 @@ func NewMediaFromRows(rows *sql.Rows) ([]*Media, error) {
|
|||
|
||||
for rows.Next() {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
user := auth.UserFromContext(ctx)
|
||||
|
|
|
@ -217,6 +217,7 @@ type Media {
|
|||
"The album that holds the media"
|
||||
album: Album!
|
||||
exif: MediaEXIF
|
||||
videoMetadata: VideoMetadata
|
||||
favorite: Boolean!
|
||||
type: MediaType!
|
||||
|
||||
|
@ -249,6 +250,19 @@ type MediaEXIF {
|
|||
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 {
|
||||
query: String!
|
||||
albums: [Album!]!
|
||||
|
|
|
@ -106,7 +106,7 @@ func (enc *EncodeMediaData) VideoMetadata() (*ffprobe.ProbeData, error) {
|
|||
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)
|
||||
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 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()
|
||||
if stream == nil {
|
||||
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)
|
||||
photo, err := models.NewMediaFromRow(row)
|
||||
media, err := models.NewMediaFromRow(row)
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
height
|
||||
}
|
||||
videoMetadata {
|
||||
id
|
||||
width
|
||||
height
|
||||
duration
|
||||
codec
|
||||
framerate
|
||||
bitrate
|
||||
colorProfile
|
||||
audio
|
||||
}
|
||||
exif {
|
||||
camera
|
||||
maker
|
||||
|
@ -96,7 +107,7 @@ const Name = styled.div`
|
|||
margin: 0.75rem 0 1rem;
|
||||
`
|
||||
|
||||
const ExifInfo = styled.div`
|
||||
const MetadataInfo = styled.div`
|
||||
margin-bottom: 1.5rem;
|
||||
`
|
||||
|
||||
|
@ -154,11 +165,33 @@ const SidebarContent = ({ media, hidePreview }) => {
|
|||
exif.focalLength = `${exif.focalLength}mm`
|
||||
}
|
||||
|
||||
exif.exposureProgram = exifItems = exifKeys.map(key => (
|
||||
exifItems = exifKeys.map(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
|
||||
if (media) {
|
||||
if (media.highRes) previewImage = media.highRes
|
||||
|
@ -175,7 +208,8 @@ const SidebarContent = ({ media, hidePreview }) => {
|
|||
</PreviewImageWrapper>
|
||||
)}
|
||||
<Name>{media && media.title}</Name>
|
||||
<ExifInfo>{exifItems}</ExifInfo>
|
||||
<MetadataInfo>{videoMetadataItems}</MetadataInfo>
|
||||
<MetadataInfo>{exifItems}</MetadataInfo>
|
||||
<SidebarDownload photo={media} />
|
||||
<SidebarShare photo={media} />
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue