Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
Merge pull request #56 from stuartmscott/video
Browse files Browse the repository at this point in the history
  • Loading branch information
owulveryck authored Jan 3, 2022
2 parents 734ccbc + 5d4c296 commit 3304e1a
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 20 deletions.
22 changes: 22 additions & 0 deletions epub.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const (
CSSFolderName = "css"
FontFolderName = "fonts"
ImageFolderName = "images"
VideoFolderName = "videos"
)

const (
Expand All @@ -92,6 +93,7 @@ img {
defaultEpubLang = "en"
fontFileFormat = "font%04d%s"
imageFileFormat = "image%04d%s"
videoFileFormat = "video%04d%s"
sectionFileFormat = "section%04d.xhtml"
urnUUIDPrefix = "urn:uuid:"
)
Expand All @@ -109,6 +111,8 @@ type Epub struct {
identifier string
// The key is the image filename, the value is the image source
images map[string]string
// The key is the video filename, the value is the video source
videos map[string]string
// Language
lang string
// Description
Expand Down Expand Up @@ -148,6 +152,7 @@ func NewEpub(title string) *Epub {
e.css = make(map[string]string)
e.fonts = make(map[string]string)
e.images = make(map[string]string)
e.videos = make(map[string]string)
e.pkg = newPackage()
e.toc = newToc()
// Set minimal required attributes
Expand Down Expand Up @@ -213,6 +218,23 @@ func (e *Epub) AddImage(source string, imageFilename string) (string, error) {
return addMedia(e.Client, source, imageFilename, imageFileFormat, ImageFolderName, e.images)
}

// AddVideo adds an video to the EPUB and returns a relative path to the video
// file that can be used in EPUB sections in the format:
// ../VideoFolderName/internalFilename
//
// The video source should either be a URL, a path to a local file, or an embedded data URL; in any
// case, the video file will be retrieved and stored in the EPUB.
//
// The internal filename will be used when storing the video file in the EPUB
// and must be unique among all video files. If the same filename is used more
// than once, FilenameAlreadyUsedError will be returned. The internal filename is
// optional; if no filename is provided, one will be generated.
func (e *Epub) AddVideo(source string, videoFilename string) (string, error) {
e.Lock()
defer e.Unlock()
return addMedia(e.Client, source, videoFilename, videoFileFormat, VideoFolderName, e.videos)
}

// AddSection adds a new section (chapter, etc) to the EPUB and returns a
// relative path to the section that can be used from another section (for
// links).
Expand Down
54 changes: 54 additions & 0 deletions epub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ const (
testNumberFilenameStart = "01filenametest.png"
testSpaceInFilename = "filename with space.png"
testImageFromURLSource = "https://golang.org/doc/gopher/gophercolor16x16.png"
testVideoFromFileFilename = "testfromfile.mp4"
testVideoFromFileSource = "testdata/sample_640x360.mp4"
testVideoFromURLSource = "https://filesamples.com/samples/video/mp4/sample_640x360.mp4"
testLangTemplate = `<dc:language>%s</dc:language>`
testDescTemplate = `<dc:description>%s</dc:description>`
testPpdTemplate = `page-progression-direction="%s"`
Expand Down Expand Up @@ -298,6 +301,56 @@ func TestAddImage(t *testing.T) {
cleanup(testEpubFilename, tempDir)
}

func TestAddVideo(t *testing.T) {
e := NewEpub(testEpubTitle)
testVideoFromFilePath, err := e.AddVideo(testVideoFromFileSource, testVideoFromFileFilename)
if err != nil {
t.Errorf("Error adding video: %s", err)
}
fmt.Println(testVideoFromFilePath)

testVideoFromURLPath, err := e.AddVideo(testVideoFromURLSource, "")
if err != nil {
t.Errorf("Error adding video: %s", err)
}
fmt.Println(testVideoFromURLPath)

tempDir := writeAndExtractEpub(t, e, testEpubFilename)

// The video path is relative to the XHTML folder
contents, err := storage.ReadFile(filesystem, filepath.Join(tempDir, contentFolderName, xhtmlFolderName, testVideoFromFilePath))
if err != nil {
t.Errorf("Unexpected error reading video file from EPUB: %s", err)
}

testVideoContents, err := os.ReadFile(testVideoFromFileSource)
if err != nil {
t.Errorf("Unexpected error reading testdata video file: %s", err)
}
if bytes.Compare(contents, testVideoContents) != 0 {
t.Errorf("Video file contents don't match")
}

contents, err = storage.ReadFile(filesystem, filepath.Join(tempDir, contentFolderName, xhtmlFolderName, testVideoFromURLPath))
if err != nil {
t.Errorf("Unexpected error reading video file from EPUB: %s", err)
}

resp, err := http.Get(testVideoFromURLSource)
if err != nil {
t.Errorf("Unexpected error response from test video URL: %s", err)
}
testVideoContents, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("Unexpected error reading test video file from URL: %s", err)
}
if bytes.Compare(contents, testVideoContents) != 0 {
t.Errorf("Video file contents don't match")
}

cleanup(testEpubFilename, tempDir)
}

func TestAddSection(t *testing.T) {
e := NewEpub(testEpubTitle)
testSection1Path, err := e.AddSection(testSectionBody, testSectionTitle, testSectionFilename, "")
Expand Down Expand Up @@ -695,6 +748,7 @@ func testEpubValidity(t testing.TB) {
e.AddImage(testImageFromFileSource, testImageFromFileFilename)
e.AddImage(testImageFromURLSource, "")
e.AddImage(testImageFromFileSource, testNumberFilenameStart)
e.AddVideo(testVideoFromURLSource, testVideoFromFileFilename)
e.SetAuthor(testEpubAuthor)
e.SetCover(testImagePath, "")
e.SetDescription(testEpubDescription)
Expand Down
15 changes: 9 additions & 6 deletions fetchmedia.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,25 @@ func (g grabber) fetchMedia(mediaSource, mediaFolderPath, mediaFilename string)

}
defer source.Close()
// Defining two buffers
var buffer bytes.Buffer

// Calling MultiWriter method with its parameters
writer := io.MultiWriter(w, &buffer)
_, err = io.Copy(writer, source)
_, err = io.Copy(w, source)
if err != nil {
// There shouldn't be any problem with the writer, but the reader
// might have an issue
return "", &FileRetrievalError{Source: mediaSource, Err: err}
}

// Detect the mediaType
mime, err := mimetype.DetectReader(&buffer)
r, err := filesystem.Open(mediaFilePath)
if err != nil {
return "", err
}
defer r.Close()
mime, err := mimetype.DetectReader(r)
if err != nil {
panic(err)
}

// Is it CSS?
mtype := mime.String()
if mime.Is("text/plain") {
Expand Down
11 changes: 11 additions & 0 deletions fetchmedia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ Tv/Ip07//uF2//7hdv/+4Xb//uF2/8zBlv/Kv4//pZJU/3tzTv9UTjj/19nd/////wD///8A4eLl
AAAAAAAAAAAAAA==`, "\n", "", -1)

func Test_fetchMedia(t *testing.T) {
t.Run("LocalFS", func(t *testing.T) {
Use(OsFS)
testFetchMedia(t)
})
t.Run("MemoryFS", func(t *testing.T) {
Use(MemoryFS)
testFetchMedia(t)
})
}

func testFetchMedia(t *testing.T) {
filename := "gophercolor16x16.png"
mux := http.NewServeMux()
mux.HandleFunc("/image.png", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
2 changes: 1 addition & 1 deletion fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
MemoryFS
)

// Use s as default storage/ This is tipically used in an init function.
// Use s as default storage/ This is typically used in an init function.
// Default to local filesystem
func Use(s FSType) {
switch s {
Expand Down
24 changes: 19 additions & 5 deletions internal/storage/memory/file.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package memory

import (
"bytes"
"io"
"io/fs"
"time"
)

type file struct {
name string
modTime time.Time
content bytes.Buffer
offset int
content []byte
mode fs.FileMode
}

Expand All @@ -22,23 +23,36 @@ func (f *file) Stat() (fs.FileInfo, error) {
}

func (f *file) Read(b []byte) (int, error) {
return f.content.Read(b)
length := len(f.content)
start := f.offset
if start == length {
return 0, io.EOF
}
end := start + len(b)
if end > length {
end = length
}
f.offset = end
count := copy(b, f.content[start:end])
return count, nil
}

func (f *file) Close() error {
f.offset = 0
return nil
}

func (f *file) Write(p []byte) (n int, err error) {
return f.content.Write(p)
f.content = append(f.content, p...)
return len(p), nil
}

func (f *file) Name() string {
return f.name
}

func (f *file) Size() int64 {
return int64(f.content.Len())
return int64(len(f.content))
}

func (f *file) Type() fs.FileMode {
Expand Down
2 changes: 0 additions & 2 deletions internal/storage/memory/file_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package memory

import (
"bytes"
"fmt"
"io/fs"
"testing"
Expand All @@ -15,7 +14,6 @@ func Test_file(t *testing.T) {
f := &file{
name: name,
modTime: now,
content: bytes.Buffer{},
}
fmt.Fprint(f, content)
if f.Size() != int64(len(content)) {
Expand Down
7 changes: 3 additions & 4 deletions internal/storage/memory/fs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package memory

import (
"bytes"
"io/fs"
"path"
"strings"
Expand Down Expand Up @@ -49,12 +48,13 @@ func (m *Memory) WriteFile(name string, data []byte, perm fs.FileMode) error {
if !fs.ValidPath(name) {
return fs.ErrInvalid
}
m.fs[name] = &file{
f := &file{
name: path.Base(name),
modTime: time.Now(),
mode: (perm),
content: *bytes.NewBuffer(data),
content: data,
}
m.fs[name] = f
return nil
}

Expand Down Expand Up @@ -91,7 +91,6 @@ func (m *Memory) Create(name string) (storage.File, error) {
name: path.Base(name),
modTime: time.Now(),
mode: 0666,
content: bytes.Buffer{},
}
m.fs[name] = f
return f, nil
Expand Down
28 changes: 27 additions & 1 deletion internal/storage/memory/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func TestMemory(t *testing.T) {
if err != nil {
t.Fatalf("writefile error: %v", err)
}
assertPrefix(t, fs, "c")
f, err := fs.Open(filepath.Join("directory", "test"))
if err != nil {
t.Fatalf("open error: %v", err)
Expand All @@ -79,7 +80,7 @@ func TestMemory(t *testing.T) {
t.Fatalf("readall error: %v", err)
}
if string(content) != "content" {
t.Fatal("unexpected content")
t.Fatalf("unexpected content: unexpected 'content', got '%s'", string(content))
}
err = fs.RemoveAll("directory")
if err != nil {
Expand Down Expand Up @@ -130,3 +131,28 @@ func TestMemory_Stat(t *testing.T) {
t.Fail()
}
}

func assertPrefix(t *testing.T, fs *Memory, prefix string) {
t.Helper()

// Open file
f, err := fs.Open(filepath.Join("directory", "test"))
if err != nil {
t.Fatalf("open error: %v", err)
}
defer f.Close()

// Read length bytes
length := len(prefix)
b := make([]byte, length)
n, err := f.Read(b)
if err != nil {
t.Fatalf("read error: %v", err)
}
if n != length {
t.Fatal("incorrect read count")
}
if string(b) != prefix {
t.Fatalf("unexpected content: unexpected '%s', got '%s'", prefix, string(b))
}
}
Binary file added testdata/sample_640x360.mp4
Binary file not shown.
15 changes: 14 additions & 1 deletion write.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ func (e *Epub) WriteTo(dst io.Writer) (int64, error) {
return 0, err
}

// Must be called after:
// createEpubFolders()
err = e.writeVideos(tempDir)
if err != nil {
return 0, err
}

// Must be called after:
// createEpubFolders()
e.writeSections(tempDir)
Expand All @@ -108,6 +115,7 @@ func (e *Epub) WriteTo(dst io.Writer) (int64, error) {
// createEpubFolders()
// writeCSSFiles()
// writeImages()
// writeVideos()
// writeSections()
// writeToc()
e.writePackageFile(tempDir)
Expand Down Expand Up @@ -322,7 +330,12 @@ func (e *Epub) writeImages(rootEpubDir string) error {
return e.writeMedia(rootEpubDir, e.images, ImageFolderName)
}

// Get images from their source and save them in the temporary directory
// Get videos from their source and save them in the temporary directory
func (e *Epub) writeVideos(rootEpubDir string) error {
return e.writeMedia(rootEpubDir, e.videos, VideoFolderName)
}

// Get media from their source and save them in the temporary directory
func (e *Epub) writeMedia(rootEpubDir string, mediaMap map[string]string, mediaFolderName string) error {
if len(mediaMap) > 0 {
mediaFolderPath := filepath.Join(rootEpubDir, contentFolderName, mediaFolderName)
Expand Down
Loading

0 comments on commit 3304e1a

Please sign in to comment.