summaryrefslogtreecommitdiff
path: root/internal/media/video.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/media/video.go')
-rw-r--r--internal/media/video.go130
1 files changed, 46 insertions, 84 deletions
diff --git a/internal/media/video.go b/internal/media/video.go
index bd624559b..bffdfbbba 100644
--- a/internal/media/video.go
+++ b/internal/media/video.go
@@ -19,63 +19,55 @@
package media
import (
- "bytes"
"fmt"
- "image"
- "image/color"
- "image/draw"
- "image/jpeg"
"io"
"os"
"github.com/abema/go-mp4"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/superseriousbusiness/gotosocial/internal/log"
)
-var thumbFill = color.RGBA{42, 43, 47, 0} // the color to fill video thumbnails with
+type gtsVideo struct {
+ frame *gtsImage
+ duration float32 // in seconds
+ bitrate uint64
+ framerate float32
+}
-func decodeVideo(r io.Reader, contentType string) (*mediaMeta, error) {
+// decodeVideoFrame decodes and returns an image from a single frame in the given video stream.
+// (note: currently this only returns a blank image resized to fit video dimensions).
+func decodeVideoFrame(r io.Reader) (*gtsVideo, error) {
// We'll need a readseeker to decode the video. We can get a readseeker
// without burning too much mem by first copying the reader into a temp file.
// First create the file in the temporary directory...
- tempFile, err := os.CreateTemp(os.TempDir(), "gotosocial-")
+ tmp, err := os.CreateTemp(os.TempDir(), "gotosocial-")
if err != nil {
- return nil, fmt.Errorf("could not create temporary file while decoding video: %w", err)
+ return nil, err
}
- tempFileName := tempFile.Name()
- // Make sure to clean up the temporary file when we're done with it
defer func() {
- if err := tempFile.Close(); err != nil {
- log.Errorf("could not close file %s: %s", tempFileName, err)
- }
- if err := os.Remove(tempFileName); err != nil {
- log.Errorf("could not remove file %s: %s", tempFileName, err)
- }
+ tmp.Close()
+ os.Remove(tmp.Name())
}()
// Now copy the entire reader we've been provided into the
// temporary file; we won't use the reader again after this.
- if _, err := io.Copy(tempFile, r); err != nil {
- return nil, fmt.Errorf("could not copy video reader into temporary file %s: %w", tempFileName, err)
+ if _, err := io.Copy(tmp, r); err != nil {
+ return nil, err
}
- var (
- width int
- height int
- duration float32
- framerate float32
- bitrate uint64
- )
-
// probe the video file to extract useful metadata from it; for methodology, see:
// https://github.com/abema/go-mp4/blob/7d8e5a7c5e644e0394261b0cf72fef79ce246d31/mp4tool/probe/probe.go#L85-L154
- info, err := mp4.Probe(tempFile)
+ info, err := mp4.Probe(tmp)
if err != nil {
- return nil, fmt.Errorf("could not probe temporary video file %s: %w", tempFileName, err)
+ return nil, fmt.Errorf("error probing tmp file %s: %w", tmp.Name(), err)
}
+ var (
+ width int
+ height int
+ video gtsVideo
+ )
+
for _, tr := range info.Tracks {
if tr.AVC == nil {
continue
@@ -89,72 +81,42 @@ func decodeVideo(r io.Reader, contentType string) (*mediaMeta, error) {
height = h
}
- if br := tr.Samples.GetBitrate(tr.Timescale); br > bitrate {
- bitrate = br
- } else if br := info.Segments.GetBitrate(tr.TrackID, tr.Timescale); br > bitrate {
- bitrate = br
+ if br := tr.Samples.GetBitrate(tr.Timescale); br > video.bitrate {
+ video.bitrate = br
+ } else if br := info.Segments.GetBitrate(tr.TrackID, tr.Timescale); br > video.bitrate {
+ video.bitrate = br
}
- if d := float32(tr.Duration) / float32(tr.Timescale); d > duration {
- duration = d
- framerate = float32(len(tr.Samples)) / duration
+ if d := float64(tr.Duration) / float64(tr.Timescale); d > float64(video.duration) {
+ video.framerate = float32(len(tr.Samples)) / float32(d)
+ video.duration = float32(d)
}
}
- var errs gtserror.MultiError
+ // Check for empty video metadata.
+ var empty []string
if width == 0 {
- errs = append(errs, "video width could not be discovered")
+ empty = append(empty, "width")
}
-
if height == 0 {
- errs = append(errs, "video height could not be discovered")
+ empty = append(empty, "height")
}
-
- if duration == 0 {
- errs = append(errs, "video duration could not be discovered")
+ if video.duration == 0 {
+ empty = append(empty, "duration")
}
-
- if framerate == 0 {
- errs = append(errs, "video framerate could not be discovered")
+ if video.framerate == 0 {
+ empty = append(empty, "framerate")
}
-
- if bitrate == 0 {
- errs = append(errs, "video bitrate could not be discovered")
+ if video.bitrate == 0 {
+ empty = append(empty, "bitrate")
}
-
- if errs != nil {
- return nil, errs.Combine()
+ if len(empty) > 0 {
+ return nil, fmt.Errorf("error determining video metadata: %v", empty)
}
- return &mediaMeta{
- width: width,
- height: height,
- duration: duration,
- framerate: framerate,
- bitrate: bitrate,
- size: height * width,
- aspect: float32(width) / float32(height),
- }, nil
-}
-
-func deriveThumbnailFromVideo(height int, width int) (*mediaMeta, error) {
- // create a rectangle with the same dimensions as the video
- img := image.NewRGBA(image.Rect(0, 0, width, height))
-
- // fill the rectangle with our desired fill color
- draw.Draw(img, img.Bounds(), &image.Uniform{thumbFill}, image.Point{}, draw.Src)
-
- // we can get away with using extremely poor quality for this monocolor thumbnail
- out := &bytes.Buffer{}
- if err := jpeg.Encode(out, img, &jpeg.Options{Quality: 1}); err != nil {
- return nil, fmt.Errorf("error encoding video thumbnail: %w", err)
- }
+ // Create new empty "frame" image.
+ // TODO: decode frame from video file.
+ video.frame = blankImage(width, height)
- return &mediaMeta{
- width: width,
- height: height,
- size: width * height,
- aspect: float32(width) / float32(height),
- small: out.Bytes(),
- }, nil
+ return &video, nil
}