webm: basic logic
This commit is contained in:
parent
f50161874a
commit
5112a16827
|
|
@ -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]**
|
||||||
|
|
@ -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")
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue