diff --git a/api/stream.go b/api/stream.go
index 0238b2f7c..e7086600e 100644
--- a/api/stream.go
+++ b/api/stream.go
@@ -50,6 +50,7 @@ func configGinStreamRestRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) {
thumbs.GET(":fid", routes.getThumbs)
thumbs.GET("/live", routes.getLiveThumbs)
thumbs.GET("/vod", routes.getVODThumbs)
+ thumbs.POST("/", routes.putCustomLiveThumbnail) // TODO: change to admin only endpoint
}
}
{
@@ -177,8 +178,15 @@ func (r streamRoutes) getLiveThumbs(c *gin.Context) {
tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)
streamId := strconv.Itoa(int(tumLiveContext.Stream.ID))
- path := pathprovider.LiveThumbnail(streamId)
- c.File(path)
+
+ file, err := r.DaoWrapper.FileDao.GetThumbnail(tumLiveContext.Stream.ID, model.FILETYPE_THUMB_LG_CAM_PRES)
+ if err != nil {
+
+ path := pathprovider.LiveThumbnail(streamId)
+ c.File(path)
+ }
+ c.File(file.Path)
+
}
func (r streamRoutes) getSubtitles(c *gin.Context) {
@@ -894,3 +902,61 @@ func (r streamRoutes) updateChatEnabled(c *gin.Context) {
return
}
}
+
+func (r streamRoutes) putCustomLiveThumbnail(c *gin.Context) {
+ tumLiveContext := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)
+ streamID := tumLiveContext.Stream.ID
+ course := tumLiveContext.Course
+ file, err := c.FormFile("file")
+ if err != nil {
+ //c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file"})
+ _ = c.AbortWithError(http.StatusBadRequest, tools.RequestError{
+ Status: http.StatusBadRequest,
+ CustomMessage: "Invalid file",
+ Err: err,
+ })
+ return
+ }
+
+ filename := file.Filename
+ fileUuid := uuid.NewV1()
+
+ filesFolder := filepath.Join(
+ tools.Cfg.Paths.Mass,
+ fmt.Sprintf("%s.%d.%s", course.Name, course.Year, course.TeachingTerm),
+ "files")
+
+ path := filepath.Join(
+ filesFolder,
+ fmt.Sprintf("%s%s", fileUuid, filepath.Ext(filename)))
+
+ //tempFilePath := pathprovider.LiveThumbnail(strconv.Itoa(int(streamID)))
+ if err := c.SaveUploadedFile(file, path); err != nil {
+ _ = c.AbortWithError(http.StatusInternalServerError, tools.RequestError{
+ Status: http.StatusInternalServerError,
+ CustomMessage: "Failed to save file",
+ Err: err,
+ })
+ return
+ }
+
+ thumb := model.File{
+ StreamID: streamID,
+ Path: path,
+ Filename: file.Filename,
+ Type: model.FILETYPE_THUMB_LG_CAM_PRES,
+ CourseName: course.Name,
+ }
+
+ if err := r.DaoWrapper.FileDao.SetThumbnail(streamID, thumb); err != nil {
+
+ _ = c.AbortWithError(http.StatusInternalServerError, tools.RequestError{
+ Status: http.StatusInternalServerError,
+ CustomMessage: "Failed to set thumbnail",
+ Err: err,
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "Thumbnail uploaded successfully"})
+}
diff --git a/dao/file.go b/dao/file.go
index 4f31790a5..1a3f2a4d6 100644
--- a/dao/file.go
+++ b/dao/file.go
@@ -14,6 +14,7 @@ type FileDao interface {
DeleteFile(id uint) error
CountVoDFiles() (int64, error)
SetThumbnail(streamId uint, thumb model.File) error
+ GetThumbnail(streamId uint, fileType model.FileType) (f model.File, err error)
}
type fileDao struct {
@@ -56,3 +57,8 @@ func (d fileDao) SetThumbnail(streamId uint, thumb model.File) error {
return tx.Create(&thumb).Error
})
}
+
+func (d fileDao) GetThumbnail(streamId uint, fileType model.FileType) (f model.File, err error) {
+ err = DB.Where("stream_id = ? AND type = ?", streamId, fileType).First(&f).Error
+ return
+}
diff --git a/model/file.go b/model/file.go
index 824b3300c..2b8d6c984 100644
--- a/model/file.go
+++ b/model/file.go
@@ -22,15 +22,17 @@ const (
FILETYPE_THUMB_LG_CAM
FILETYPE_THUMB_LG_PRES
FILETYPE_THUMB_LG_CAM_PRES // generated from CAM and PRES, preferred over the others
+ FILETYPE_THUMB_CUSTOM
)
type File struct {
gorm.Model
- StreamID uint `gorm:"not null"`
- Path string `gorm:"not null"`
- Filename string
- Type FileType `gorm:"not null; default: 1"`
+ StreamID uint `gorm:"not null"`
+ Path string `gorm:"not null"`
+ Filename string
+ Type FileType `gorm:"not null; default: 1"`
+ CourseName string `gorm:"default: null"`
}
func (f File) GetDownloadFileName() string {
@@ -68,7 +70,7 @@ func (f File) GetVodTypeByName() string {
}
func (f File) IsThumb() bool {
- return f.Type == FILETYPE_THUMB_CAM || f.Type == FILETYPE_THUMB_PRES || f.Type == FILETYPE_THUMB_COMB
+ return f.Type == FILETYPE_THUMB_CAM || f.Type == FILETYPE_THUMB_PRES || f.Type == FILETYPE_THUMB_COMB || f.Type == FILETYPE_THUMB_CUSTOM
}
func (f File) IsURL() bool {
diff --git a/model/stream.go b/model/stream.go
index ab44a7a6c..6104d1c48 100755
--- a/model/stream.go
+++ b/model/stream.go
@@ -18,43 +18,44 @@ import (
type Stream struct {
gorm.Model
- Name string `gorm:"index:,class:FULLTEXT"`
- Description string `gorm:"type:text;index:,class:FULLTEXT"`
- CourseID uint
- Start time.Time `gorm:"not null"`
- End time.Time `gorm:"not null"`
- ChatEnabled bool `gorm:"default:null"`
- RoomName string
- RoomCode string
- EventTypeName string
- TUMOnlineEventID uint
- SeriesIdentifier string `gorm:"default:null"`
- StreamKey string `gorm:"not null"`
- PlaylistUrl string
- PlaylistUrlPRES string
- PlaylistUrlCAM string
- LiveNow bool `gorm:"not null"`
- LiveNowTimestamp time.Time `gorm:"default:null;column:live_now_timestamp"`
- Recording bool
- Premiere bool `gorm:"default:null"`
- Ended bool `gorm:"default:null"`
- Chats []Chat
- Stats []Stat
- Units []StreamUnit
- VodViews uint `gorm:"default:0"` // todo: remove me before next semester
- StartOffset uint `gorm:"default:null"`
- EndOffset uint `gorm:"default:null"`
- LectureHallID uint `gorm:"default:null"`
- Silences []Silence
- Files []File `gorm:"foreignKey:StreamID"`
- ThumbInterval uint32 `gorm:"default:null"`
- StreamName string
- Duration sql.NullInt32 `gorm:"default:null"`
- StreamWorkers []Worker `gorm:"many2many:stream_workers;"`
- StreamProgresses []StreamProgress `gorm:"foreignKey:StreamID"`
- VideoSections []VideoSection
- TranscodingProgresses []TranscodingProgress `gorm:"foreignKey:StreamID"`
- Private bool `gorm:"not null;default:false"`
+ Name string `gorm:"index:,class:FULLTEXT"`
+ Description string `gorm:"type:text;index:,class:FULLTEXT"`
+ CourseID uint
+ Start time.Time `gorm:"not null"`
+ End time.Time `gorm:"not null"`
+ ChatEnabled bool `gorm:"default:null"`
+ CustomThumbnailEnabled bool `gorm:"default:false"`
+ RoomName string
+ RoomCode string
+ EventTypeName string
+ TUMOnlineEventID uint
+ SeriesIdentifier string `gorm:"default:null"`
+ StreamKey string `gorm:"not null"`
+ PlaylistUrl string
+ PlaylistUrlPRES string
+ PlaylistUrlCAM string
+ LiveNow bool `gorm:"not null"`
+ LiveNowTimestamp time.Time `gorm:"default:null;column:live_now_timestamp"`
+ Recording bool
+ Premiere bool `gorm:"default:null"`
+ Ended bool `gorm:"default:null"`
+ Chats []Chat
+ Stats []Stat
+ Units []StreamUnit
+ VodViews uint `gorm:"default:0"` // todo: remove me before next semester
+ StartOffset uint `gorm:"default:null"`
+ EndOffset uint `gorm:"default:null"`
+ LectureHallID uint `gorm:"default:null"`
+ Silences []Silence
+ Files []File `gorm:"foreignKey:StreamID"`
+ ThumbInterval uint32 `gorm:"default:null"`
+ StreamName string
+ Duration sql.NullInt32 `gorm:"default:null"`
+ StreamWorkers []Worker `gorm:"many2many:stream_workers;"`
+ StreamProgresses []StreamProgress `gorm:"foreignKey:StreamID"`
+ VideoSections []VideoSection
+ TranscodingProgresses []TranscodingProgress `gorm:"foreignKey:StreamID"`
+ Private bool `gorm:"not null;default:false"`
Watched bool `gorm:"-"` // Used to determine if stream is watched when loaded for a specific user.
}
@@ -337,30 +338,31 @@ func (s Stream) GetJson(lhs []LectureHall, course Course) gin.H {
}
return gin.H{
- "lectureId": s.Model.ID,
- "courseId": s.CourseID,
- "seriesIdentifier": s.SeriesIdentifier,
- "name": s.Name,
- "description": s.Description,
- "lectureHallId": s.LectureHallID,
- "lectureHallName": lhName,
- "streamKey": s.StreamKey,
- "isLiveNow": s.LiveNow,
- "isRecording": s.Recording,
- "isConverting": s.IsConverting(),
- "transcodingProgresses": s.TranscodingProgresses,
- "isPast": s.IsPast(),
- "hasStats": s.Stats != nil,
- "files": files,
- "color": s.Color(),
- "start": s.Start,
- "end": s.End,
- "isChatEnabled": s.ChatEnabled,
- "courseSlug": course.Slug,
- "private": s.Private,
- "downloadableVods": s.GetVodFiles(),
- "isCopying": false,
- "videoSections": videoSections,
+ "lectureId": s.Model.ID,
+ "courseId": s.CourseID,
+ "seriesIdentifier": s.SeriesIdentifier,
+ "name": s.Name,
+ "description": s.Description,
+ "lectureHallId": s.LectureHallID,
+ "lectureHallName": lhName,
+ "streamKey": s.StreamKey,
+ "isLiveNow": s.LiveNow,
+ "isRecording": s.Recording,
+ "isConverting": s.IsConverting(),
+ "transcodingProgresses": s.TranscodingProgresses,
+ "isPast": s.IsPast(),
+ "hasStats": s.Stats != nil,
+ "files": files,
+ "color": s.Color(),
+ "start": s.Start,
+ "end": s.End,
+ "isChatEnabled": s.ChatEnabled,
+ "isCustomThumbnailEnabled": s.CustomThumbnailEnabled,
+ "courseSlug": course.Slug,
+ "private": s.Private,
+ "downloadableVods": s.GetVodFiles(),
+ "isCopying": false,
+ "videoSections": videoSections,
}
}
diff --git a/web/template/partial/course/manage/lecture-management-card.gohtml b/web/template/partial/course/manage/lecture-management-card.gohtml
index 2b66b8865..c8155ff08 100644
--- a/web/template/partial/course/manage/lecture-management-card.gohtml
+++ b/web/template/partial/course/manage/lecture-management-card.gohtml
@@ -374,6 +374,40 @@
Chat Enabled
+
+
+
+{{/* */}}
+