webm: basic logic

This commit is contained in:
eugene 2025-09-06 23:56:06 +03:00
parent f50161874a
commit 5112a16827
7 changed files with 201 additions and 21 deletions

View File

@ -22,3 +22,6 @@
### WEBM ### WEBM
- WEBM → MP4 **[WIP]** - WEBM → MP4 **[WIP]**
- WEBM → GIF **[WIP]** - WEBM → GIF **[WIP]**
- WEBM → PNG (first framge, all frames, N frame) **[WIP]**
- WEBM → JPEG (first framge, all frames, N frame) **[WIP]**
- WEBM → WEBP (first framge, all frames, N frame) **[WIP]**

View File

@ -1,12 +0,0 @@
package main
import (
"os"
"github.com/yazmeyaa/telegram_sticker_converter/internal/logger"
)
func main() {
logger := logger.NewLogger(os.Stdout, "telegram_bot")
logger.Info("Starting telegram bot")
}

View File

@ -58,7 +58,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
opts := converter.TransformOptions{ opts := converter.TGSTransformOptions{
Format: converter.OutputFormat(*format), Format: converter.OutputFormat(*format),
Frame: frameSel, Frame: frameSel,
FrameIndex: *frameIndex, FrameIndex: *frameIndex,
@ -66,7 +66,6 @@ func main() {
ResizeWidth: *resizeW, ResizeWidth: *resizeW,
ResizeHeight: *resizeH, ResizeHeight: *resizeH,
} }
fmt.Fprintf(os.Stdout, "opts: %+v\n", opts)
service := converter.TGSConverterService(tgs.NewService()) service := converter.TGSConverterService(tgs.NewService())
if err := service.Transform(context.Background(), in, out, opts); err != nil { if err := service.Transform(context.Background(), in, out, opts); err != nil {

View File

@ -13,6 +13,7 @@ const (
FrameFirst FrameSelector = iota FrameFirst FrameSelector = iota
FrameAll FrameAll
FrameN FrameN
FrameRange
) )
var ( var (
@ -29,15 +30,16 @@ const (
FormatLottie OutputFormat = "lottie" FormatLottie OutputFormat = "lottie"
) )
type TransformOptions struct { type TGSTransformOptions struct {
Format OutputFormat Format OutputFormat
Frame FrameSelector Frame FrameSelector
FrameIndex int FrameIndex int
FrameOffset int
Qualtity int Qualtity int
ResizeWidth int ResizeWidth int
ResizeHeight int ResizeHeight int
} }
type TGSConverterService interface { type TGSConverterService interface {
Transform(ctx context.Context, data io.Reader, out io.Writer, opts TransformOptions) error Transform(ctx context.Context, data io.Reader, out io.Writer, opts TGSTransformOptions) error
} }

View File

@ -0,0 +1,19 @@
package converter
import (
"context"
"io"
)
type WEBMTransformOptions struct {
Format OutputFormat
Width uint
Height uint
Frame FrameSelector
FrameIndex uint
FrameOffset uint
}
type WEBMConverter interface {
Transform(ctx context.Context, in io.Reader, out io.Writer, opts WEBMTransformOptions) error
}

View File

@ -17,7 +17,7 @@ import (
"github.com/yazmeyaa/telegram_sticker_converter/internal/converter" "github.com/yazmeyaa/telegram_sticker_converter/internal/converter"
) )
func (t tgsServiceImpl) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.TransformOptions) error { func (t tgsServiceImpl) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.TGSTransformOptions) error {
data, err := io.ReadAll(in) data, err := io.ReadAll(in)
if err != nil { if err != nil {
return err return err
@ -57,7 +57,7 @@ func (t tgsServiceImpl) Transform(ctx context.Context, in io.Reader, out io.Writ
return converter.ErrUnknownFormat return converter.ErrUnknownFormat
} }
func (t tgsServiceImpl) processFrames(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TransformOptions) error { func (t tgsServiceImpl) processFrames(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TGSTransformOptions) error {
width := uint(opts.ResizeWidth) width := uint(opts.ResizeWidth)
height := uint(opts.ResizeHeight) height := uint(opts.ResizeHeight)
@ -111,7 +111,7 @@ var (
} }
) )
func (t tgsServiceImpl) processVideo(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TransformOptions) error { func (t tgsServiceImpl) processVideo(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TGSTransformOptions) error {
totalFrames := rlottie.LottieAnimationGetTotalframe(anim) totalFrames := rlottie.LottieAnimationGetTotalframe(anim)
frameRate := rlottie.LottieAnimationGetFramerate(anim) frameRate := rlottie.LottieAnimationGetFramerate(anim)
var preset ffmpeg_go.KwArgs var preset ffmpeg_go.KwArgs
@ -168,7 +168,7 @@ func BGRAtoRGBA(buf []byte) {
} }
} }
func (t tgsServiceImpl) makeSingleImage(ctx context.Context, frameBuffer []byte, out io.Writer, opts converter.TransformOptions) error { func (t tgsServiceImpl) makeSingleImage(ctx context.Context, frameBuffer []byte, out io.Writer, opts converter.TGSTransformOptions) error {
BGRAtoRGBA(frameBuffer) BGRAtoRGBA(frameBuffer)
img := &image.RGBA{ img := &image.RGBA{
Pix: frameBuffer, Pix: frameBuffer,
@ -190,7 +190,7 @@ func (t tgsServiceImpl) makeSingleImage(ctx context.Context, frameBuffer []byte,
return converter.ErrUnknownFormat return converter.ErrUnknownFormat
} }
func (t tgsServiceImpl) makeAllImages(ctx context.Context, anim rlottie.Lottie_Animation, frameBuffer []byte, out io.Writer, opts converter.TransformOptions) error { func (t tgsServiceImpl) makeAllImages(ctx context.Context, anim rlottie.Lottie_Animation, frameBuffer []byte, out io.Writer, opts converter.TGSTransformOptions) error {
totalFrames := rlottie.LottieAnimationGetTotalframe(anim) totalFrames := rlottie.LottieAnimationGetTotalframe(anim)
archive := zip.NewWriter(out) archive := zip.NewWriter(out)
defer archive.Close() defer archive.Close()

169
internal/webm/service.go Normal file
View File

@ -0,0 +1,169 @@
package webm
import (
"context"
"fmt"
"io"
ffmpeg_go "github.com/u2takey/ffmpeg-go"
"github.com/yazmeyaa/telegram_sticker_converter/internal/converter"
)
type webmService struct{}
func NewService() *webmService {
return &webmService{}
}
func (ws webmService) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.WEBMTransformOptions) error {
return ws.process(ctx, in, out, opts)
}
var (
PresetMP4 = ffmpeg_go.KwArgs{
"vcodec": "libx264",
"format": "mp4",
"pix_fmt": "yuv420p",
"movflags": "frag_keyframe+empty_moov",
"preset": "ultrafast",
"tune": "zerolatency",
}
PresetPNG = ffmpeg_go.KwArgs{
"f": "image2pipe",
"c:v": "png",
"vsync": "0",
}
PresetJPEG = ffmpeg_go.KwArgs{
"f": "image2pipe",
"c:v": "mjpeg",
"vsync": "0",
}
PresetWEBP = ffmpeg_go.KwArgs{
"f": "image2pipe",
"c:v": "libwebp",
}
PresetGIF = ffmpeg_go.KwArgs{"f": "gif"}
)
func buildPreset(opts converter.WEBMTransformOptions) (ffmpeg_go.KwArgs, error) {
switch opts.Format {
case converter.FormatGIF:
return PresetGIF, nil
case converter.FormatMP4:
return PresetMP4, nil
case converter.FormatPNG:
switch opts.Frame {
case converter.FrameAll:
return PresetPNG, nil
case converter.FrameFirst:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetPNG,
{
"vframes": "1",
},
}), nil
case converter.FrameN:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetPNG,
{
"vframes": "1",
"vf": fmt.Sprintf("select=eq(n\\,%d)", opts.FrameIndex),
},
}), nil
case converter.FrameRange:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetPNG,
{
"vf": fmt.Sprintf("select=between(n\\,%d\\,%d)", opts.FrameIndex, opts.FrameIndex+opts.FrameOffset),
},
}), nil
}
case converter.FormatJPEG:
switch opts.Frame {
case converter.FrameAll:
return PresetJPEG, nil
case converter.FrameFirst:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetJPEG,
{
"frames": "1",
},
}), nil
case converter.FrameN:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetJPEG,
{
"vframes": "1",
"vf": fmt.Sprintf("select=eq(n\\,%d)", opts.FrameIndex),
},
}), nil
case converter.FrameRange:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetJPEG,
{
"vf": fmt.Sprintf("select=between(n\\,%d\\,%d)", opts.FrameIndex, opts.FrameIndex+opts.FrameOffset),
},
}), nil
}
case converter.FormatWEBP:
switch opts.Frame {
case converter.FrameAll:
return PresetWEBP, nil
case converter.FrameFirst:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetWEBP,
{
"frames": "1",
},
}), nil
case converter.FrameN:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetWEBP,
{
"vframes": "1",
"vf": fmt.Sprintf("select=eq(n\\,%d)", opts.FrameIndex),
},
}), nil
case converter.FrameRange:
return ffmpeg_go.MergeKwArgs([]ffmpeg_go.KwArgs{
PresetWEBP,
{
"vf": fmt.Sprintf("select=between(n\\,%d\\,%d)", opts.FrameIndex, opts.FrameIndex+opts.FrameOffset),
},
}), 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()
go func() {
defer w.Close()
io.Copy(w, in)
}()
preset, err := buildPreset(opts)
if err != nil {
return err
}
err = ffmpeg_go.
Input("pipe:0", ffmpeg_go.KwArgs{
"f": "webm",
}).
Output("pipe:1", preset).
WithInput(r).
WithOutput(out).
Run()
if err != nil {
return err
}
return nil
}