2020-05-13 13:36:42 +00:00
package api
import (
2022-06-24 04:59:22 +00:00
"fmt"
2020-05-13 13:36:42 +00:00
"net/http"
2022-06-26 17:47:12 +00:00
"strings"
2020-05-13 13:36:42 +00:00
2022-04-15 07:42:07 +00:00
"github.com/photoprism/photoprism/pkg/video"
2020-05-13 13:36:42 +00:00
"github.com/gin-gonic/gin"
2022-04-06 15:46:41 +00:00
2020-06-07 08:09:35 +00:00
"github.com/photoprism/photoprism/internal/photoprism"
2020-05-13 13:36:42 +00:00
"github.com/photoprism/photoprism/internal/query"
2022-04-06 15:46:41 +00:00
"github.com/photoprism/photoprism/internal/service"
2022-04-15 07:42:07 +00:00
"github.com/photoprism/photoprism/pkg/clean"
2020-05-13 13:36:42 +00:00
)
2022-01-18 11:28:27 +00:00
// GetVideo streams videos.
//
2020-05-27 17:38:40 +00:00
// GET /api/v1/videos/:hash/:token/:type
2020-05-13 13:36:42 +00:00
//
// Parameters:
2022-08-10 14:09:21 +00:00
//
// hash: string The photo or video file hash as returned by the search API
// type: string Video format
2020-06-25 12:54:04 +00:00
func GetVideo ( router * gin . RouterGroup ) {
2022-04-12 11:28:28 +00:00
router . GET ( "/videos/:hash/:token/:format" , func ( c * gin . Context ) {
2020-06-26 14:11:56 +00:00
if InvalidPreviewToken ( c ) {
2020-05-27 17:38:40 +00:00
c . Data ( http . StatusForbidden , "image/svg+xml" , brokenIconSvg )
return
}
2022-04-15 07:42:07 +00:00
fileHash := clean . Token ( c . Param ( "hash" ) )
formatName := clean . Token ( c . Param ( "format" ) )
2020-05-13 13:36:42 +00:00
2022-04-15 07:42:07 +00:00
format , ok := video . Types [ formatName ]
2020-05-13 13:36:42 +00:00
if ! ok {
2022-04-15 07:42:07 +00:00
log . Errorf ( "video: invalid format %s" , clean . Log ( formatName ) )
2020-05-13 13:36:42 +00:00
c . Data ( http . StatusOK , "image/svg+xml" , videoIconSvg )
return
}
f , err := query . FileByHash ( fileHash )
if err != nil {
2020-05-20 08:42:48 +00:00
log . Errorf ( "video: %s" , err . Error ( ) )
2020-05-13 13:36:42 +00:00
c . Data ( http . StatusOK , "image/svg+xml" , videoIconSvg )
return
}
2020-05-20 08:42:48 +00:00
if ! f . FileVideo {
2020-05-23 18:58:58 +00:00
f , err = query . VideoByPhotoUID ( f . PhotoUID )
2020-05-20 08:42:48 +00:00
if err != nil {
log . Errorf ( "video: %s" , err . Error ( ) )
c . Data ( http . StatusOK , "image/svg+xml" , videoIconSvg )
return
}
}
2020-05-13 13:36:42 +00:00
if f . FileError != "" {
log . Errorf ( "video: file error %s" , f . FileError )
c . Data ( http . StatusOK , "image/svg+xml" , videoIconSvg )
return
}
2020-06-07 08:09:35 +00:00
fileName := photoprism . FileName ( f . FileRoot , f . FileName )
2022-06-26 17:47:12 +00:00
fileBitrate := f . Bitrate ( )
// File format supported by the client/browser?
supported := f . FileCodec != "" && f . FileCodec == string ( format . Codec ) || format . Codec == video . UnknownCodec && f . FileType == string ( format . File )
// File bitrate too high (for streaming)?
conf := service . Config ( )
transcode := ! supported || conf . FFmpegEnabled ( ) && conf . FFmpegBitrateExceeded ( fileBitrate )
2020-05-13 13:36:42 +00:00
2020-12-05 03:24:10 +00:00
if mf , err := photoprism . NewMediaFile ( fileName ) ; err != nil {
2020-05-25 17:10:44 +00:00
// Set missing flag so that the file doesn't show up in search results anymore.
2020-05-28 14:26:22 +00:00
logError ( "video" , f . Update ( "FileMissing" , true ) )
2020-05-25 17:10:44 +00:00
2022-06-24 04:59:22 +00:00
// Log error and default to 404.mp4
log . Errorf ( "video: file %s is missing" , clean . Log ( f . FileName ) )
fileName = service . Config ( ) . StaticFile ( "video/404.mp4" )
AddContentTypeHeader ( c , ContentTypeAvc )
2022-06-26 17:47:12 +00:00
} else if transcode {
if f . FileCodec != "" {
log . Debugf ( "video: %s is %s compressed and cannot be streamed directly, average bitrate %.1f MBit/s" , clean . Log ( f . FileName ) , clean . Log ( strings . ToUpper ( f . FileCodec ) ) , fileBitrate )
2022-06-24 04:59:22 +00:00
} else {
2022-06-26 17:47:12 +00:00
log . Debugf ( "video: %s cannot be streamed directly, average bitrate %.1f MBit/s" , clean . Log ( f . FileName ) , fileBitrate )
2022-06-24 04:59:22 +00:00
}
2022-06-26 17:47:12 +00:00
2020-12-05 03:24:10 +00:00
conv := service . Convert ( )
2022-04-06 15:46:41 +00:00
if avcFile , err := conv . ToAvc ( mf , service . Config ( ) . FFmpegEncoder ( ) , false , false ) ; err != nil {
2022-06-24 04:59:22 +00:00
// Log error and default to 404.mp4
2022-04-15 07:42:07 +00:00
log . Errorf ( "video: transcoding %s failed" , clean . Log ( f . FileName ) )
2022-06-24 04:59:22 +00:00
fileName = service . Config ( ) . StaticFile ( "video/404.mp4" )
2020-12-12 16:20:31 +00:00
} else {
fileName = avcFile . FileName ( )
2020-12-05 03:24:10 +00:00
}
2020-05-13 13:36:42 +00:00
2022-06-23 23:30:48 +00:00
AddContentTypeHeader ( c , ContentTypeAvc )
2022-06-26 17:47:12 +00:00
} else {
if f . FileCodec != "" && f . FileCodec != f . FileType {
log . Debugf ( "video: %s is %s compressed and requires no transcoding, average bitrate %.1f MBit/s" , clean . Log ( f . FileName ) , clean . Log ( strings . ToUpper ( f . FileCodec ) ) , fileBitrate )
AddContentTypeHeader ( c , fmt . Sprintf ( "%s; codecs=\"%s\"" , f . FileMime , clean . Codec ( f . FileCodec ) ) )
} else {
log . Debugf ( "video: %s is streamed directly, average bitrate %.1f MBit/s" , clean . Log ( f . FileName ) , fileBitrate )
AddContentTypeHeader ( c , f . FileMime )
}
2022-06-23 23:30:48 +00:00
}
2020-12-12 16:53:19 +00:00
2020-05-13 13:36:42 +00:00
if c . Query ( "download" ) != "" {
2021-01-27 20:30:10 +00:00
c . FileAttachment ( fileName , f . DownloadName ( DownloadName ( c ) , 0 ) )
2020-05-13 13:36:42 +00:00
} else {
c . File ( fileName )
}
return
} )
}