PDF转doc(golang 跨平台版)
PDF转doc(golang 跨平台版)
git地址
码云地址: https://gitee.com/mr_yanghw/go-utils.git
使用到的技术
- golang
- tesseract
实现
1. 代码
// 打包文件参考 https://blog.csdn.net/daily886/article/details/97527117
// mac : go build -o pdf2Doc-mac cmd/pdf2img.go
// linux: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o pdf2Doc-mac cmd/pdf2img.go
// windows: CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows go build -o pdf2Doc-win.exe cmd/pdf2img.go (需要安装 brew install mingw-w64)
func main() {
welcome := `================================================================================
欢迎使用疯狂的 "IT小🐑" PDF转图片工具
================================================================================`
fmt.Println(welcome)
fmt.Println(`共有 5 个参数:
参数1:目标pdf文件路径
参数2:目标文件名称
参数3:电脑系统 1:mac 2:windows
参数4:可选,100 - 800,代表的图片清晰度,数值越大,图片越清晰,相对执行时间也会变长,建议:400
参数5:1/0,是否关闭无痕模式,1关闭 0开启,默认 开启
例如:/Users/yixia/Documents 大学英语B统考1 1 400 0`)
flag.Parse()
var splitStr string = "/"
path := flag.Arg(0)
if path == "" {
fmt.Println("🐑温馨提示:缺少参数, 请参考上述提示~~~")
return
}
fileName := flag.Arg(1)
if fileName == "" {
fmt.Println("🐑温馨提示:缺少参数, 请参考上述提示~~~")
return
}
// 1 mac 2 windows 默认为 1
osMac := flag.Arg(2)
if osMac != "" && osMac != "1" && osMac != "2" {
fmt.Println("🐑温馨提示:电脑系统参数错误, 请参考上述提示~~~")
return
}
if osMac == "" {
osMac = "1"
splitStr = "/"
} else if osMac == "1" {
splitStr = "/"
} else if osMac == "2" {
splitStr = "\\"
}
var imgDpi float64 = 400
imgDpiStr := flag.Arg(3)
if imgDpiStr != "" {
imgDpiRt := utils.ToFloat64(imgDpi)
if imgDpiRt != 0 {
imgDpi = imgDpiRt
}
}
closeClear := flag.Arg(4)
clearStr := "开启"
if closeClear == "1" {
clearStr = "关闭"
}
var outTempPath string
var outImgPath string
var outDocPath string
if !PathExists(path) {
fmt.Println("🐑: 抱歉,输入地址有误,请检查~~~")
return
}
if strings.HasSuffix(path, splitStr) {
path = path[0 : len(path)-1]
}
outImgPath = CreateDateDir(fmt.Sprintf("%s%s%s", path, splitStr, "image"))
outDocPath = CreateDateDir(fmt.Sprintf("%s%s%s", path, splitStr, "doc"))
outTempPath = CreateDateDir(fmt.Sprintf("%s%s%s", path, splitStr, "temp"))
if strings.HasSuffix(fileName, "pdf") {
fileName = fileName[0 : len(fileName)-4]
}
fmt.Println("")
fmt.Println(fmt.Sprintf("输入目标文件:%s%s%s.pdf", path, splitStr, fileName))
fmt.Println(fmt.Sprintf("输出图片路径:%s", outImgPath))
fmt.Println(fmt.Sprintf("输出txt路径:%s", outTempPath))
fmt.Println(fmt.Sprintf("输出Doc路径:%s", outDocPath))
fmt.Println(fmt.Sprintf("清晰度:%f", imgDpi))
fmt.Println(fmt.Sprintf("无痕模式:%s", clearStr))
fmt.Println("")
fmt.Println(" 🐑 🐑 🐑 开始干活了,驾驾驾 ~~~ 🐑 🐑 🐑 ")
fmt.Println("")
fmt.Println(" 🐑 🐑 🐑PDF转化为图片开始~~~🐑 🐑 🐑 ")
// 处理PDF->image
imagePathArr, err := pdfDeal(path, fileName, outImgPath, imgDpi, splitStr)
if err != nil {
fmt.Println("🐑: 处理PDF->image失败了, 😭😭😭~~~")
return
}
fmt.Println("🐑 🐑 🐑 ✌️✌️✌️ 阶段 1 完美收工 ~~~ ✌️✌️✌️ 🐑 🐑 🐑 ")
fmt.Println("")
// 处理image->doc
txtTempPathArr, err := batchRunImg2Doc(outImgPath, fileName, outDocPath, outTempPath, splitStr, osMac)
if err != nil {
fmt.Println("🐑: 处理image->doc失败了, 😭😭😭~~~")
return
}
if closeClear != "1" {
clean(imagePathArr, txtTempPathArr)
}
fmt.Println("🐑 🐑 🐑 ✌️✌️✌️ 任务执行成功了 ~~~ ✌️✌️✌️ 🐑 🐑 🐑")
}
type FileEntity struct {
Idx int `json:"idx"`
FileName string `json:"file_name"`
}
func FileExist(path string) bool {
_, err := os.Lstat(path)
return !os.IsNotExist(err)
}
func PathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
if err != nil {
panic("🐑: 抱歉,输入地址有误,请检查~~~")
}
return false
}
func CreateDateDir(path string) string {
if _, err := os.Stat(path); os.IsNotExist(err) {
// 必须分成两步:先创建文件夹、再修改权限
os.Mkdir(path, 0777) //0777也可以os.ModePerm
os.Chmod(path, 0777)
}
return path
}
func clean(imagePathArr []string, txtTempPathArr []string) {
var errr error
for _, s := range imagePathArr {
errr = os.Remove(s)
}
for _, s := range txtTempPathArr {
errr = os.Remove(s)
}
if errr != nil {
fmt.Println("🐑: 抱歉过程文件删除失败了,请手动清理吧 😭😭😭~~~")
}
}
func pdfDeal(path string, fileName string, outImgPath string, imgDpi float64, splitStr string) ([]string, error) {
doc, err := fitz.New(fmt.Sprintf("%s%s%s.pdf", path, splitStr, fileName))
if err != nil {
return nil, err
panic(err)
}
defer doc.Close()
// Extract pages as images
imgList := make([]string, 0)
totalNum := doc.NumPage()
for n := 0; n < totalNum; n++ {
imgPath := fmt.Sprintf("%s%s%s_%d.jpg", outImgPath, splitStr, fileName, n)
imgList = append(imgList, imgPath)
if FileExist(imgPath) {
fmt.Println(fmt.Sprintf("🐑: 文件存在跳过,path:%s", imgPath))
continue
}
img, err := doc.ImageDPI(n, imgDpi)
if err != nil {
return nil, err
}
f, err := os.Create(imgPath)
if err != nil {
return nil, err
}
err = jpeg.Encode(f, img, &jpeg.Options{jpeg.DefaultQuality})
if err != nil {
return nil, err
}
f.Close()
fmt.Println(fmt.Sprintf("🐑: 一共需要转换 %d 张图, 已完成 %d 张,请耐心等待一下~~~", totalNum, n))
}
fmt.Println(fmt.Sprintf("🐑: PDF转化的图片存放地址:%s", outImgPath))
return imgList, nil
}
func batchRunImg2Doc(targetDir string, filePre string, outfileDir string, outTempPath string, splitStr string, osMac string) ([]string, error) {
fmt.Println("")
fmt.Println(" 🐑 🐑 🐑图片转化为txt文件开始~~~🐑 🐑 🐑 ")
files, err := ioutil.ReadDir(targetDir)
if err != nil {
fmt.Println("打开文件夹失败")
}
if len(files) == 0 {
return nil, nil
}
fileArr := make([]FileEntity, 0)
for _, file := range files {
fileName := file.Name()
if !strings.HasPrefix(fileName, filePre) {
continue
}
idx := strings.Split(strings.Split(fileName, ".")[0], "_")[1]
fileArr = append(fileArr, FileEntity{
Idx: utils.ToInt(idx),
FileName: fileName,
})
// 排序
sort.Sort(IdxSort(fileArr))
}
if len(files) == 0 {
return nil, nil
}
outFileArr := make([]string, 0)
for i, fileVo := range fileArr {
fileName := fileVo.FileName
outFileName := fmt.Sprintf("%s%s%s_%d", outTempPath, splitStr, filePre, i)
outFileArr = append(outFileArr, outFileName+".txt")
if FileExist(outFileName + ".txt") {
fmt.Println(fmt.Sprintf("🐑: 文件存在跳过,path:%s", outFileName+".txt"))
continue
}
cmd := "tesseract"
if osMac == "1" {
cmd = "tesseract.ext"
}
_, err = runCmd(cmd, []string{fmt.Sprintf("%s%s%s", targetDir, splitStr, fileName), outFileName, "-l", "chi_sim+eng"})
if err != nil {
fmt.Println(fmt.Sprintf("🐑: 图片转换失败:%s", fileName))
return nil, err
}
fmt.Println(fmt.Sprintf("🐑: 完成 => %s", fileVo.FileName))
}
fmt.Println("🐑 🐑 🐑 ✌️✌️✌️ 阶段 2 完美收工 ~~~ ✌️✌️✌️ 🐑 🐑 🐑 ")
fmt.Println("")
fmt.Println(" 🐑 🐑 🐑txt文件合并开始~~~🐑 🐑 🐑 ")
if strings.HasSuffix(outfileDir, splitStr) {
outfileDir = outfileDir + filePre + ".docx"
} else {
outfileDir = outfileDir + splitStr + filePre + ".docx"
}
//创建一个新文件,写入内容 5 句 “http://c.biancheng.net/golang/”
filePath := outfileDir
_, err = os.Create(filePath)
if err != nil {
fmt.Println("文件创建失败", err)
return nil, err
}
for _, path := range outFileArr {
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
fmt.Println("文件打开失败", err)
}
//写入文件时,使用带缓存的 *Writer
write := bufio.NewWriter(file)
//及时关闭file句柄
defer file.Close()
write.WriteString(ReadFile2String(path))
//Flush将缓存的文件真正写入到文件中
write.Flush()
}
fmt.Println(fmt.Sprintf("🐑: doc文件存放地址:%s", filePath))
return outFileArr, nil
}
func ReadFile2String(readPath string) string {
//获得一个file
f, err := os.Open(readPath)
if err != nil {
fmt.Println("read fail")
return ""
}
//把file读取到缓冲区中
defer f.Close()
var chunk []byte
buf := make([]byte, 1024)
for {
//从file读取到buf中
n, err := f.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("read buf fail", err)
return ""
}
//说明读取结束
if n == 0 {
break
}
//读取到最终的缓冲区中
chunk = append(chunk, buf[:n]...)
}
return string(chunk)
//fmt.Println(string(chunk))
}
func runCmd(cmdName string, argsArr []string) (string, error) {
cmd := exec.Command(cmdName, argsArr...)
var ioReader io.ReadCloser
var err error
if ioReader, err = cmd.StderrPipe(); err != nil {
fmt.Println("获取输出对象失败 ~~~")
return "", err
}
defer ioReader.Close()
if err = cmd.Start(); err != nil {
fmt.Println("命令执行失败 ~~~")
return "", err
}
var outByte []byte
if outByte, err = ioutil.ReadAll(ioReader); err != nil {
fmt.Println("获取执行结果内容失败 ~~~")
return "", err
}
return string(outByte), err
}
type IdxSort []FileEntity
func (mt IdxSort) Len() int { return len(mt) }
func (mt IdxSort) Swap(i, j int) { mt[i], mt[j] = mt[j], mt[i] }
func (mt IdxSort) Less(i, j int) bool {
return mt[i].Idx < mt[j].Idx
}
2. 打包
# mac :
go build -o pdf2Doc-mac cmd/pdf2img.go
# linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o pdf2Doc-mac cmd/pdf2img.go
# windows: (需要安装 brew install mingw-w64)
CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows go build -o pdf2Doc-win.exe cmd/pdf2img.go
3. 安装环境
mac安装软件
1. homebrew 和git,打开终端执行下述命令 参考(https://zhuanlan.zhihu.com/p/372576355)
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
2. 切换 homebrew 源
# 1.修改 brew.git 为阿里源
git -C "$(brew --repo)" remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git
# 2.修改 homebrew-core.git 为阿里源
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git
# 3.zsh 替换 brew bintray 镜像
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc
3. 上一步如果没有安装 git 需要安装 git,打开终端执行下述命令
brew install git
4. 安装 tesseract,打开终端执行下述命令 参考(https://blog.csdn.net/qq_38680405/article/details/105605928)
brew install tesseract
windows安装软件
1. 下载 tesseract (https://github.com/UB-Mannheim/tesseract/wiki)
2. 配置 "tesseract" 环境变量,可参考(https://jingyan.baidu.com/article/00a07f3876cd0582d128dc55.html)
2.1 找到 tesseract 的安装目录,
2.2 打开win10系统的“此电脑”文件夹,然后在上面的选项中,点击“计算机”选项,在顶部,
2.3 接着我们点击打开“系统属性”,进入系统的信息界面,离成功就快了;
2.4 在系统的信息界面,点击左侧的【高级系统设置】按钮打开系统属性,
2.5 在“系统属性”界面的最下面,就有“环境变量”的选项,请点击该选项,进入环境变量的编辑框之中。
2.6 打开之后我们就可以进行 添加一个配置项 如 tesseract 的安装目录为 “D:\Users\tesseract” 的话, 找到对应的文件下的bin 目录,如“ D:\Users\tesseract\bin”
4.使用
mac:
1.打开终端
2.将文件拉至终端
3.后边追加参数 增加参数
参数1:目标pdf文件路径
参数2:目标文件名称
参数3:电脑系统 1:mac 2:windows
参数4:可选,100 - 800,代表的图片清晰度,数值越大,图片越清晰,相对执行时间也会变长,建议:400
参数5:1/0,是否关闭无痕模式,1关闭 0开启,默认开启
追加的参数例如:/Users/yixia/Documents 大学英语B统考1 1 400 1
命令预览如: /Users/yixia/Desktop/归档/pdf2Doc-mac /Users/yixia/Documents 大学英语B统考 1 400 1
4. 回车执行命令
window:
1. 打开CMD
2. 找到 可执行文件 “pdf2Doc-win.exe”
3. 执行命令 pdf2Doc-win.exe /Users/yixia/Documents 大学英语B统考 2 400 1
版权声明:本文为qq_29323645原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。