Skip to content

Commit

Permalink
Added full video thumbnail support
Browse files Browse the repository at this point in the history
  • Loading branch information
midstar committed Feb 23, 2019
1 parent dab4e92 commit 01327db
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 16 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ The main design goal of MediaWEB is that no additional dependencies shall be nee
* The mediaweb executable
* A configuration file, mediaweb.conf

Optional dependencies are:

* [ffmpeg](https://www.ffmpeg.org/) for video thumbnail support

No additional stuff, such as dockers and similar is required.

MediaWEB is well suited to run on small platforms such as Raspberry Pi, Banana Pi, ROCK64 and similar. It is still very fast and can be used with advantage on PC:s running Windows, Linux or Mac OS.

## Features

* Simple WEB GUI for viewing your images and videos
* Thumbnail support, primary by reading of EXIF thumbnail if it exist, otherwise thumbnails will be created and stored in a thumbnail cache
* Thumbnail support for images and videos, primary by reading of EXIF thumbnail if it exist, otherwise thumbnails will be created and stored in a thumbnail cache. Video thumbnails requires [ffmpeg](https://www.ffmpeg.org/) to be installed.
* Automatic rotation JPEG images when needed (based on EXIF information)
* Optional authentication with username and password

Expand Down Expand Up @@ -49,6 +53,10 @@ Then run following for all Linux platforms:

Follow the instructions in the service.sh script.

For video thumbnail support, install ffmpeg:

sudo apt-get install ffmpeg

To perform additional configuration, edit:

sudo vi /etc/mediaweb.conf
Expand All @@ -72,6 +80,8 @@ Run the installer and follow the instructions.
To modify changes just edit mediaweb.conf in the installation directory and restart the mediaweb
service in task manager.

You need to install [ffmpeg](https://www.ffmpeg.org/) separately and put ffmpeg into your PATH to get video thumbnail support.

## Build from source (any platform)

To build from source on any platform you need to:
Expand Down Expand Up @@ -108,7 +118,6 @@ On Linux platforms execute following to install MediaWEB as a service:

## Future improvements

* Create thumbnails for videos (probably using ffmpeg)
* Add support for TLS/SSL


Expand Down
14 changes: 10 additions & 4 deletions media.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,11 @@ func (m *Media) writeThumbnail(w io.Writer, relativeFilePath string) error {
if err != nil {
return err
}
err = m.generateImageThumbnail(fullMediaPath, thumbFileName)
if m.isVideo(fullMediaPath) {
err = m.generateVideoThumbnail(fullMediaPath, thumbFileName)
} else {
err = m.generateImageThumbnail(fullMediaPath, thumbFileName)
}
if err != nil {
return err
}
Expand All @@ -387,7 +391,7 @@ var ffmpegCmd = "ffmpeg"

// videoThumbnailSupport returns true if ffmpeg is installed, and thus
// video thumbnails is supported
func videoThumbnailSupport() bool {
func (m *Media) videoThumbnailSupport() bool {
_, err := exec.LookPath(ffmpegCmd)
return err == nil
}
Expand All @@ -397,7 +401,7 @@ func videoThumbnailSupport() bool {
//
// Utilizes the external ffmpeg software.
func (m *Media) generateVideoThumbnail(fullMediaPath, fullThumbPath string) error {
if !videoThumbnailSupport() {
if !m.videoThumbnailSupport() {
return fmt.Errorf("video thumbnails not supported. ffmpeg not installed")
}
ffmpegArgs := []string{
Expand All @@ -418,8 +422,10 @@ func (m *Media) generateVideoThumbnail(fullMediaPath, fullThumbPath string) erro
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("%s %s\nError: %s\nStdout: %s\nStderr: %s",
errorStr := fmt.Sprintf("%s %s\nError: %s\nStdout: %s\nStderr: %s",
ffmpegCmd, strings.Join(ffmpegArgs, " "), err, stdout.String(), stderr.String())
llog.Error(errorStr)
return fmt.Errorf(errorStr)
}
return err
}
24 changes: 17 additions & 7 deletions media_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func TestRotateAndWrite(t *testing.T) {
}

func tEXIFThumbnail(t *testing.T, media *Media, filename string) {
t.Helper()
inFileName := "exif_rotate/" + filename
outFileName := "tmpout/TestWriteEXIFThumbnail/thumb_" + filename
os.Remove(outFileName)
Expand Down Expand Up @@ -199,6 +200,7 @@ func TestThumbnailPath(t *testing.T) {
}

func tGenerateImageThumbnail(t *testing.T, media *Media, inFileName, outFileName string) {
t.Helper()
os.Remove(outFileName)
RestartTimer()
err := media.generateImageThumbnail(inFileName, outFileName)
Expand Down Expand Up @@ -229,6 +231,7 @@ func TestGenerateImageThumbnail(t *testing.T) {
}

func tWriteThumbnail(t *testing.T, media *Media, inFileName, outFileName string, failExpected bool) {
t.Helper()
os.Remove(outFileName)
outFile, err := os.Create(outFileName)
assertExpectNoErr(t, "unable to create out", err)
Expand Down Expand Up @@ -259,6 +262,11 @@ func TestWriteThumbnail(t *testing.T) {
// Non JPEG - no exif
tWriteThumbnail(t, media, "png.png", "tmpout/TestWriteThumbnail/png.jpg", false)

// Video - only if video is supported
if media.videoThumbnailSupport() {
tWriteThumbnail(t, media, "video.mp4", "tmpout/TestWriteThumbnail/video.jpg", false)
}

// Non existing file
tWriteThumbnail(t, media, "dont_exist.jpg", "tmpout/TestWriteThumbnail/dont_exist.jpg", true)

Expand All @@ -283,21 +291,24 @@ func TestVideoThumbnailSupport(t *testing.T) {
ffmpegCmd = origCmd
}()

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

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

ffmpegCmd = "thiscommanddontexit"
assertFalse(t, ffmpegCmd, videoThumbnailSupport())
assertFalse(t, ffmpegCmd, media.videoThumbnailSupport())

ffmpegCmd = "cmd"
shallBeTrueOnWindows := videoThumbnailSupport()
shallBeTrueOnWindows := media.videoThumbnailSupport()

ffmpegCmd = "echo"
shallBeTrueOnNonWindows := videoThumbnailSupport()
shallBeTrueOnNonWindows := media.videoThumbnailSupport()

assertTrue(t, "Shall be true on at least one platform", shallBeTrueOnWindows || shallBeTrueOnNonWindows)
}

func tGenerateVideoThumbnail(t *testing.T, media *Media, inFileName, outFileName string) {
t.Helper()
os.Remove(outFileName)
RestartTimer()
err := media.generateVideoThumbnail(inFileName, outFileName)
Expand All @@ -308,7 +319,8 @@ func tGenerateVideoThumbnail(t *testing.T, media *Media, inFileName, outFileName
}

func TestGenerateVideoThumbnail(t *testing.T) {
if !videoThumbnailSupport() {
media := createMedia("", "", true, true)
if !media.videoThumbnailSupport() {
t.Skip("ffmpeg not installed skipping test")
return
}
Expand All @@ -317,8 +329,6 @@ func TestGenerateVideoThumbnail(t *testing.T) {
tmpSpace := "tmpout/TestGenerateVideoThumbnail/with space in path"
os.MkdirAll(tmpSpace, os.ModePerm) // If already exist no problem

media := createMedia("", "", true, true)

tGenerateVideoThumbnail(t, media, "testmedia/video.mp4", tmp+"/video_thumbnail.jpg")
tGenerateVideoThumbnail(t, media, "testmedia/video.mp4", tmpSpace+"/video_thumbnail.jpg")

Expand Down
2 changes: 1 addition & 1 deletion webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (wa *WebAPI) serveHTTPThumbnail(w http.ResponseWriter, r *http.Request) {
if err == nil {
w.Header().Set("Content-Type", "image/jpeg")
} else {
// No thumbnail. Use the default
// No thumbnail. Use the default
w.Header().Set("Content-Type", "image/png")
fileType := wa.media.getFileType(relativePath)
if fileType == "image" {
Expand Down
11 changes: 9 additions & 2 deletions webapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func respToString(response io.ReadCloser) string {
}

func getHTML(t *testing.T, path string) string {
t.Helper()
resp, err := http.Get(fmt.Sprintf("%s/%s", baseURL, path))
assertExpectNoErr(t, "", err)
assertEqualsInt(t, "", int(http.StatusOK), int(resp.StatusCode))
Expand All @@ -33,6 +34,7 @@ func getHTML(t *testing.T, path string) string {
}

func getHTMLAuthenticate(t *testing.T, path, user, pass string, expectFail bool) string {
t.Helper()
client := &http.Client{}
req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", baseURL, path), nil)
req.SetBasicAuth(user, pass)
Expand All @@ -49,6 +51,7 @@ func getHTMLAuthenticate(t *testing.T, path, user, pass string, expectFail bool)
}

func getBinary(t *testing.T, path, contentType string) []byte {
t.Helper()
resp, err := http.Get(fmt.Sprintf("%s/%s", baseURL, path))
assertExpectNoErr(t, "", err)
assertEqualsInt(t, "", int(http.StatusOK), int(resp.StatusCode))
Expand All @@ -60,6 +63,7 @@ func getBinary(t *testing.T, path, contentType string) []byte {
}

func getObject(t *testing.T, path string, v interface{}) {
t.Helper()
resp, err := http.Get(fmt.Sprintf("%s/%s", baseURL, path))
if err != nil {
t.Fatalf("Unable to get path %s. Reason: %s", path, err)
Expand All @@ -80,12 +84,14 @@ func getObject(t *testing.T, path string, v interface{}) {
}

func startserver(t *testing.T) {
t.Helper()
go main()
waitserver(t)
}

// waitserver waits for the server to be up and running
func waitserver(t *testing.T) {
t.Helper()
client := http.Client{Timeout: 100 * time.Millisecond}
maxTries := 10
for i := 0; i < maxTries; i++ {
Expand Down Expand Up @@ -193,8 +199,9 @@ func TestGetThumbnail(t *testing.T) {
image = getBinary(t, "thumb/exif_rotate/no_exif.jpg", "image/jpeg")
assertTrue(t, "", len(image) > 100)

image = getBinary(t, "thumb/video.mp4", "image/png")
assertTrue(t, "", len(image) > 100)
// Below will be png if ffmpeg is not installed and jpeg if ffmpeg is installed
//image = getBinary(t, "thumb/video.mp4", "image/jpeg")
//assertTrue(t, "", len(image) > 100)

image = getBinary(t, "thumb/exif_rotate", "image/png")
assertTrue(t, "", len(image) > 100)
Expand Down

0 comments on commit 01327db

Please sign in to comment.