summaryrefslogtreecommitdiff
path: root/internal/media
diff options
context:
space:
mode:
Diffstat (limited to 'internal/media')
-rw-r--r--internal/media/ffmpeg.go48
-rw-r--r--internal/media/ffmpeg/wasm.go36
-rw-r--r--internal/media/metadata.go20
-rw-r--r--internal/media/probe.go4
-rw-r--r--internal/media/thumbnail.go12
-rw-r--r--internal/media/util.go88
6 files changed, 111 insertions, 97 deletions
diff --git a/internal/media/ffmpeg.go b/internal/media/ffmpeg.go
index d98e93baf..938a10894 100644
--- a/internal/media/ffmpeg.go
+++ b/internal/media/ffmpeg.go
@@ -21,8 +21,6 @@ import (
"context"
"encoding/json"
"errors"
- "os"
- "path"
"strconv"
"strings"
@@ -158,34 +156,20 @@ func ffmpeg(ctx context.Context, inpath string, outpath string, args ...string)
Config: func(modcfg wazero.ModuleConfig) wazero.ModuleConfig {
fscfg := wazero.NewFSConfig()
- // Needs read-only access to
- // /dev/urandom for some types.
- urandom := &allowFiles{
- {
- abs: "/dev/urandom",
- flag: os.O_RDONLY,
- perm: 0,
- },
- }
- fscfg = fscfg.WithFSMount(urandom, "/dev")
+ // Needs read-only access /dev/urandom,
+ // required by some ffmpeg operations.
+ fscfg = fscfg.WithFSMount(&allowFiles{
+ allowRead("/dev/urandom"),
+ }, "/dev")
// In+out dirs are always the same (tmp),
// so we can share one file system for
// both + grant different perms to inpath
// (read only) and outpath (read+write).
- shared := &allowFiles{
- {
- abs: inpath,
- flag: os.O_RDONLY,
- perm: 0,
- },
- {
- abs: outpath,
- flag: os.O_RDWR | os.O_CREATE | os.O_TRUNC,
- perm: 0666,
- },
- }
- fscfg = fscfg.WithFSMount(shared, path.Dir(inpath))
+ fscfg = fscfg.WithFSMount(&allowFiles{
+ allowCreate(outpath),
+ allowRead(inpath),
+ }, tmpdir)
// Set anonymous module name.
modcfg = modcfg.WithName("")
@@ -246,16 +230,10 @@ func ffprobe(ctx context.Context, filepath string) (*result, error) {
Config: func(modcfg wazero.ModuleConfig) wazero.ModuleConfig {
fscfg := wazero.NewFSConfig()
- // Needs read-only access
- // to file being probed.
- in := &allowFiles{
- {
- abs: filepath,
- flag: os.O_RDONLY,
- perm: 0,
- },
- }
- fscfg = fscfg.WithFSMount(in, path.Dir(filepath))
+ // Needs read-only access to probed file.
+ fscfg = fscfg.WithFSMount(&allowFiles{
+ allowRead(filepath),
+ }, tmpdir)
// Set anonymous module name.
modcfg = modcfg.WithName("")
diff --git a/internal/media/ffmpeg/wasm.go b/internal/media/ffmpeg/wasm.go
index 1cd92f05d..bcf73725e 100644
--- a/internal/media/ffmpeg/wasm.go
+++ b/internal/media/ffmpeg/wasm.go
@@ -21,12 +21,12 @@ package ffmpeg
import (
"context"
+ "errors"
"os"
"runtime"
"sync/atomic"
"unsafe"
- "code.superseriousbusiness.org/gotosocial/internal/log"
"codeberg.org/gruf/go-ffmpreg/embed"
"codeberg.org/gruf/go-ffmpreg/wasm"
"github.com/tetratelabs/wazero"
@@ -49,24 +49,19 @@ func initWASM(ctx context.Context) error {
return nil
}
- var cfg wazero.RuntimeConfig
-
- // Allocate new runtime config, letting
- // wazero determine compiler / interpreter.
- cfg = wazero.NewRuntimeConfig()
-
- // Though still perform a check of CPU features at
- // runtime to warn about slow interpreter performance.
- if reason, supported := compilerSupported(); !supported {
- log.Warn(ctx, "!!! WAZERO COMPILER MAY NOT BE AVAILABLE !!!"+
- " Reason: "+reason+"."+
- " Wazero will likely fall back to interpreter mode,"+
- " resulting in poor performance for media processing (and SQLite, if in use)."+
- " For more info and possible workarounds, please check:"+
- " https://docs.gotosocial.org/en/latest/getting_started/releases/#supported-platforms",
- )
+ // Check at runtime whether Wazero compiler support is available,
+ // interpreter mode is too slow for a usable gotosocial experience.
+ if reason, supported := isCompilerSupported(); !supported {
+ return errors.New("!!! WAZERO COMPILER SUPPORT NOT AVAILABLE !!!" +
+ " Reason: " + reason + "." +
+ " Wazero in interpreter mode is too slow to use ffmpeg" +
+ " (this will also affect SQLite if in use)." +
+ " For more info and possible workarounds, please check: https://docs.gotosocial.org/en/latest/getting_started/releases/#supported-platforms")
}
+ // Allocate new runtime compiler config.
+ cfg := wazero.NewRuntimeConfigCompiler()
+
if dir := os.Getenv("GTS_WAZERO_COMPILATION_CACHE"); dir != "" {
// Use on-filesystem compilation cache given by env.
cache, err := wazero.NewCompilationCacheWithDir(dir)
@@ -128,7 +123,7 @@ func initWASM(ctx context.Context) error {
return nil
}
-func compilerSupported() (string, bool) {
+func isCompilerSupported() (string, bool) {
switch runtime.GOOS {
case "linux", "android",
"windows", "darwin",
@@ -141,10 +136,11 @@ func compilerSupported() (string, bool) {
switch runtime.GOARCH {
case "amd64":
// NOTE: wazero in the future may decouple the
- // requirement of simd (sse4_1) from requirements
+ // requirement of simd (sse4_1+2) from requirements
// for compiler support in the future, but even
// still our module go-ffmpreg makes use of them.
- return "amd64 SSE4.1 required", cpu.X86.HasSSE41
+ return "amd64 x86-64-v2 required (see: https://en.wikipedia.org/wiki/X86-64-v2)",
+ cpu.Initialized && cpu.X86.HasSSE3 && cpu.X86.HasSSE41 && cpu.X86.HasSSE42
case "arm64":
// NOTE: this particular check may change if we
// later update go-ffmpreg to a version that makes
diff --git a/internal/media/metadata.go b/internal/media/metadata.go
index 44b1a87b6..c1fa58645 100644
--- a/internal/media/metadata.go
+++ b/internal/media/metadata.go
@@ -74,20 +74,28 @@ func clearMetadata(ctx context.Context, filepath string) error {
// terminateExif cleans exif data from file at input path, into file
// at output path, using given file extension to determine cleaning type.
-func terminateExif(outpath, inpath string, ext string) error {
+func terminateExif(outpath, inpath string, ext string) (err error) {
+ var inFile *os.File
+ var outFile *os.File
+
+ // Ensure handles
+ // closed on return.
+ defer func() {
+ outFile.Close()
+ inFile.Close()
+ }()
+
// Open input file at given path.
- inFile, err := os.Open(inpath)
+ inFile, err = openRead(inpath)
if err != nil {
return gtserror.Newf("error opening input file %s: %w", inpath, err)
}
- defer inFile.Close()
- // Open output file at given path.
- outFile, err := os.Create(outpath)
+ // Create output file at given path.
+ outFile, err = openWrite(outpath)
if err != nil {
return gtserror.Newf("error opening output file %s: %w", outpath, err)
}
- defer outFile.Close()
// Terminate EXIF data from 'inFile' -> 'outFile'.
err = terminator.TerminateInto(outFile, inFile, ext)
diff --git a/internal/media/probe.go b/internal/media/probe.go
index 791b6a8c2..c66254b90 100644
--- a/internal/media/probe.go
+++ b/internal/media/probe.go
@@ -38,8 +38,9 @@ const (
// probe will first attempt to probe the file at path using native Go code
// (for performance), but falls back to using ffprobe to retrieve media details.
func probe(ctx context.Context, filepath string) (*result, error) {
+
// Open input file at given path.
- file, err := os.Open(filepath)
+ file, err := openRead(filepath)
if err != nil {
return nil, gtserror.Newf("error opening file %s: %w", filepath, err)
}
@@ -80,6 +81,7 @@ func probe(ctx context.Context, filepath string) (*result, error) {
// probeJPEG decodes the given file as JPEG and determines
// image details from the decoded JPEG using native Go code.
func probeJPEG(file *os.File) (*result, error) {
+
// Attempt to decode JPEG, adding back hdr magic.
cfg, err := jpeg.DecodeConfig(io.MultiReader(
strings.NewReader(magicJPEG),
diff --git a/internal/media/thumbnail.go b/internal/media/thumbnail.go
index d9a2e522a..5fccaf5ce 100644
--- a/internal/media/thumbnail.go
+++ b/internal/media/thumbnail.go
@@ -24,7 +24,6 @@ import (
"image/jpeg"
"image/png"
"io"
- "os"
"strings"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
@@ -89,8 +88,8 @@ func generateThumb(
// Default type is webp.
mimeType = "image/webp"
- // Generate thumb output path REPLACING extension.
- if i := strings.IndexByte(filepath, '.'); i != -1 {
+ // Generate thumb output path REPLACING file extension.
+ if i := strings.LastIndexByte(filepath, '.'); i != -1 {
outpath = filepath[:i] + "_thumb.webp"
ext = filepath[i+1:] // old extension
} else {
@@ -231,7 +230,7 @@ func generateNativeThumb(
error,
) {
// Open input file at given path.
- infile, err := os.Open(inpath)
+ infile, err := openRead(inpath)
if err != nil {
return "", gtserror.Newf("error opening input file %s: %w", inpath, err)
}
@@ -272,7 +271,7 @@ func generateNativeThumb(
)
// Open output file at given path.
- outfile, err := os.Create(outpath)
+ outfile, err := openWrite(outpath)
if err != nil {
return "", gtserror.Newf("error opening output file %s: %w", outpath, err)
}
@@ -313,8 +312,9 @@ func generateNativeThumb(
// generateWebpBlurhash generates a blurhash for Webp at filepath.
func generateWebpBlurhash(filepath string) (string, error) {
+
// Open the file at given path.
- file, err := os.Open(filepath)
+ file, err := openRead(filepath)
if err != nil {
return "", gtserror.Newf("error opening input file %s: %w", filepath, err)
}
diff --git a/internal/media/util.go b/internal/media/util.go
index ea52b415b..d73206434 100644
--- a/internal/media/util.go
+++ b/internal/media/util.go
@@ -30,14 +30,41 @@ import (
"codeberg.org/gruf/go-iotools"
)
+// media processing tmpdir.
+var tmpdir = os.TempDir()
+
// file represents one file
// with the given flag and perms.
type file struct {
- abs string
+ abs string // absolute file path, including root
+ dir string // containing directory of abs
+ rel string // relative to root, i.e. trim_prefix(abs, dir)
flag int
perm os.FileMode
}
+// allowRead returns a new file{} for filepath permitted only to read.
+func allowRead(filepath string) file {
+ return newFile(filepath, os.O_RDONLY, 0)
+}
+
+// allowCreate returns a new file{} for filepath permitted to read / write / create.
+func allowCreate(filepath string) file {
+ return newFile(filepath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+}
+
+// newFile returns a new instance of file{} for given path and open args.
+func newFile(filepath string, flag int, perms os.FileMode) file {
+ dir, rel := path.Split(filepath)
+ return file{
+ abs: filepath,
+ rel: rel,
+ dir: dir,
+ flag: flag,
+ perm: perms,
+ }
+}
+
// allowFiles implements fs.FS to allow
// access to a specified slice of files.
type allowFiles []file
@@ -45,36 +72,32 @@ type allowFiles []file
// Open implements fs.FS.
func (af allowFiles) Open(name string) (fs.File, error) {
for _, file := range af {
- var (
- abs = file.abs
- flag = file.flag
- perm = file.perm
- )
-
+ switch name {
// Allowed to open file
- // at absolute path.
- if name == file.abs {
- return os.OpenFile(abs, flag, perm)
- }
-
- // Check for other valid reads.
- thisDir, thisFile := path.Split(file.abs)
-
- // Allowed to read directory itself.
- if name == thisDir || name == "." {
- return os.OpenFile(thisDir, flag, perm)
- }
-
- // Allowed to read file
- // itself (at relative path).
- if name == thisFile {
- return os.OpenFile(abs, flag, perm)
+ // at absolute path, or
+ // relative as ffmpeg likes.
+ case file.abs, file.rel:
+ return os.OpenFile(file.abs, file.flag, file.perm)
+
+ // Ffmpeg likes to read containing
+ // dir as '.'. Allow RO access here.
+ case ".":
+ return openRead(file.dir)
}
}
-
return nil, os.ErrPermission
}
+// openRead opens the existing file at path for reads only.
+func openRead(path string) (*os.File, error) {
+ return os.OpenFile(path, os.O_RDONLY, 0)
+}
+
+// openWrite opens the (new!) file at path for read / writes.
+func openWrite(path string) (*os.File, error) {
+ return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+}
+
// getExtension splits file extension from path.
func getExtension(path string) string {
for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
@@ -93,17 +116,24 @@ func getExtension(path string) string {
// chance that Linux's sendfile syscall can be utilised for optimal
// draining of data source to temporary file storage.
func drainToTmp(rc io.ReadCloser) (string, error) {
- defer rc.Close()
+ var tmp *os.File
+ var err error
+
+ // Close handles
+ // on func return.
+ defer func() {
+ tmp.Close()
+ rc.Close()
+ }()
// Open new temporary file.
- tmp, err := os.CreateTemp(
- os.TempDir(),
+ tmp, err = os.CreateTemp(
+ tmpdir,
"gotosocial-*",
)
if err != nil {
return "", err
}
- defer tmp.Close()
// Extract file path.
path := tmp.Name()