├── .gitignore ├── LICENSE ├── README.md ├── test.jpg ├── test_output └── README.md ├── tinify ├── client.go ├── result.go ├── result_meta.go ├── source.go └── tinify.go └── tinify_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Go template 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 16 | .glide/ 17 | .idea/ 18 | test_output/*.jpg 19 | temp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 gwpp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tinify API client for Golang 2 | 3 | [:book: 国内的朋友看这里](http://www.jianshu.com/p/5c4161db4ac8) 4 | 5 | ------ 6 | 7 | Golang client for the Tinify API, used for [TinyPNG](https://tinypng.com) and [TinyJPG](https://tinyjpg.com). Tinify compresses or resize your images intelligently. Read more at [http://tinify.com](http://tinify.com). 8 | 9 | ## Documentation 10 | 11 | [Go to the documentation for the HTTP client](https://tinypng.com/developers/reference). 12 | 13 | ## Installation 14 | 15 | Install the API client with `go get`. 16 | 17 | ```shell 18 | go get -u github.com/gwpp/tinify-go 19 | ``` 20 | 21 | ## Usage 22 | 23 | - About key 24 | 25 | Get your API key from https://tinypng.com/developers 26 | 27 | - Compress 28 | ```golang 29 | func TestCompressFromFile(t *testing.T) { 30 | Tinify.SetKey(Key) 31 | source, err := Tinify.FromFile("./test.jpg") 32 | if err != nil { 33 | t.Error(err) 34 | return 35 | } 36 | 37 | err = source.ToFile("./test_output/CompressFromFile.jpg") 38 | if err != nil { 39 | t.Error(err) 40 | return 41 | } 42 | t.Log("Compress successful") 43 | } 44 | ``` 45 | 46 | - Resize 47 | ```golang 48 | func TestResizeFromBuffer(t *testing.T) { 49 | Tinify.SetKey(Key) 50 | 51 | buf, err := ioutil.ReadFile("./test.jpg") 52 | if err != nil { 53 | t.Error(err) 54 | return 55 | } 56 | source, err := Tinify.FromBuffer(buf) 57 | if err != nil { 58 | t.Error(err) 59 | return 60 | } 61 | 62 | err = source.Resize(&Tinify.ResizeOption{ 63 | Method: Tinify.ResizeMethodScale, 64 | Width: 200, 65 | }) 66 | if err != nil { 67 | t.Error(err) 68 | return 69 | } 70 | 71 | err = source.ToFile("./test_output/ResizesFromBuffer.jpg") 72 | if err != nil { 73 | t.Error(err) 74 | return 75 | } 76 | t.Log("Resize successful") 77 | } 78 | ``` 79 | 80 | - ***Notice:*** 81 | 82 | Tinify.ResizeMethod support `scale`, `fit` and `cover`. If used fit or cover, you must provide `both a width and a height`. But used scale, you must provide either a target width or a target height, `but not both`. 83 | 84 | 85 | - More usage, please see [tinify_test.go](./tinify_test.go) 86 | 87 | ## Running tests 88 | 89 | ```shell 90 | cd $GOPATH/src/github.com/gwpp/tinify-go 91 | go test 92 | ``` 93 | 94 | ## License 95 | 96 | This software is licensed under the MIT License. [View the license](LICENSE). 97 | -------------------------------------------------------------------------------- /test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gwpp/tinify-go/77b9df15f343fc8eb8394b84e1eb71e410093b61/test.jpg -------------------------------------------------------------------------------- /test_output/README.md: -------------------------------------------------------------------------------- 1 | ## test output -------------------------------------------------------------------------------- /tinify/client.go: -------------------------------------------------------------------------------- 1 | package Tinify 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | const API_ENDPOINT = "https://api.tinify.com" 12 | 13 | type Client struct { 14 | options map[string]interface{} 15 | key string 16 | } 17 | 18 | func NewClient(key string) (c *Client, err error) { 19 | c = new(Client) 20 | c.key = key 21 | return 22 | } 23 | 24 | // method: http.MethodPost、http.MethodGet 25 | func (c *Client) Request(method string, url string, body interface{}) (response *http.Response, err error) { 26 | if strings.HasPrefix(url, "https") == false { 27 | url = API_ENDPOINT + url 28 | } 29 | req, err := http.NewRequest(method, url, nil) 30 | if err != nil { 31 | return 32 | } 33 | 34 | switch body.(type) { 35 | case []byte: 36 | if len(body.([]byte)) > 0 { 37 | req.Body = ioutil.NopCloser(bytes.NewReader(body.([]byte))) 38 | } 39 | case map[string]interface{}: 40 | if len(body.(map[string]interface{})) > 0 { 41 | body2, err2 := json.Marshal(body) 42 | if err2 != nil { 43 | err = err2 44 | return 45 | } 46 | req.Body = ioutil.NopCloser(bytes.NewReader(body2)) 47 | } 48 | req.Header["Content-Type"] = []string{"application/json"} 49 | } 50 | 51 | req.SetBasicAuth("api", c.key) 52 | 53 | response, err = http.DefaultClient.Do(req) 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /tinify/result.go: -------------------------------------------------------------------------------- 1 | package Tinify 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | ) 10 | 11 | type Result struct { 12 | data []byte 13 | *ResultMeta 14 | } 15 | 16 | func NewResult(meta http.Header, data []byte) *Result { 17 | r := new(Result) 18 | r.ResultMeta = NewResultMeta(meta) 19 | r.data = data 20 | return r 21 | } 22 | 23 | func (r *Result) Data() []byte { 24 | return r.data 25 | } 26 | 27 | func (r *Result) ToBuffer() []byte { 28 | return r.Data() 29 | } 30 | 31 | func (r *Result) ToFile(path string) error { 32 | path, err := filepath.Abs(path) 33 | if err != nil { 34 | return err 35 | } 36 | err = ioutil.WriteFile(path, r.data, os.ModePerm) 37 | return err 38 | } 39 | 40 | func (r *Result) Size() int64 { 41 | s := r.meta["Content-Length"] 42 | if len(s) == 0 { 43 | return 0 44 | } 45 | 46 | size, _ := strconv.Atoi(s[0]) 47 | return int64(size) 48 | } 49 | 50 | func (r *Result) MediaType() string { 51 | arr := r.meta["Content-Type"] 52 | if len(arr) == 0 { 53 | return "" 54 | } 55 | return arr[0] 56 | } 57 | 58 | func (r *Result) ContentType() string { 59 | return r.MediaType() 60 | } 61 | -------------------------------------------------------------------------------- /tinify/result_meta.go: -------------------------------------------------------------------------------- 1 | package Tinify 2 | 3 | import ( 4 | "net/http" 5 | 6 | "strconv" 7 | ) 8 | 9 | type ResultMeta struct { 10 | meta http.Header 11 | } 12 | 13 | func NewResultMeta(meta http.Header) *ResultMeta { 14 | r := new(ResultMeta) 15 | r.meta = meta 16 | return r 17 | } 18 | 19 | func (r *ResultMeta) width() int64 { 20 | w := r.meta["Image-Width"] 21 | if len(w) == 0 { 22 | return 0 23 | } 24 | width, _ := strconv.Atoi(w[0]) 25 | 26 | return int64(width) 27 | } 28 | 29 | func (r *ResultMeta) height() int64 { 30 | h := r.meta["Image-Height"] 31 | if len(h) == 0 { 32 | return 0 33 | } 34 | 35 | height, _ := strconv.Atoi(h[0]) 36 | return int64(height) 37 | } 38 | 39 | func (r *ResultMeta) location() string { 40 | arr := r.meta["Location"] 41 | if len(arr) == 0 { 42 | return "" 43 | } 44 | return arr[0] 45 | } 46 | -------------------------------------------------------------------------------- /tinify/source.go: -------------------------------------------------------------------------------- 1 | package Tinify 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | const ( 10 | ResizeMethodScale = "scale" 11 | ResizeMethodFit = "fit" 12 | ResizeMethodCover = "cover" 13 | ) 14 | 15 | type ResizeMethod string 16 | 17 | type ResizeOption struct { 18 | Method ResizeMethod `json:"method"` 19 | Width int64 `json:"width"` 20 | Height int64 `json:"height"` 21 | } 22 | 23 | type Source struct { 24 | url string 25 | commands map[string]interface{} 26 | } 27 | 28 | func newSource(url string, commands map[string]interface{}) *Source { 29 | s := new(Source) 30 | s.url = url 31 | if commands != nil { 32 | s.commands = commands 33 | } else { 34 | s.commands = make(map[string]interface{}) 35 | } 36 | 37 | return s 38 | } 39 | 40 | func FromFile(path string) (s *Source, err error) { 41 | buf, err := ioutil.ReadFile(path) 42 | if err != nil { 43 | return 44 | } 45 | 46 | return FromBuffer(buf) 47 | } 48 | 49 | func FromBuffer(buf []byte) (s *Source, err error) { 50 | response, err := GetClient().Request(http.MethodPost, "/shrink", buf) 51 | if err != nil { 52 | return 53 | } 54 | 55 | s, err = getSourceFromResponse(response) 56 | return 57 | } 58 | 59 | func FromUrl(url string) (s *Source, err error) { 60 | if len(url) == 0 { 61 | err = errors.New("url is required") 62 | return 63 | } 64 | 65 | body := map[string]interface{}{ 66 | "source": map[string]interface{}{ 67 | "url": url, 68 | }, 69 | } 70 | 71 | response, err := GetClient().Request(http.MethodPost, "/shrink", body) 72 | if err != nil { 73 | return 74 | } 75 | 76 | s, err = getSourceFromResponse(response) 77 | return 78 | } 79 | 80 | func getSourceFromResponse(response *http.Response) (s *Source, err error) { 81 | location := response.Header["Location"] 82 | url := "" 83 | if len(location) > 0 { 84 | url = location[0] 85 | } 86 | 87 | s = newSource(url, nil) 88 | return 89 | } 90 | 91 | func (s *Source) ToFile(path string) error { 92 | result, err := s.toResult() 93 | if err != nil { 94 | return err 95 | } 96 | 97 | return result.ToFile(path) 98 | } 99 | 100 | func (s *Source) Resize(option *ResizeOption) error { 101 | if option == nil { 102 | return errors.New("option is required") 103 | } 104 | 105 | s.commands["resize"] = option 106 | 107 | return nil 108 | } 109 | 110 | func (s *Source) toResult() (r *Result, err error) { 111 | if len(s.url) == 0 { 112 | err = errors.New("url is empty") 113 | return 114 | } 115 | 116 | //body := make([]byte, 0) 117 | //if len(s.commands) > 0 { 118 | // body, err = json.Marshal(s.commands) 119 | // if err != nil { 120 | // return 121 | // } 122 | //} 123 | response, err := GetClient().Request(http.MethodGet, s.url, s.commands) 124 | if err != nil { 125 | return 126 | } 127 | 128 | data, err := ioutil.ReadAll(response.Body) 129 | if err != nil { 130 | return 131 | } 132 | 133 | r = NewResult(response.Header, data) 134 | return 135 | } 136 | -------------------------------------------------------------------------------- /tinify/tinify.go: -------------------------------------------------------------------------------- 1 | package Tinify 2 | 3 | import "errors" 4 | 5 | const VERSION = "1.0" 6 | 7 | var ( 8 | key string 9 | client *Client 10 | ) 11 | 12 | func SetKey(set_key string) { 13 | key = set_key 14 | } 15 | 16 | func GetClient() *Client { 17 | if len(key) == 0 { 18 | panic(errors.New("Provide an API key with Tinify.setKey(key string)")) 19 | } 20 | 21 | if client == nil { 22 | c, err := NewClient(key) 23 | if err != nil { 24 | panic(errors.New("Provide an API key with Tinify.setKey(key string)")) 25 | } 26 | client = c 27 | } 28 | return client 29 | } 30 | -------------------------------------------------------------------------------- /tinify_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "io/ioutil" 7 | 8 | "github.com/gwpp/tinify-go/tinify" 9 | ) 10 | 11 | const Key = "rcPZm3Zrg_1DbjYtV6AXM_-53Jg9wuWB" 12 | 13 | func TestCompressFromFile(t *testing.T) { 14 | Tinify.SetKey(Key) 15 | source, err := Tinify.FromFile("./test.jpg") 16 | if err != nil { 17 | t.Error(err) 18 | return 19 | } 20 | 21 | err = source.ToFile("./test_output/CompressFromFile.jpg") 22 | if err != nil { 23 | t.Error(err) 24 | return 25 | } 26 | t.Log("Compress successful") 27 | } 28 | 29 | func TestCompressFromBuffer(t *testing.T) { 30 | Tinify.SetKey(Key) 31 | 32 | buf, err := ioutil.ReadFile("./test.jpg") 33 | if err != nil { 34 | t.Error(err) 35 | return 36 | } 37 | source, err := Tinify.FromBuffer(buf) 38 | if err != nil { 39 | t.Error(err) 40 | return 41 | } 42 | 43 | err = source.ToFile("./test_output/CompressFromBuffer.jpg") 44 | if err != nil { 45 | t.Error(err) 46 | return 47 | } 48 | t.Log("Compress successful") 49 | } 50 | 51 | func TestCompressFromUrl(t *testing.T) { 52 | Tinify.SetKey(Key) 53 | url := "http://pic.tugou.com/realcase/1481255483_7311782.jpg" 54 | source, err := Tinify.FromUrl(url) 55 | if err != nil { 56 | t.Error(err) 57 | return 58 | } 59 | err = source.ToFile("./test_output/CompressFromUrl.jpg") 60 | if err != nil { 61 | t.Error(err) 62 | return 63 | } 64 | t.Log("Compress successful") 65 | } 66 | 67 | func TestResizeFromFile(t *testing.T) { 68 | Tinify.SetKey(Key) 69 | source, err := Tinify.FromFile("./test.jpg") 70 | if err != nil { 71 | t.Error(err) 72 | return 73 | } 74 | 75 | err = source.Resize(&Tinify.ResizeOption{ 76 | Method: Tinify.ResizeMethodFit, 77 | Width: 100, 78 | Height: 100, 79 | }) 80 | if err != nil { 81 | t.Error(err) 82 | return 83 | } 84 | 85 | err = source.ToFile("./test_output/ResizeFromFile.jpg") 86 | if err != nil { 87 | t.Error(err) 88 | return 89 | } 90 | 91 | t.Log("Resize successful") 92 | } 93 | 94 | func TestResizeFromBuffer(t *testing.T) { 95 | Tinify.SetKey(Key) 96 | 97 | buf, err := ioutil.ReadFile("./test.jpg") 98 | if err != nil { 99 | t.Error(err) 100 | return 101 | } 102 | source, err := Tinify.FromBuffer(buf) 103 | if err != nil { 104 | t.Error(err) 105 | return 106 | } 107 | 108 | err = source.Resize(&Tinify.ResizeOption{ 109 | Method: Tinify.ResizeMethodScale, 110 | Width: 200, 111 | }) 112 | if err != nil { 113 | t.Error(err) 114 | return 115 | } 116 | 117 | err = source.ToFile("./test_output/ResizesFromBuffer.jpg") 118 | if err != nil { 119 | t.Error(err) 120 | return 121 | } 122 | t.Log("Resize successful") 123 | } 124 | 125 | func TestResizeFromUrl(t *testing.T) { 126 | Tinify.SetKey(Key) 127 | url := "http://pic.tugou.com/realcase/1481255483_7311782.jpg" 128 | source, err := Tinify.FromUrl(url) 129 | if err != nil { 130 | t.Error(err) 131 | return 132 | } 133 | 134 | err = source.Resize(&Tinify.ResizeOption{ 135 | Method: Tinify.ResizeMethodCover, 136 | Width: 300, 137 | Height: 100, 138 | }) 139 | if err != nil { 140 | t.Error(err) 141 | return 142 | } 143 | 144 | err = source.ToFile("./test_output/ResizeFromUrl.jpg") 145 | if err != nil { 146 | t.Error(err) 147 | return 148 | } 149 | t.Log("Resize successful") 150 | } 151 | --------------------------------------------------------------------------------