diff --git a/frontend/src/css/video.css b/frontend/src/css/video.css index bb678a1..b6b7a2c 100644 --- a/frontend/src/css/video.css +++ b/frontend/src/css/video.css @@ -1,4 +1,5 @@ .frame--video { + position: relative; display: flex; justify-content: center; width: 100%; diff --git a/internal/cache/cache.go b/internal/cache/cache.go index c1c5cfd..7f51ed9 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/damongolding/immich-kiosk/internal/config" + "github.com/damongolding/immich-kiosk/internal/utils" gocache "github.com/patrickmn/go-cache" ) @@ -88,3 +90,20 @@ func Replace(key string, x any) error { func ReplaceWithExpiration(key string, x any, t time.Duration) error { return kioskCache.Replace(key, x, t) } + +func AssetToCache[T any](viewDataToAdd T, requestConfig *config.Config, deviceID, url string) { + utils.TrimHistory(&requestConfig.History, 10) + + cachedViewData := []T{} + + viewCacheKey := ViewCacheKey(url, deviceID) + + if data, found := Get(viewCacheKey); found { + cachedViewData = data.([]T) + } + + cachedViewData = append(cachedViewData, viewDataToAdd) + + Set(viewCacheKey, cachedViewData) + +} diff --git a/internal/routes/routes_image_helpers.go b/internal/routes/routes_image_helpers.go index 199dfbb..1ed584e 100644 --- a/internal/routes/routes_image_helpers.go +++ b/internal/routes/routes_image_helpers.go @@ -209,18 +209,22 @@ func processVideo(immichImage *immich.ImmichAsset, sourceType kiosk.Source, requ // We need to see if the video has been downloaded // if so, return nil // if it hasn't been downloaded, download it and return a image - // - // if the video is not available, run processAsset again to get a new image + + // Video is available if VideoManager.IsDownloaded(immichImage.ID) { immichImage.KioskSource = sourceType - return nil, nil + img, err := fetchImagePreview(immichImage, requestID, deviceID, isPrefetch) + + return img, err } + // video is not available, is video downloading? if !VideoManager.IsDownloading(immichImage.ID) { go VideoManager.DownloadVideo(*immichImage, requestConfig, deviceID, requestUrl) } + // if the video is not available, run processAsset again to get a new image return processAsset(immichImage, []immich.ImmichAssetType{immich.VideoType}, requestConfig, requestID, deviceID, requestUrl, isPrefetch) } @@ -228,7 +232,10 @@ func processImage(immichImage *immich.ImmichAsset, sourceType kiosk.Source, requ immichImage.KioskSource = sourceType - return fetchImagePreview(immichImage, requestID, deviceID, isPrefetch) + img, err := fetchImagePreview(immichImage, requestID, deviceID, isPrefetch) + + return img, err + } // imageToBase64 converts image bytes to a base64 string and logs the processing time. @@ -340,13 +347,8 @@ func processViewImageData(imageOrientation immich.ImageOrientation, requestConfi return common.ViewImageData{}, fmt.Errorf("selecting image: %w", err) } - // Video has been chosen - if img == nil { - return common.ViewImageData{ - ImmichAsset: immichImage, - ImageData: "", - ImageBlurData: "", - }, nil + if immichImage.Type == immich.VideoType { + log.Info("Video found", "img", img) } if strings.EqualFold(requestConfig.ImageEffect, "smart-zoom") && len(immichImage.People)+len(immichImage.UnassignedFaces) == 0 { @@ -391,19 +393,8 @@ func ProcessViewImageDataWithRatio(imageOrientation immich.ImageOrientation, req } func assetToCache(viewDataToAdd common.ViewData, requestConfig *config.Config, deviceID string, requestData *common.RouteRequestData, c echo.Context) { - utils.TrimHistory(&requestConfig.History, 10) - - cachedViewData := []common.ViewData{} - - viewCacheKey := cache.ViewCacheKey(c.Request().URL.String(), deviceID) - - if data, found := cache.Get(viewCacheKey); found { - cachedViewData = data.([]common.ViewData) - } - - cachedViewData = append(cachedViewData, viewDataToAdd) - cache.Set(viewCacheKey, cachedViewData) + cache.AssetToCache(viewDataToAdd, requestConfig, deviceID, c.Request().URL.String()) go webhooks.Trigger(requestData, KioskVersion, webhooks.PrefetchAsset, viewDataToAdd) } diff --git a/internal/routes/routes_video.go b/internal/routes/routes_video.go index 091b883..a6383ea 100644 --- a/internal/routes/routes_video.go +++ b/internal/routes/routes_video.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/charmbracelet/log" "github.com/damongolding/immich-kiosk/internal/config" "github.com/labstack/echo/v4" ) @@ -52,6 +53,7 @@ func NewVideo(baseConfig *config.Config) echo.HandlerFunc { end = fileSize - 1 if rangeHeader != "" { + log.Info("using rangeHeader") // Remove "bytes=" prefix rangeStr := strings.Replace(rangeHeader, "bytes=", "", 1) // Split the range into start-end @@ -98,13 +100,12 @@ func NewVideo(baseConfig *config.Config) echo.HandlerFunc { c.Response().Header().Set("Content-Length", strconv.FormatInt(chunkSize, 10)) c.Response().Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize)) c.Response().Header().Set("Accept-Ranges", "bytes") - c.Response().Header().Set("Connection", "keep-alive") - c.Response().Header().Set("Keep-Alive", "timeout=5, max=100") + // c.Response().Header().Set("Connection", "keep-alive") + // c.Response().Header().Set("Keep-Alive", "timeout=5, max=100") // Create a limited reader for the chunk chunk := io.LimitReader(video, chunkSize) - // Stream the chunk return c.Stream(http.StatusPartialContent, vid.ImmichAsset.OriginalMimeType, chunk) } } diff --git a/internal/templates/components/video/video.templ b/internal/templates/components/video/video.templ index 087ae33..4db13c6 100644 --- a/internal/templates/components/video/video.templ +++ b/internal/templates/components/video/video.templ @@ -8,13 +8,21 @@ import ( templ Video(viewData common.ViewData) {
+
+ Blurred image background +
+ preload="auto" + > + +
@partials.RenderHistory(viewData) diff --git a/internal/video/video.go b/internal/video/video.go index e203b66..db8ed3b 100644 --- a/internal/video/video.go +++ b/internal/video/video.go @@ -21,11 +21,7 @@ import ( ) var ( - tmpDirectory string - videoDirectory string customTempVideoDir = filepath.Join(os.TempDir(), "immich-kiosk", "videos") - - requestConfig config.Config ) type Video struct { @@ -50,8 +46,6 @@ func New(ctx context.Context, base config.Config) (*VideoManager, error) { return nil, err } - requestConfig = base - v := &VideoManager{} go v.VideoCleanup(ctx) @@ -163,7 +157,7 @@ func (v *VideoManager) GetVideo(id string) (Video, error) { return Video{}, fmt.Errorf("video not found") } -func (v *VideoManager) AddVideoToViewCache(id, fileName, filePath string, requestConfig *config.Config, deviceID, requestUrl string, immichAsset immich.ImmichAsset) { +func (v *VideoManager) AddVideoToViewCache(id, fileName, filePath string, requestConfig *config.Config, deviceID, requestUrl string, immichAsset immich.ImmichAsset, imageBlurData string) { v.mu.Lock() defer v.mu.Unlock() @@ -180,14 +174,15 @@ func (v *VideoManager) AddVideoToViewCache(id, fileName, filePath string, reques Config: *requestConfig, Images: []common.ViewImageData{ common.ViewImageData{ - ImmichAsset: immichAsset, + ImmichAsset: immichAsset, + ImageBlurData: imageBlurData, }, }, } log.Info("Adding video to cache") - ViewDataToCache(viewDataToAdd, requestConfig, deviceID, nil, requestUrl) + cache.AssetToCache(viewDataToAdd, requestConfig, deviceID, requestUrl) } func (v *VideoManager) updateLastAccessed(id string) { @@ -277,25 +272,29 @@ func (v *VideoManager) DownloadVideo(immichAsset immich.ImmichAsset, requestConf return } - v.AddVideoToViewCache(videoID, filename, filePath, &requestConfig, deviceID, requestUrl, immichAsset) - - log.Debug("downloaded video", "path", filePath) -} - -func ViewDataToCache(viewDataToAdd common.ViewData, requestConfig *config.Config, deviceID string, requestData any, url string) { - utils.TrimHistory(&requestConfig.History, 10) + imgBytes, err := immichAsset.ImagePreview() + if err != nil { + log.Errorf("getting image preview: %w", err) + } - cachedViewData := []common.ViewData{} + img, err := utils.BytesToImage(imgBytes) + if err != nil { + log.Errorf("image BytesToImage: %w", err) + } - viewCacheKey := cache.ViewCacheKey(url, deviceID) + img = utils.ApplyExifOrientation(img, immichAsset.IsLandscape, immichAsset.ExifInfo.Orientation) - if data, found := cache.Get(viewCacheKey); found { - cachedViewData = data.([]common.ViewData) + img, err = utils.BlurImage(img, false, 0, 0) + if err != nil { + log.Errorf("getting image preview: %w", err) } - cachedViewData = append(cachedViewData, viewDataToAdd) + imageBlurData, err := utils.ImageToBase64(img) + if err != nil { + log.Errorf("converting image to base64: %w", err) + } - cache.Set(viewCacheKey, cachedViewData) + v.AddVideoToViewCache(videoID, filename, filePath, &requestConfig, deviceID, requestUrl, immichAsset, imageBlurData) - // go webhooks.Trigger(requestData, KioskVersion, webhooks.PrefetchAsset, viewDataToAdd) + log.Debug("downloaded video", "path", filePath) }