update: add webm support
This commit is contained in:
parent
84c816cb8f
commit
1db6864ea0
10
README.md
10
README.md
|
|
@ -20,8 +20,8 @@
|
|||
- WEBP → JPEG **[WIP]**
|
||||
|
||||
### WEBM
|
||||
- WEBM → MP4 **[WIP]**
|
||||
- WEBM → GIF **[WIP]**
|
||||
- WEBM → PNG (first framge, all frames, N frame, frames range) **[WIP]**
|
||||
- WEBM → JPEG (first framge, all frames, N frame, frames range) **[WIP]**
|
||||
- WEBM → WEBP (first framge, all frames, N frame, frames range) **[WIP]**
|
||||
- WEBM → MP4
|
||||
- WEBM → GIF
|
||||
- WEBM → PNG (first framge, all frames, N frame, frames range)
|
||||
- WEBM → JPEG (first framge, all frames, N frame, frames range)
|
||||
- WEBM → WEBP (first framge, all frames, N frame, frames range)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/yazmeyaa/telegram_sticker_converter/internal/converter"
|
||||
"github.com/yazmeyaa/telegram_sticker_converter/internal/webm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
filePath := flag.String("file", "", "path to input .tgs file")
|
||||
outPath := flag.String("out", "", "path to output file")
|
||||
format := flag.String("format", "png", "output format (png|jpeg|webp|gif|webm|mp4|lottie)")
|
||||
frame := flag.String("frame", "all", "frame selector (first|all|n)")
|
||||
frameIndex := flag.Uint("frame-index", 0, "frame index (used only with frame=n)")
|
||||
resizeW := flag.Uint("resize-width", 0, "resize width (0 = keep original)")
|
||||
resizeH := flag.Uint("resize-height", 0, "resize height (0 = keep original)")
|
||||
flag.Parse()
|
||||
|
||||
if *filePath == "" {
|
||||
fmt.Fprintln(os.Stderr, "error: -file is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *outPath == "" {
|
||||
fmt.Fprintln(os.Stderr, "error: -out is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
in, err := os.Open(*filePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error opening file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(*outPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error creating output: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
var frameSel converter.FrameSelector
|
||||
switch *frame {
|
||||
case "first":
|
||||
frameSel = converter.FrameFirst
|
||||
case "all":
|
||||
frameSel = converter.FrameAll
|
||||
case "n":
|
||||
frameSel = converter.FrameN
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown frame selector: %s\n", *frame)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
opts := converter.WEBMTransformOptions{
|
||||
Format: converter.OutputFormat(*format),
|
||||
Frame: frameSel,
|
||||
FrameIndex: *frameIndex,
|
||||
Width: *resizeW,
|
||||
Height: *resizeH,
|
||||
}
|
||||
|
||||
service := converter.WEBMConverter(webm.NewService())
|
||||
if err := service.Transform(context.Background(), in, out, opts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "transform failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
|
@ -150,6 +150,7 @@ func (t tgsServiceImpl) processVideo(ctx context.Context, anim rlottie.Lottie_An
|
|||
"s": fmt.Sprintf("%dx%d", opts.ResizeWidth, opts.ResizeHeight),
|
||||
"r": frameRate,
|
||||
}).
|
||||
Silent(true).
|
||||
Output("pipe:1", preset).
|
||||
WithInput(r).
|
||||
WithOutput(out).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
package webm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/yazmeyaa/telegram_sticker_converter/internal/converter"
|
||||
)
|
||||
|
||||
type frameScanner struct {
|
||||
r io.Reader
|
||||
opts converter.WEBMTransformOptions
|
||||
|
||||
buf []byte
|
||||
frame []byte
|
||||
eof bool
|
||||
}
|
||||
|
||||
func newScanner(r io.Reader, opts converter.WEBMTransformOptions) *frameScanner {
|
||||
return &frameScanner{
|
||||
r: r,
|
||||
opts: opts,
|
||||
buf: make([]byte, 0, 64*1024),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
pngStart = []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
|
||||
pngEnd = []byte{0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82}
|
||||
|
||||
jpegStart = []byte{0xFF, 0xD8}
|
||||
jpegEnd = []byte{0xFF, 0xD9}
|
||||
|
||||
webpStart = []byte{'R', 'I', 'F', 'F'}
|
||||
webpWebP = []byte{'W', 'E', 'B', 'P'}
|
||||
)
|
||||
|
||||
func (f *frameScanner) signatures() ([]byte, []byte) {
|
||||
switch f.opts.Format {
|
||||
case converter.FormatPNG:
|
||||
return pngStart, pngEnd
|
||||
case converter.FormatJPEG:
|
||||
return jpegStart, jpegEnd
|
||||
case converter.FormatWEBP:
|
||||
return webpStart, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frameScanner) Next() ([]byte, error) {
|
||||
if f.eof {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
startSig, endSig := f.signatures()
|
||||
if startSig == nil {
|
||||
return nil, converter.ErrUnknownFormat
|
||||
}
|
||||
|
||||
for {
|
||||
startIdx := bytes.Index(f.buf, startSig)
|
||||
if startIdx >= 0 {
|
||||
switch f.opts.Format {
|
||||
case converter.FormatWEBP:
|
||||
if len(f.buf[startIdx:]) < 12 {
|
||||
break
|
||||
}
|
||||
if !bytes.Equal(f.buf[startIdx+8:startIdx+12], webpWebP) {
|
||||
f.buf = f.buf[startIdx+4:]
|
||||
continue
|
||||
}
|
||||
size := int(uint32(f.buf[startIdx+4]) |
|
||||
uint32(f.buf[startIdx+5])<<8 |
|
||||
uint32(f.buf[startIdx+6])<<16 |
|
||||
uint32(f.buf[startIdx+7])<<24)
|
||||
total := 8 + size
|
||||
if len(f.buf[startIdx:]) < total {
|
||||
break
|
||||
}
|
||||
frame := f.buf[startIdx : startIdx+total]
|
||||
f.buf = append([]byte{}, f.buf[startIdx+total:]...)
|
||||
return frame, nil
|
||||
|
||||
default:
|
||||
endIdx := bytes.Index(f.buf[startIdx+len(startSig):], endSig)
|
||||
if endIdx >= 0 {
|
||||
endIdx += startIdx + len(startSig)
|
||||
frame := f.buf[startIdx : endIdx+len(endSig)]
|
||||
f.buf = append([]byte{}, f.buf[endIdx+len(endSig):]...)
|
||||
return frame, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmp := make([]byte, 8192)
|
||||
n, err := f.r.Read(tmp)
|
||||
if n > 0 {
|
||||
f.buf = append(f.buf, tmp[:n]...)
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
f.eof = true
|
||||
return nil, io.EOF
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package webm
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
|
|
@ -136,15 +138,17 @@ func buildPreset(opts converter.WEBMTransformOptions) (ffmpeg_go.KwArgs, error)
|
|||
}), nil
|
||||
}
|
||||
}
|
||||
|
||||
return ffmpeg_go.KwArgs{}, converter.ErrUnknownFormat
|
||||
}
|
||||
|
||||
func (ws webmService) process(ctx context.Context, in io.Reader, out io.Writer, opts converter.WEBMTransformOptions) error {
|
||||
r, w := io.Pipe()
|
||||
rIn, wIn := io.Pipe()
|
||||
rOut, wOut := io.Pipe()
|
||||
|
||||
go func() {
|
||||
defer w.Close()
|
||||
io.Copy(w, in)
|
||||
defer wIn.Close()
|
||||
_, _ = io.Copy(wIn, in)
|
||||
}()
|
||||
|
||||
preset, err := buildPreset(opts)
|
||||
|
|
@ -152,18 +156,56 @@ func (ws webmService) process(ctx context.Context, in io.Reader, out io.Writer,
|
|||
return err
|
||||
}
|
||||
|
||||
err = ffmpeg_go.
|
||||
stream := ffmpeg_go.
|
||||
Input("pipe:0", ffmpeg_go.KwArgs{
|
||||
"f": "webm",
|
||||
}).
|
||||
Silent(true).
|
||||
Output("pipe:1", preset).
|
||||
WithInput(r).
|
||||
WithOutput(out).
|
||||
Run()
|
||||
WithInput(rIn).
|
||||
WithOutput(wOut)
|
||||
|
||||
if (opts.Frame == converter.FrameAll || opts.Frame == converter.FrameRange) &&
|
||||
(opts.Format == converter.FormatWEBP || opts.Format == converter.FormatJPEG || opts.Format == converter.FormatPNG) {
|
||||
|
||||
go func() {
|
||||
defer wOut.Close()
|
||||
_ = stream.Run()
|
||||
}()
|
||||
|
||||
zw := zip.NewWriter(out)
|
||||
defer zw.Close()
|
||||
|
||||
frameIdx := 0
|
||||
scanner := newScanner(rOut, opts)
|
||||
|
||||
for {
|
||||
frame, err := scanner.Next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := zw.Create(fmt.Sprintf("frame_%d.%s", frameIdx, string(opts.Format)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.Write(frame); err != nil {
|
||||
return err
|
||||
}
|
||||
frameIdx++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
defer wOut.Close()
|
||||
if err := stream.WithOutput(out).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue