用golang解救PDF文件中的圖片只要200行代碼!

""PDF 里的圖,難道只能看不能拿?""

—— 別急,今天教你用 Go 寫個(gè)""圖片提取器"",把 PDF 里的圖統(tǒng)統(tǒng)打包帶走!

你有沒有遇到過這種情況:

看到一份超贊的 PDF 報(bào)告,里面的圖表清晰又專業(yè),想保存下來做參考?
老師發(fā)的課件里有張神圖,但右鍵不能保存,復(fù)制還糊成馬賽克?
想批量提取 PDF 中的所有插圖,卻只能一張張截圖?
別慌!今天我們就用 Go 語言 + pdfcpu 庫,手搓一個(gè)超實(shí)用的 PDF 圖片提取工具,一鍵把 PDF 里的所有圖片“扒”出來,整齊碼好,命名清晰,絕不手軟!

??? 它是怎么工作的?
簡單來說,這個(gè)工具干了三件事:

讀 PDF:用 pdfcpu 庫解析 PDF 文件結(jié)構(gòu)。
找圖片:遍歷每一頁,找出所有嵌入的圖像對象(不管是 JPG、PNG 還是其他格式)。
存圖片:把每張圖按 名稱-頁碼-對象ID.格式 的規(guī)則命名,存到你指定的文件夾里。
?? 小知識:PDF 里的圖片其實(shí)是“嵌入對象”,每個(gè)都有唯一 ID 和所屬頁碼。我們就是靠這些信息精準(zhǔn)定位每張圖!

?? 用起來有多爽?
假設(shè)你有個(gè)文件叫 report.pdf,只需一行命令:

extractimages report.pdf

它就會自動創(chuàng)建一個(gè)叫 report.pdf.images 的文件夾,里面是這樣的:

chart-5-42.jpg
diagram-12-108.png
image-3-17.jpg
...

想換個(gè)目錄?加個(gè) -o 就行:

extractimages -o my_pics report.pdf

連幫助都貼心準(zhǔn)備好了:

extractimages --help

??♂? 技術(shù)小彩蛋
用 unsafe “偷看”私有字段:pdfcpu.Image 的內(nèi)部字段是私有的,但我們用 unsafe.Pointer 強(qiáng)行轉(zhuǎn)成自定義結(jié)構(gòu)體 MyImage,就能拿到圖片名、頁碼、對象ID等關(guān)鍵信息?。▌e怕,這在 Go 里是合法“黑科技”)
自動防重名:如果文件名沖突,自動加上時(shí)間戳,絕不覆蓋!
智能默認(rèn)輸出目錄:不指定 -o?沒問題,自動用 PDF文件名.images 當(dāng)文件夾!
?? 快來試試吧!
只要你會裝 Go,三步搞定:

安裝依賴:

go mod init extractimages
go get github.com/pdfcpu/pdfcpu

把下面的代碼保存為 main.go

編譯運(yùn)行:

go build -o extractimages main.go
./extractimages your_file.pdf

從此,PDF 再也不是圖片的"監(jiān)獄"——而是你的"圖庫倉庫"!

?? 完整代碼(直接復(fù)制就能用?。?/p>

package main

import (
    "flag"               // 用于處理命令行參數(shù)
    "fmt"               // 用于格式化輸出
    "io"                // 提供基礎(chǔ)的I/O接口
    "log"               // 提供日志功能
    "os"                // 提供操作系統(tǒng)功能接口
    "path/filepath"     // 用于處理文件路徑
    "strings"           // 提供字符串處理函數(shù)
    "time"              // 用于計(jì)時(shí)和時(shí)間戳
    "unsafe"            // 用于不安全的內(nèi)存操作

    "github.com/pdfcpu/pdfcpu/pkg/api"      // pdfcpu庫的API接口
    "github.com/pdfcpu/pdfcpu/pkg/pdfcpu"   // pdfcpu庫的核心功能
)

// MyImage 是對pdfcpu.Image的封裝,用于訪問圖片的詳細(xì)信息
type MyImage struct {
    io.Reader  // 嵌入的Reader接口,用于讀取圖片數(shù)據(jù)
    Name     string // 圖片名稱
    FileType string // 文件類型(如jpg、png等)
    PageNr   int    // 圖片所在的PDF頁碼
    ObjNr    int    // 圖片在PDF中的對象編號
}

// 全局變量,用于統(tǒng)計(jì)提取的圖片數(shù)量
var extractedImagesCount int

// main 程序入口函數(shù)
func main() {
    // 設(shè)置日志格式
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    log.Println("PDF圖片提取工具啟動")

    // 解析命令行參數(shù)
    var help bool
    var outputDir string
    flag.BoolVar(&help, "h", false, "顯示幫助信息")
    flag.BoolVar(&help, "help", false, "顯示幫助信息")
    flag.StringVar(&outputDir, "o", "", "指定輸出圖片的目錄")
    flag.StringVar(&outputDir, "output", "", "指定輸出圖片的目錄")
    flag.Parse()

    // 獲取剩余的非選項(xiàng)參數(shù)(即PDF文件路徑)
    args := flag.Args()
    if help || len(args) < 1 {
        showHelp()
        return
    }

    // 獲取PDF文件的絕對路徑
    pdfFilename, err := filepath.Abs(args[0])
    if err != nil {
        log.Fatalf("獲取文件絕對路徑失敗: %v", err)
    }

    // 檢查文件是否存在
    if _, err := os.Stat(pdfFilename); os.IsNotExist(err) {
        log.Fatalf("PDF文件不存在: %s", pdfFilename)
    }

    // 檢查文件擴(kuò)展名是否為pdf
    if !strings.HasSuffix(strings.ToLower(pdfFilename), ".pdf") {
        log.Printf("警告: 文件擴(kuò)展名不是.pdf,可能不是有效的PDF文件: %s\n", pdfFilename)
    }

    // 確定輸出目錄
    if outputDir == "" {
        // 如果未指定輸出目錄,使用默認(rèn)目錄(PDF文件名.images)
        pdfName := strings.TrimSuffix(filepath.Base(pdfFilename), ".pdf")
        outputDir = pdfName + ".images"
    }
    
    // 獲取輸出目錄的絕對路徑
    outputDir, err = filepath.Abs(outputDir)
    if err != nil {
        log.Fatalf("獲取輸出目錄絕對路徑失敗: %v", err)
    }

    // 創(chuàng)建輸出目錄(如果不存在)
    if err := os.MkdirAll(outputDir, 0755); err != nil {
        log.Fatalf("創(chuàng)建輸出目錄失敗: %v", err)
    }

    log.Printf("開始處理PDF文件: %s", pdfFilename)
    log.Printf("圖片將保存到目錄: %s", outputDir)
    startTime := time.Now()

    // 打開 PDF 文件
    pdfFile, err := os.Open(pdfFilename)
    if err != nil {
        log.Fatalf("打開PDF文件失敗: %v", err)
    }
    // 確保程序結(jié)束時(shí)關(guān)閉文件
    defer pdfFile.Close()

    // 解析 PDF,將提取的圖片寫入指定目錄
    // 使用saveImageToDir函數(shù)作為回調(diào)函數(shù)處理每個(gè)提取到的圖片
    if err := api.ExtractImages(pdfFile, nil, saveImageToDir(outputDir), nil); err != nil {
        log.Fatalf("提取圖片過程中出錯: %v", err)
    }

    elapsedTime := time.Since(startTime)
    log.Printf("圖片提取完成,共提取 %d 張圖片,耗時(shí): %v", extractedImagesCount, elapsedTime)
    log.Printf("所有圖片已保存到目錄: %s", outputDir)
}

// showHelp 顯示幫助信息
func showHelp() {
    fmt.Println("PDF圖片提取工具 - 從PDF文件中提取所有圖片并保存到指定目錄")
    fmt.Println("\n用法:")
    fmt.Println("  extractimages [選項(xiàng)] <PDF文件路徑>")
    fmt.Println("\n選項(xiàng):")
    fmt.Println("  -h, --help              顯示此幫助信息")
    fmt.Println("  -o, --output <目錄路徑>  指定輸出圖片的目錄(默認(rèn): PDF文件名.images)")
    fmt.Println("\n示例:")
    fmt.Println("  extractimages document.pdf")
    fmt.Println("  extractimages -o images_folder document.pdf")
    fmt.Println("\n說明:")
    fmt.Println("  1. 程序會從指定的PDF文件中提取所有圖片")
    fmt.Println("  2. 提取的圖片將保存到指定目錄或默認(rèn)目錄中")
    fmt.Println("  3. 圖片文件名格式: 圖片名稱-頁碼-對象編號.文件類型")
    fmt.Println("  4. 如果指定的目錄不存在,將自動創(chuàng)建")
}

// saveImageToDir 創(chuàng)建一個(gè)回調(diào)函數(shù),用于將提取的圖片保存到指定目錄
// 參數(shù) outputDir 是保存圖片的目標(biāo)目錄路徑
// 返回值是一個(gè)函數(shù),該函數(shù)將在pdfcpu庫提取到圖片時(shí)被調(diào)用
func saveImageToDir(outputDir string) func(pdfcpu.Image, bool, int) error {
    return func(img pdfcpu.Image, singleImgPerPage bool, maxPageDigits int) error {
        // 使用unsafe包將pdfcpu.Image類型強(qiáng)制轉(zhuǎn)換為MyImage類型,以便訪問私有字段
        myImg := (*MyImage)(unsafe.Pointer(&img))
        
        // 處理空文件名的情況
        name := myImg.Name
        if name == "" {
            name = "image"
        }
        
        // 處理未知文件類型的情況
        fileType := myImg.FileType
        if fileType == "" {
            fileType = "jpg" // 默認(rèn)使用jpg格式
        }
        
        // 生成圖片文件名,格式為:圖片名稱-頁碼-對象編號.文件類型
        filename := fmt.Sprintf("%s-%d-%d.%s", name, myImg.PageNr, myImg.ObjNr, fileType)
        
        // 生成完整的文件路徑
        filePath := filepath.Join(outputDir, filename)
        
        // 檢查文件是否已存在,如果存在則添加時(shí)間戳
        if _, err := os.Stat(filePath); err == nil {
            timestamp := time.Now().Format("20060102_150405")
            filename = fmt.Sprintf("%s-%d-%d_%s.%s", name, myImg.PageNr, myImg.ObjNr, timestamp, fileType)
            filePath = filepath.Join(outputDir, filename)
            log.Printf("圖片文件已存在,將使用新文件名: %s", filename)
        }
        
        // 創(chuàng)建目標(biāo)文件
        outFile, err := os.Create(filePath)
        if err != nil {
            log.Printf("創(chuàng)建圖片文件失敗: %v", err)
            return err
        }
        
        // 確保文件關(guān)閉
        defer outFile.Close()
        
        // 將圖片數(shù)據(jù)寫入文件
        n, err := io.Copy(outFile, img.Reader)
        if err != nil {
            log.Printf("寫入圖片數(shù)據(jù)失敗: %v", err)
            return err
        }
        
        // 增加統(tǒng)計(jì)計(jì)數(shù)
        extractedImagesCount++
        
        // 輸出成功處理的圖片文件名和大小
        log.Printf("成功提取圖片 #%d: %s (大小: %d 字節(jié))", extractedImagesCount, filename, n)
        return nil
    }
}

?? 從此 PDF 里的圖,都是你的!

快去試試吧,說不定下一個(gè)被你提取到的,就是價(jià)值千金的設(shè)計(jì)圖呢~

往期部分文章列表

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容