tiny png

golang 

 

package main

import (
    "encoding/base64"
    "fmt"
    "os"
    "net/http"
    "io/ioutil"
    "strings"
    "path/filepath"
    "encoding/json"
    "errors"
    "bytes"
    "time"
    "sync"
    "strconv"
    "bufio"
    "io"
)

var sum_file_count int = 0
var handle_file_count int = 0
var wg sync.WaitGroup
var invalid_keys map[string]int
var keys_user_count map[string]int

func main() {
    url := "https://api.tinify.com/shrink"

    if len(os.Args) < 2 {
        fmt.Println("==该工具用于使用 TinyPng 服务进行图片压缩处理,需要传一个文件夹参数,程序会自动递归搜索其中的 *.png 文件进行处理,处理完毕后会覆盖原文件==\n")
        return
    }
    dirPath := os.Args[1]

    keys := []string{}
    key_file, err := os.Open("keys.txt")
    defer key_file.Close()
    if err != nil {
        error_msg := fmt.Sprintf("读取 KEY 文件错误,文件路径错误! (path: %s\terror: %s)", "keys.txt", err.Error())
        fmt.Printf(error_msg)
        return
    }
    rd := bufio.NewReader(key_file)
    for {
        line, err := rd.ReadString('\n')
        if err != nil || err == io.EOF {
            break
        }
        if len(line) > 1 {
            keys = append(keys,strings.Trim(line,"\n"))
        }
    }

    /*
    for _,v := range keys{
        println(v)
    }
    */

    files, err := WalkDir(dirPath, ".png")
    if err != nil {
        fmt.Println("请检查目录是否存在~~")
        return
    }

    sum_file_count = len(files)
    if sum_file_count == 0 {
        fmt.Println("没有 *.png 图片需要处理~~")
        return
    }

    fmt.Printf("有 %d 张图片需要处理,请稍候~~\n",sum_file_count)

    invalid_keys = make(map[string]int)
    keys_user_count = make(map[string]int)

    startTime := time.Now()
    for i:=0;i<sum_file_count;i++ {
        wg.Add(1)
        shrink(url,keys,files[i])
    }
    wg.Wait()
    fmt.Printf("任务总耗时: %s\n",time.Since(startTime))

    if len(keys_user_count) > 0 {
        fmt.Printf("\n==KEY的本月已使用次数如下:\n")
        for k,v := range keys_user_count {
            fmt.Printf("KEY: %s\tUseCount: %d\n",k,v)
        }
    }
}

//上传图片,阻塞等待服务器压缩,服务器压缩成功返回结果后,去下载图片覆盖原图片
func shrink(url string, keys []string, filePath string) error {
    defer wg.Done()
    file_bytes, err := ioutil.ReadFile(filePath)
    if err != nil {
        error_msg := fmt.Sprintf("读取本地文件错误,文件路径错误! (path: %s\terror: %s)", filePath, err.Error())
        fmt.Println(error_msg)
        return err
    }

    down_url := ""
    for i:=0;i<len(keys);i++ {if _,ok := invalid_keys[keys[i]];ok {
            continue
        }
        req, err := http.NewRequest("POST", url, bytes.NewReader(file_bytes))
        if err != nil {
            error_msg := fmt.Sprintf("请求 Tiny 出现网络错误! (error: %s)\n",err.Error())
            fmt.Println(error_msg)
            return errors.New(error_msg)
        }
        credentials := base64.StdEncoding.EncodeToString([]byte("api:" + keys[i]))
        req.Header.Set("Authorization", "Basic "+credentials)
        //处理返回结果
        res, err := http.DefaultClient.Do(req)
        if err != nil {
            error_msg := fmt.Sprintf("请求 Tiny 出现网络错误! (error: %s)\n",err.Error())
            fmt.Println(error_msg)
            return errors.New(error_msg)
        }
        status := res.StatusCode
        body, err := ioutil.ReadAll(res.Body)
        if err != nil {
            error_msg := fmt.Sprintf("请求 Tiny 出现网络错误! (error: %s)\n",err.Error())
            fmt.Println(error_msg)
            return errors.New(error_msg)
        }

        //判断HTTP状态码,如果是 415表示文件类型不正确;401表示证书不正确;400表示输入文件为空;5xx表示服务器异常;2xx表示成功
        if status == 401 || status == 400 {
            //KEY不正确,使用下一个KEY
            /*
            if status == 400 {
                fmt.Printf("该 KEY: %s 很可能是格式非法,请留意。\n",keys[i])
            }
            */
            invalid_keys[keys[i]] = 1
            time.Sleep(time.Millisecond * 50)
            continue
        } else if status < 300 && status >= 200 {
            //正确
            right_data := new(RightData)
            json.Unmarshal(body, right_data)
            down_url = right_data.Output.Url

            CompressionCount := res.Header.Get("Compression-Count")
            CompressionCountInt, err := strconv.Atoi(CompressionCount)
            if err != nil {
                CompressionCountInt = 0
            }
            if CompressionCountInt > keys_user_count[keys[i]] {
                keys_user_count[keys[i]] = CompressionCountInt
            }

            break
        } else {
            //其它错误
            error_msg := fmt.Sprintf("Tiny状态码: %d\tTiny错误信息: %s\n",res.StatusCode,string(body))
            fmt.Println(error_msg)
            return errors.New(error_msg)
        }
    }

    if down_url == "" {
        error_msg := "可能所有 KEY 都已经不可用。"
        fmt.Println(error_msg)
        return errors.New(error_msg)
    }

    //CompressionCount := res.Header.Get("Compression-Count")
    //fmt.Printf("Key(%s) UseCount: %s\n",key,CompressionCount)

    //覆盖原文件
    down_bytes := []byte{}
    for i:= 0;i < 10;i++ {
        if i>0 {
            fmt.Printf("下载图片 %s 出错,正在进行第 %d 次尝试...\n",down_url,i+1)
            time.Sleep(time.Millisecond*50)
        }
        res_down, err := http.Get(down_url)
        if err != nil {
            continue
        }
        down_bytes, err = ioutil.ReadAll(res_down.Body)
        if err != nil {
            continue
        }
        break
    }
    if len(down_bytes) == 0 {
        error_msg := fmt.Sprintf("下载图片出错!(filePath: %s\turl: %s\terror: %s)\n", filePath,down_url,err.Error())
        fmt.Printf(error_msg)
        return errors.New(error_msg)
    }

    file, err := os.OpenFile(filePath,os.O_RDWR | os.O_TRUNC, 0600)
    defer file.Close()
    if err != nil {
        error_msg := fmt.Sprintf("清空原图片出错!(filePath: %s\turl: %s\terror: %s)\n", filePath,down_url,err.Error())
        fmt.Printf(error_msg)
        return errors.New(error_msg)
    }
    _, err = file.Write(down_bytes)
    if err != nil {
        error_msg := fmt.Sprintf("覆写原图片出错!(filePath: %s\turl: %s\terror: %s)\n", filePath,down_url,err.Error())
        fmt.Printf(error_msg)
        return errors.New(error_msg)
    }

    handle_file_count += 1
    fmt.Printf("任务进度[%d/%d]\n",handle_file_count,sum_file_count)
    return nil
}

//获取指定目录下的所有文件,不进入下一级目录搜索,可以匹配后缀过滤。
func ListDir(dirPth string, suffix string) (files []string, err error) {
    files = make([]string, 0, 10)
    dir, err := ioutil.ReadDir(dirPth)
    if err != nil {
        return nil, err
    }
    PthSep := string(os.PathSeparator)
    suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写
    for _, fi := range dir {
        if fi.IsDir() { // 忽略目录
            continue
        }
        if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) { //匹配文件
            files = append(files, dirPth+PthSep+fi.Name())
        }
    }
    return files, nil
}

//获取指定目录及所有子目录下的所有文件,可以匹配后缀过滤。
func WalkDir(dirPth, suffix string) (files []string, err error) {
    files = make([]string, 0, 30)
    suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写
    err = filepath.Walk(dirPth, func(filename string, fi os.FileInfo, err error) error { //遍历目录
        //if err != nil { //忽略错误
        // return err
        //}
        if fi.IsDir() { // 忽略目录
            return nil
        }
        if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) {
            files = append(files, filename)
        }
        return nil
    })
    return files, err
}

type RightData struct {
    Input struct {
        Size int     `json:"size"`
        Type string     `json:"type"`
    } `json:"input"`
    Output struct {
        Size int    `json:"size"`
        Type string    `json:"type"`
        Width int    `json:"width"`
        Height int    `json:"height"`
        Ratio float32    `json:"ratio"`
        Url string    `json:"url"`
    }
}

 

06-13
### TinyPNG 使用方法及功能特点 TinyPNG 是一款在线图片压缩工具,其核心功能是通过智能有损压缩技术来减少 PNG 和 JPEG 图片文件的大小,同时尽量保持视觉效果不变[^1]。以下是关于 TinyPNG 的使用方法和功能特点的详细介绍: #### 一、TinyPNG 的功能特点 TinyPNG 的主要功能包括以下几点: - **智能压缩技术**:对于 PNG 文件,TinyPNG 使用一种名为 Smart Lossy Compression 的算法,通过减少颜色通道的数量来降低文件大小,同时保留 PNG 的 Alpha 透明度[^2]。 - **JPEG 压缩支持**:对于 JPEG 文件,TinyPNG 使用传统的有损压缩方法,但能更聪明地处理图像,尽量减少视觉上的质量损失[^2]。 - **批量压缩能力**:用户可以上传多个文件进行批量压缩,显著提高工作效率[^3]。 - **参数调整**:用户可以通过指定参数调整压缩后的图片宽度,高度会自适应缩放[^3]。 - **免费配额**:每个月用户可以免费压缩 500 张图片,超出部分需要申请额外的 API key。 #### 二、TinyPNG 的使用方法 以下是使用 TinyPNG 进行图片压缩的具体步骤: 1. **访问官网**:打开浏览器并访问 [TinyPNG 官网](https://tinypng.com/)。 2. **上传图片**:将需要压缩的图片拖放到网站的上传区域,或者点击上传按钮选择本地文件[^1]。 3. **等待压缩完成**:TinyPNG 会自动对图片进行压缩,并显示压缩前后的文件大小对比[^1]。 4. **下载压缩后的图片**:点击“Download”按钮下载压缩后的图片文件。如果上传了多张图片,可以选择批量下载压缩包。 #### 三、基于 Python 的批量压缩脚本 对于需要频繁处理大量图片的用户,可以使用基于 Python 的批量压缩脚本。该脚本利用 Tinify(TinyPNG 提供的官方 Python 库)实现自动化压缩[^4]。以下是脚本的核心代码示例: ```python import tinify import os # 设置 Tinify API Key tinify.key = "your_api_key_here" # 定义压缩函数 def compress_image(input_path, output_path): source = tinify.from_file(input_path) source.to_file(output_path) # 批量压缩文件夹中的所有图片 def batch_compress(folder_path, output_folder): if not os.path.exists(output_folder): os.makedirs(output_folder) for filename in os.listdir(folder_path): if filename.lower().endswith(('.png', '.jpg', '.jpeg')): input_file = os.path.join(folder_path, filename) output_file = os.path.join(output_folder, filename) compress_image(input_file, output_file) print(f"Compressed: {input_file} -> {output_file}") # 示例调用 batch_compress("path/to/input/folder", "path/to/output/folder") ``` 上述脚本实现了以下功能: - 设置 Tinify API Key[^4]。 - 定义单个图片的压缩函数 `compress_image`[^4]。 - 实现批量压缩文件夹中所有图片的功能 `batch_compress`[^4]。 #### 四、应用场景 TinyPNG 及其相关工具适用于以下场景: - **网站开发者**:优化图片加载速度,提升用户体验。 - **内容创作者**:在发布博客或设计作品时,减少图片文件大小以节省用户流量[^4]。 - **移动应用开发者**:优化应用内的图片资源,减少应用体积,加快下载和安装速度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值