mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-12-18 16:53:50 +00:00
3d3e99ae52
* update to use go-storage/ instead of go-store/v2/storage/
* pull in latest version from codeberg
* remove test output 😇
* add code comments
* set the exclusive bit when creating new files in disk config
* bump to actual release version
* bump to v0.1.1 (tis a simple no-logic change)
* update readme
* only use a temporary read seeker when decoding video if required (should only be S3 now)
* use fastcopy library to use memory pooled buffers when calling TempFileSeeker()
* update to use seek call in serveFileRange()
141 lines
3.9 KiB
Go
141 lines
3.9 KiB
Go
// GoToSocial
|
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package media
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/abema/go-mp4"
|
|
"github.com/superseriousbusiness/gotosocial/internal/iotools"
|
|
"github.com/superseriousbusiness/gotosocial/internal/log"
|
|
)
|
|
|
|
type gtsVideo struct {
|
|
frame *gtsImage
|
|
duration float32 // in seconds
|
|
bitrate uint64
|
|
framerate float32
|
|
}
|
|
|
|
// 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) {
|
|
// Check if video stream supports
|
|
// seeking, usually when *os.File.
|
|
rsc, ok := r.(io.ReadSeekCloser)
|
|
if !ok {
|
|
var err error
|
|
|
|
// Store stream to temporary location
|
|
// in order that we can get seek-reads.
|
|
rsc, err = iotools.TempFileSeeker(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating temp file seeker: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
// Ensure temp. read seeker closed.
|
|
if err := rsc.Close(); err != nil {
|
|
log.Errorf(nil, "error closing temp file seeker: %s", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// 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(rsc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error during mp4 probe: %w", err)
|
|
}
|
|
|
|
var (
|
|
width int
|
|
height int
|
|
videoBitrate uint64
|
|
audioBitrate uint64
|
|
video gtsVideo
|
|
)
|
|
|
|
for _, tr := range info.Tracks {
|
|
if tr.AVC == nil {
|
|
// audio track
|
|
if br := tr.Samples.GetBitrate(tr.Timescale); br > audioBitrate {
|
|
audioBitrate = br
|
|
} else if br := info.Segments.GetBitrate(tr.TrackID, tr.Timescale); br > audioBitrate {
|
|
audioBitrate = br
|
|
}
|
|
|
|
if d := float64(tr.Duration) / float64(tr.Timescale); d > float64(video.duration) {
|
|
video.duration = float32(d)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// video track
|
|
if w := int(tr.AVC.Width); w > width {
|
|
width = w
|
|
}
|
|
|
|
if h := int(tr.AVC.Height); h > height {
|
|
height = h
|
|
}
|
|
|
|
if br := tr.Samples.GetBitrate(tr.Timescale); br > videoBitrate {
|
|
videoBitrate = br
|
|
} else if br := info.Segments.GetBitrate(tr.TrackID, tr.Timescale); br > videoBitrate {
|
|
videoBitrate = br
|
|
}
|
|
|
|
if d := float64(tr.Duration) / float64(tr.Timescale); d > float64(video.duration) {
|
|
video.framerate = float32(len(tr.Samples)) / float32(d)
|
|
video.duration = float32(d)
|
|
}
|
|
}
|
|
|
|
// overall bitrate should be audio + video combined
|
|
// (since they're both playing at the same time)
|
|
video.bitrate = audioBitrate + videoBitrate
|
|
|
|
// Check for empty video metadata.
|
|
var empty []string
|
|
if width == 0 {
|
|
empty = append(empty, "width")
|
|
}
|
|
if height == 0 {
|
|
empty = append(empty, "height")
|
|
}
|
|
if video.duration == 0 {
|
|
empty = append(empty, "duration")
|
|
}
|
|
if video.framerate == 0 {
|
|
empty = append(empty, "framerate")
|
|
}
|
|
if video.bitrate == 0 {
|
|
empty = append(empty, "bitrate")
|
|
}
|
|
if len(empty) > 0 {
|
|
return nil, fmt.Errorf("error determining video metadata: %v", empty)
|
|
}
|
|
|
|
// Create new empty "frame" image.
|
|
// TODO: decode frame from video file.
|
|
video.frame = blankImage(width, height)
|
|
|
|
return &video, nil
|
|
}
|