Skip to content

Commit

Permalink
Added small icon on videos
Browse files Browse the repository at this point in the history
  • Loading branch information
midstar committed Feb 23, 2019
1 parent a16c024 commit 57b3b3d
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 26 deletions.
2 changes: 1 addition & 1 deletion main_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ func mainCommon() *WebAPI {
llog.Info("Version: %s", applicationVersion)
llog.Info("Build time: %s", applicationBuildTime)
llog.Info("Git hash: %s", applicationGitHash)
media := createMedia(s.mediaPath, s.thumbPath, s.enableThumbCache, s.autoRotate)
box := packr.New("templates", "./templates")
media := createMedia(box, s.mediaPath, s.thumbPath, s.enableThumbCache, s.autoRotate)
webAPI := CreateWebAPI(s.port, "templates", media, box, s.userName, s.password)
return webAPI
}
59 changes: 51 additions & 8 deletions media.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/disintegration/imaging"
packr "github.com/gobuffalo/packr/v2"
"github.com/midstar/llog"
"github.com/rwcarlsen/goexif/exif"
)
Expand All @@ -21,10 +22,11 @@ var vidExtensions = [...]string{".avi", ".mov", ".vid", ".mkv", ".mp4"}

// Media represents the media including its base path
type Media struct {
mediaPath string // Top level path for media files
thumbPath string // Top level path for thumbnails
enableThumbCache bool // Generate thumbnails
autoRotate bool // Rotate JPEG files when needed
mediaPath string // Top level path for media files
thumbPath string // Top level path for thumbnails
enableThumbCache bool // Generate thumbnails
autoRotate bool // Rotate JPEG files when needed
box *packr.Box // For icons
}

// File represents a folder or any other file
Expand All @@ -36,7 +38,7 @@ type File struct {

// createMedia creates a new media. If thumb cache is enabled the path is
// created when needed.
func createMedia(mediaPath string, thumbPath string, enableThumbCache bool, autoRotate bool) *Media {
func createMedia(box *packr.Box, mediaPath string, thumbPath string, enableThumbCache bool, autoRotate bool) *Media {
llog.Info("Media path: %s", mediaPath)
if enableThumbCache {
directory := filepath.Dir(thumbPath)
Expand All @@ -55,7 +57,8 @@ func createMedia(mediaPath string, thumbPath string, enableThumbCache bool, auto
media := &Media{mediaPath: filepath.ToSlash(filepath.Clean(mediaPath)),
thumbPath: filepath.ToSlash(filepath.Clean(thumbPath)),
enableThumbCache: enableThumbCache,
autoRotate: autoRotate}
autoRotate: autoRotate,
box: box}
llog.Info("Video thumbnails supported (ffmpeg installed): %v", media.videoThumbnailSupport())
return media
}
Expand Down Expand Up @@ -367,7 +370,7 @@ func (m *Media) writeThumbnail(w io.Writer, relativeFilePath string) error {
thumbFile, err := os.Open(thumbFileName)
if err != nil {
// No thumb exist. Create it
llog.Info("Creating new thumbnail for %s", relativeFilePath)
llog.Trace("Creating new thumbnail for %s", relativeFilePath)
fullMediaPath, err := m.getFullMediaPath(relativeFilePath)
if err != nil {
return err
Expand Down Expand Up @@ -415,7 +418,47 @@ func (m *Media) generateVideoThumbnail(fullMediaPath, fullThumbPath string) erro
defer os.Remove(screenShot) // Remove temporary file

// Generate thumbnail from the screenshot
return m.generateImageThumbnail(screenShot, fullThumbPath)
img, err := imaging.Open(screenShot, imaging.AutoOrientation(true))
if err != nil {
return fmt.Errorf("Unable to open screenshot image %s. Reason: %s", screenShot, err)
}
thumbImg := imaging.Thumbnail(img, 256, 256, imaging.Lanczos)

// Add small video icon i upper right corner to indicate that this is
// a video
iconVideoImg, err := m.getVideoIcon()
if err != nil {
return err
}
thumbImg = imaging.Overlay(thumbImg, iconVideoImg, image.Pt(155, 11), 1.0)

// Write thumbnail to file
outFile, err := os.Create(fullThumbPath)
if err != nil {
return fmt.Errorf("Unable to open %s for creating thumbnail. Reason %s", fullThumbPath, err)
}
defer outFile.Close()
err = imaging.Encode(outFile, thumbImg, imaging.JPEG)

return err
}

// Cache to avoid regenerate icon each time (do it once)
var videoIcon image.Image

func (m *Media) getVideoIcon() (image.Image, error) {
if videoIcon != nil {
// To avoid re-generate
return videoIcon, nil
}
var err error
videoIconBytes, _ := m.box.Find("icon_video.png")
videoIcon, err = imaging.Decode(bytes.NewReader(videoIconBytes))
if err != nil {
return nil, err
}
videoIcon = imaging.Resize(videoIcon, 90, 90, imaging.Lanczos)
return videoIcon, nil
}

// extractVideoScreenshot extracts a screenshot from a video using external
Expand Down
44 changes: 29 additions & 15 deletions media_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"testing"
"time"

packr "github.com/gobuffalo/packr/v2"
)

type timerType struct {
Expand All @@ -27,28 +29,32 @@ func LogTime(t *testing.T, whatWasMeasured string) {
}

func TestGetFiles(t *testing.T) {
media := createMedia("testmedia", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", ".", true, true)
files, err := media.getFiles("")
assertExpectNoErr(t, "", err)
assertTrue(t, "No files found", len(files) > 5)
}

func TestGetFilesInvalid(t *testing.T) {
media := createMedia("testmedia", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", ".", true, true)
files, err := media.getFiles("invalidfolder")
assertExpectErr(t, "invalid path shall give errors", err)
assertTrue(t, "Should not find any files", len(files) == 0)
}

func TestGetFilesHacker(t *testing.T) {
media := createMedia("testmedia", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", ".", true, true)
files, err := media.getFiles("../..")
assertExpectErr(t, "hacker path shall give errors", err)
assertTrue(t, "Should not find any files", len(files) == 0)
}

func TestIsRotationNeeded(t *testing.T) {
media := createMedia("testmedia", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", ".", true, true)

rotationNeeded := media.isRotationNeeded("exif_rotate/180deg.jpg")
assertTrue(t, "Rotation should be needed", rotationNeeded)
Expand Down Expand Up @@ -97,7 +103,8 @@ func TestRotateAndWrite(t *testing.T) {
outFileName := "tmpout/TestRotateAndWrite/jpeg_rotated_fixed.jpg"
os.MkdirAll("tmpout/TestRotateAndWrite", os.ModePerm) // If already exist no problem
os.Remove(outFileName)
media := createMedia("testmedia", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", ".", true, true)
outFile, err := os.Create(outFileName)
assertExpectNoErr(t, "unable to create out", err)
defer outFile.Close()
Expand Down Expand Up @@ -126,7 +133,8 @@ func tEXIFThumbnail(t *testing.T, media *Media, filename string) {

func TestWriteEXIFThumbnail(t *testing.T) {
os.MkdirAll("tmpout/TestWriteEXIFThumbnail", os.ModePerm) // If already exist no problem
media := createMedia("testmedia", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", ".", true, true)

tEXIFThumbnail(t, media, "normal.jpg")
tEXIFThumbnail(t, media, "180deg.jpg")
Expand All @@ -150,7 +158,8 @@ func TestWriteEXIFThumbnail(t *testing.T) {

func TestFullPath(t *testing.T) {
// Root path
media := createMedia(".", ".", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, ".", ".", true, true)
p, err := media.getFullMediaPath("afile.jpg")
assertExpectNoErr(t, "unable to get valid full path", err)
assertEqualsStr(t, "invalid path", "afile.jpg", p)
Expand All @@ -159,7 +168,7 @@ func TestFullPath(t *testing.T) {
assertExpectErr(t, "hackers shall not be allowed", err)

// Relative path
media = createMedia("arelative/path", ".", true, true)
media = createMedia(box, "arelative/path", ".", true, true)
p, err = media.getFullMediaPath("afile.jpg")
assertExpectNoErr(t, "unable to get valid full path", err)
assertEqualsStr(t, "invalid path", "arelative/path/afile.jpg", p)
Expand All @@ -168,7 +177,7 @@ func TestFullPath(t *testing.T) {
assertExpectErr(t, "hackers shall not be allowed", err)

// Absolute path
media = createMedia("/root/absolute/path", ".", true, true)
media = createMedia(box, "/root/absolute/path", ".", true, true)
p, err = media.getFullMediaPath("afile.jpg")
assertExpectNoErr(t, "unable to get valid full path", err)
assertEqualsStr(t, "invalid path", "/root/absolute/path/afile.jpg", p)
Expand All @@ -178,7 +187,8 @@ func TestFullPath(t *testing.T) {
}

func TestThumbnailPath(t *testing.T) {
media := createMedia("/c/mediapath", "/d/thumbpath", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "/c/mediapath", "/d/thumbpath", true, true)

thumbPath, err := media.thumbnailPath("myimage.jpg")
assertExpectNoErr(t, "", err)
Expand Down Expand Up @@ -213,7 +223,8 @@ func tGenerateImageThumbnail(t *testing.T, media *Media, inFileName, outFileName
func TestGenerateImageThumbnail(t *testing.T) {
os.MkdirAll("tmpout/TestGenerateImageThumbnail", os.ModePerm) // If already exist no problem

media := createMedia("", "", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "", "", true, true)

tGenerateImageThumbnail(t, media, "testmedia/jpeg.jpg", "tmpout/TestGenerateImageThumbnail/jpeg_thumbnail.jpg")
tGenerateImageThumbnail(t, media, "testmedia/jpeg_rotated.jpg", "tmpout/TestGenerateImageThumbnail/jpeg_rotated_thumbnail.jpg")
Expand Down Expand Up @@ -251,7 +262,8 @@ func TestWriteThumbnail(t *testing.T) {
os.MkdirAll("tmpout/TestWriteThumbnail", os.ModePerm) // If already exist no problem
os.RemoveAll("tmpout/TestWriteThumbnail/*")

media := createMedia("testmedia", "tmpcache/TestWriteThumbnail", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", "tmpcache/TestWriteThumbnail", true, true)

// JPEG with embedded EXIF
tWriteThumbnail(t, media, "jpeg.jpg", "tmpout/TestWriteThumbnail/jpeg.jpg", false)
Expand All @@ -274,7 +286,7 @@ func TestWriteThumbnail(t *testing.T) {
tWriteThumbnail(t, media, "invalid.jpg", "tmpout/TestWriteThumbnail/invalid.jpg", true)

// Disable thumb cache
media = createMedia("testmedia", "tmpcache/TestWriteThumbnail", false, true)
media = createMedia(box, "testmedia", "tmpcache/TestWriteThumbnail", false, true)

// JPEG with embedded EXIF
tWriteThumbnail(t, media, "jpeg.jpg", "tmpout/TestWriteThumbnail/jpeg.jpg", false)
Expand All @@ -291,7 +303,8 @@ func TestVideoThumbnailSupport(t *testing.T) {
ffmpegCmd = origCmd
}()

media := createMedia("", "", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "", "", true, true)

t.Logf("ffmpeg supported: %v", media.videoThumbnailSupport())

Expand Down Expand Up @@ -319,7 +332,8 @@ func tGenerateVideoThumbnail(t *testing.T, media *Media, inFileName, outFileName
}

func TestGenerateVideoThumbnail(t *testing.T) {
media := createMedia("", "", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "", "", true, true)
if !media.videoThumbnailSupport() {
t.Skip("ffmpeg not installed skipping test")
return
Expand Down
4 changes: 2 additions & 2 deletions webapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ func TestGetThumbnail(t *testing.T) {
}

func TestGetThumbnailNoCache(t *testing.T) {
media := createMedia("testmedia", "", false, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", "", false, true)
webAPI := CreateWebAPI(9834, "templates", media, box, "", "")
webAPI.Start()
waitserver(t)
Expand Down Expand Up @@ -256,8 +256,8 @@ func TestInvalidPath(t *testing.T) {
}

func TestAuthentication(t *testing.T) {
media := createMedia("testmedia", "", true, true)
box := packr.New("templates", "./templates")
media := createMedia(box, "testmedia", "", true, true)
webAPI := CreateWebAPI(9834, "templates", media, box, "myuser", "mypass")
webAPI.Start()
waitserver(t)
Expand Down

0 comments on commit 57b3b3d

Please sign in to comment.