diff --git a/README.md b/README.md
index 83fd15e..10c4029 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Telegram sticker converter
-## System requirements
-- ffmpeg
+## Required system binaries
+- ffmpeg 7.1.1
## Build requirements
- Go 1.24
@@ -12,22 +12,104 @@
- WEBM
## Supported transformations
-### TGS
-- TGS → PNG (first framge, all frames, N frame, frames range)
-- TGS → JPEG (first framge, all frames, N frame, frames range)
-- TGS → WEBP (first framge, all frames, N frame, frames range)
-- TGS → Lottie.JSON
-- TGS → GIF
-- TGS → WEBM
-- TGS → MP4
-### WEBP
-- WEBP → PNG
-- WEBP → JPEG
+| Input | Output formats |
+| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **TGS** | PNG (first frame, all frames, N frame, frame range)
JPEG (first frame, all frames, N frame, frame range)
WEBP (first frame, all frames, N frame, frame range)
Lottie JSON
GIF
WEBM
MP4 |
+| **WEBP** | PNG
JPEG |
+| **WEBM** | MP4
GIF
PNG (first frame, all frames, N frame, frame range)
JPEG (first frame, all frames, N frame, frame range)
WEBP (first frame, all frames, N frame, frame range) |
-### WEBM
-- 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)
\ No newline at end of file
+
+## Examples
+### Extract frame from TGS
+```go
+package main
+import (
+ "context"
+ "os"
+
+ converter "github.com/yazmeyaa/telegram_sticker_converter"
+ "github.com/yazmeyaa/telegram_sticker_converter/tgs"
+)
+
+
+func main() {
+ conv := tgs.NewConverter()
+
+ r, err := os.Open("./sticker.tgs")
+ defer r.Close()
+ w, err := os.Create("./sticker.png")
+ defer w.Close()
+ opts := converter.TGSTransformOptions{
+ Format: converter.FormatPNG,
+ Frame: converter.FrameN,
+ FrameIndex: 10,
+ ResizeWidth: 1024,
+ ResizeHeight: 1024,
+ }
+ if err := conv.Transform(context.Background(), r, w, opts); err != nil {
+ panic(err)
+ }
+}
+```
+
+### Convert TGS to video
+```go
+package main
+import (
+ "context"
+ "os"
+
+ converter "github.com/yazmeyaa/telegram_sticker_converter"
+ "github.com/yazmeyaa/telegram_sticker_converter/tgs"
+)
+
+
+func main() {
+ conv := tgs.NewConverter()
+
+ r, err := os.Open("./sticker.tgs")
+ defer r.Close()
+ w, err := os.Create("./sticker.mp4")
+ defer w.Close()
+ opts := converter.TGSTransformOptions{
+ Format: converter.FormatMP4,
+ ResizeWidth: 1024,
+ ResizeHeight: 1024,
+ }
+ if err := conv.Transform(context.Background(), r, w, opts); err != nil {
+ panic(err)
+ }
+}
+```
+
+### Convert TGS to frames array (ZIP)
+```go
+package main
+import (
+ "context"
+ "os"
+
+ converter "github.com/yazmeyaa/telegram_sticker_converter"
+ "github.com/yazmeyaa/telegram_sticker_converter/tgs"
+)
+
+
+func main() {
+ conv := tgs.NewConverter()
+
+ r, err := os.Open("./sticker.tgs")
+ defer r.Close()
+ w, err := os.Create("./sticker.zip")
+ defer w.Close()
+ opts := converter.TGSTransformOptions{
+ Format: converter.FormatPNG,
+ Frame: converter.FrameAll,
+ ResizeWidth: 1024,
+ ResizeHeight: 1024,
+ }
+ if err := conv.Transform(context.Background(), r, w, opts); err != nil {
+ panic(err)
+ }
+}
+```
\ No newline at end of file
diff --git a/cmd/tgs_file_convert/tgs_file_convert.go b/cmd/tgs_file_convert/tgs_file_convert.go
index 45eb087..caa7669 100644
--- a/cmd/tgs_file_convert/tgs_file_convert.go
+++ b/cmd/tgs_file_convert/tgs_file_convert.go
@@ -5,39 +5,53 @@ import (
"flag"
"fmt"
"os"
+ "strings"
converter "github.com/yazmeyaa/telegram_sticker_converter"
"github.com/yazmeyaa/telegram_sticker_converter/tgs"
)
func main() {
- filePath := flag.String("file", "", "path to input .tgs file")
- outPath := flag.String("out", "", "path to output file")
+ input := flag.String("input", "stream:stdin", "path to input .tgs file")
+ outPath := flag.String("output", "", "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.Int("frame-index", 0, "frame index (used only with frame=n)")
- quality := flag.Int("quality", 90, "output quality (0-100)")
+ quality := flag.Int("quality", 100, "output quality (0-100)")
resizeW := flag.Int("resize-width", 0, "resize width (0 = keep original)")
resizeH := flag.Int("resize-height", 0, "resize height (0 = keep original)")
flag.Parse()
- if *filePath == "" {
- fmt.Fprintln(os.Stderr, "error: -file is required")
+ inputParts := strings.Split(*input, ":")
+ if len(inputParts) < 2 {
+ fmt.Fprintf(os.Stderr, "wrong input signature. valid is \"{type}:{input}\"\nExample: \"stream:stdin\"; \"file:input.tgs\"\n")
os.Exit(1)
}
- if *outPath == "" {
- fmt.Fprintln(os.Stderr, "error: -out is required")
- os.Exit(1)
+ inputType := inputParts[0]
+ var in *os.File
+ if inputType == "file" {
+ input, err := os.Open(*input)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error opening file: %v\n", err)
+ os.Exit(1)
+ }
+ in = input
}
-
- in, err := os.Open(*filePath)
- if err != nil {
- fmt.Fprintf(os.Stderr, "error opening file: %v\n", err)
+ if inputType == "stream" {
+ in = os.Stdin
+ }
+ if in == nil {
+ fmt.Fprintf(os.Stderr, "Unexpected input type\n")
os.Exit(1)
}
defer in.Close()
+ if *outPath == "" {
+ fmt.Fprintln(os.Stderr, "error: -output is required")
+ os.Exit(1)
+ }
+
out, err := os.Create(*outPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating output: %v\n", err)
@@ -67,8 +81,9 @@ func main() {
ResizeHeight: *resizeH,
}
- service := converter.TGSConverterService(tgs.NewService())
- if err := service.Transform(context.Background(), in, out, opts); err != nil {
+ converter := tgs.NewConverter()
+
+ if err := converter.Transform(context.Background(), in, out, opts); err != nil {
fmt.Fprintf(os.Stderr, "transform failed: %v\n", err)
os.Exit(1)
}
diff --git a/cmd/webm_file_convert/webm_file_convert.go b/cmd/webm_file_convert/webm_file_convert.go
index 77f11da..e3daa5b 100644
--- a/cmd/webm_file_convert/webm_file_convert.go
+++ b/cmd/webm_file_convert/webm_file_convert.go
@@ -65,8 +65,8 @@ func main() {
Height: *resizeH,
}
- service := converter.WEBMConverter(webm.NewService())
- if err := service.Transform(context.Background(), in, out, opts); err != nil {
+ converter := converter.WEBMConverter(webm.NewConverter())
+ if err := converter.Transform(context.Background(), in, out, opts); err != nil {
fmt.Fprintf(os.Stderr, "transform failed: %v\n", err)
os.Exit(1)
}
diff --git a/tgs.go b/tgs.go
index a382d15..f3f2529 100644
--- a/tgs.go
+++ b/tgs.go
@@ -15,6 +15,6 @@ type TGSTransformOptions struct {
ResizeHeight int
}
-type TGSConverterService interface {
+type TGSConverter interface {
Transform(ctx context.Context, data io.Reader, out io.Writer, opts TGSTransformOptions) error
}
diff --git a/tgs/service.go b/tgs/service.go
index 2d3cf29..7f902d8 100644
--- a/tgs/service.go
+++ b/tgs/service.go
@@ -17,7 +17,7 @@ import (
converter "github.com/yazmeyaa/telegram_sticker_converter"
)
-func (t tgsServiceImpl) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.TGSTransformOptions) error {
+func (t tgsConverterImpl) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.TGSTransformOptions) error {
data, err := io.ReadAll(in)
if err != nil {
return err
@@ -57,7 +57,7 @@ func (t tgsServiceImpl) Transform(ctx context.Context, in io.Reader, out io.Writ
return converter.ErrUnknownFormat
}
-func (t tgsServiceImpl) processFrames(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TGSTransformOptions) error {
+func (t tgsConverterImpl) processFrames(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TGSTransformOptions) error {
width := uint(opts.ResizeWidth)
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.TGSTransformOptions) error {
+func (t tgsConverterImpl) processVideo(ctx context.Context, anim rlottie.Lottie_Animation, out io.Writer, opts converter.TGSTransformOptions) error {
totalFrames := rlottie.LottieAnimationGetTotalframe(anim)
frameRate := rlottie.LottieAnimationGetFramerate(anim)
var preset ffmpeg_go.KwArgs
@@ -169,7 +169,7 @@ func BGRAtoRGBA(buf []byte) {
}
}
-func (t tgsServiceImpl) makeSingleImage(ctx context.Context, frameBuffer []byte, out io.Writer, opts converter.TGSTransformOptions) error {
+func (t tgsConverterImpl) makeSingleImage(ctx context.Context, frameBuffer []byte, out io.Writer, opts converter.TGSTransformOptions) error {
BGRAtoRGBA(frameBuffer)
img := &image.RGBA{
Pix: frameBuffer,
@@ -191,7 +191,7 @@ func (t tgsServiceImpl) makeSingleImage(ctx context.Context, frameBuffer []byte,
return converter.ErrUnknownFormat
}
-func (t tgsServiceImpl) makeAllImages(ctx context.Context, anim rlottie.Lottie_Animation, frameBuffer []byte, out io.Writer, opts converter.TGSTransformOptions) error {
+func (t tgsConverterImpl) makeAllImages(ctx context.Context, anim rlottie.Lottie_Animation, frameBuffer []byte, out io.Writer, opts converter.TGSTransformOptions) error {
totalFrames := rlottie.LottieAnimationGetTotalframe(anim)
archive := zip.NewWriter(out)
defer archive.Close()
@@ -222,7 +222,7 @@ func (t tgsServiceImpl) makeAllImages(ctx context.Context, anim rlottie.Lottie_A
return nil
}
-func (t tgsServiceImpl) processFrame(ctx context.Context, anim rlottie.Lottie_Animation, frameN uint, sx uint, sy uint, out []byte) error {
+func (t tgsConverterImpl) processFrame(ctx context.Context, anim rlottie.Lottie_Animation, frameN uint, sx uint, sy uint, out []byte) error {
expectedSize := int(sx * sy * 4)
rlottie.LottieAnimationRender(anim, frameN, out, sx, sy, sx*4)
if len(out) < expectedSize {
@@ -231,8 +231,8 @@ func (t tgsServiceImpl) processFrame(ctx context.Context, anim rlottie.Lottie_An
return nil
}
-type tgsServiceImpl struct{}
+type tgsConverterImpl struct{}
-func NewService() *tgsServiceImpl {
- return &tgsServiceImpl{}
+func NewConverter() *tgsConverterImpl {
+ return &tgsConverterImpl{}
}
diff --git a/webm/service.go b/webm/service.go
index 199889f..2603c6f 100644
--- a/webm/service.go
+++ b/webm/service.go
@@ -11,13 +11,13 @@ import (
converter "github.com/yazmeyaa/telegram_sticker_converter"
)
-type webmService struct{}
+type webmConverter struct{}
-func NewService() *webmService {
- return &webmService{}
+func NewConverter() *webmConverter {
+ return &webmConverter{}
}
-func (ws webmService) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.WEBMTransformOptions) error {
+func (ws webmConverter) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.WEBMTransformOptions) error {
rIn, wIn := io.Pipe()
rOut, wOut := io.Pipe()
diff --git a/webp.go b/webp.go
index 639f231..2fd81d2 100644
--- a/webp.go
+++ b/webp.go
@@ -9,6 +9,6 @@ type WebpTransformOptions struct {
Format OutputFormat
}
-type WebpConverterService interface {
+type WebpConverter interface {
Transform(ctx context.Context, in io.Reader, out io.Writer, opts WebpTransformOptions) error
}
diff --git a/webp/service.go b/webp/service.go
index 6fc321f..16002a4 100644
--- a/webp/service.go
+++ b/webp/service.go
@@ -10,10 +10,10 @@ import (
"golang.org/x/image/webp"
)
-type webpConverterService struct{}
+type webpConverter struct{}
-// Transform implements converter.WebpConverterService.
-func (w webpConverterService) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.WebpTransformOptions) error {
+// Transform implements converter.WebpConverter.
+func (w webpConverter) Transform(ctx context.Context, in io.Reader, out io.Writer, opts converter.WebpTransformOptions) error {
i, err := webp.Decode(in)
if err != nil {
return err
@@ -29,8 +29,8 @@ func (w webpConverterService) Transform(ctx context.Context, in io.Reader, out i
return converter.ErrUnknownFormat
}
-var _ converter.WebpConverterService = webpConverterService{}
+var _ converter.WebpConverter = webpConverter{}
-func NewService() *webpConverterService {
- return &webpConverterService{}
+func NewConverter() *webpConverter {
+ return &webpConverter{}
}