You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Swift中打包目录生成ePub且不压缩mimetype文件?

问题分析与解决方案

你的推测完全正确——NSFileCoordinator的.forUploading选项会对所有文件进行压缩,无法单独排除mimetype文件,这就是Apple Books无法识别你生成的epub的核心原因。ePub规范明确要求:mimetype必须是zip包中的第一个文件条目,且不能被压缩,否则阅读器会判定文件无效。

下面提供两种可行的Swift解决方案,均能满足epub的格式要求:


方案1:使用系统Archive API(macOS 11+/iOS 14+)

直接利用Foundation框架的Archive类手动构建zip包,可精确控制每个文件的压缩策略:

import Foundation

func createEPUB(from sourceDirURL: URL, to destinationURL: URL) throws {
    // 清理已存在的目标文件
    if FileManager.default.fileExists(atPath: destinationURL.path) {
        try FileManager.default.removeItem(at: destinationURL)
    }
    
    // 创建用于写入的Archive实例
    guard let archive = Archive(url: destinationURL, accessMode: .create) else {
        throw NSError(domain: "EPUBBuilder", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法创建zip归档"])
    }
    
    // 1. 优先添加mimetype,设置为无压缩
    let mimetypeURL = sourceDirURL.appendingPathComponent("mimetype")
    guard let mimetypeData = try? Data(contentsOf: mimetypeURL) else {
        throw NSError(domain: "EPUBBuilder", code: 2, userInfo: [NSLocalizedDescriptionKey: "未找到mimetype文件"])
    }
    try archive.addEntry(withPath: "mimetype", data: mimetypeData, compressionMethod: .none)
    
    // 2. 遍历目录中其余文件,按默认压缩添加
    let fileEnumerator = FileManager.default.enumerator(at: sourceDirURL, includingPropertiesForKeys: [.isDirectoryKey])!
    for case let fileURL as URL in fileEnumerator {
        let relativePath = fileURL.relativePath(from: sourceDirURL)
        guard relativePath != "mimetype" else { continue } // 跳过已添加的mimetype
        
        if fileURL.hasDirectoryPath {
            // 添加目录条目
            try archive.addEntry(withPath: relativePath + "/", type: .directory)
        } else {
            // 添加文件,使用默认压缩级别
            let fileData = try Data(contentsOf: fileURL)
            try archive.addEntry(withPath: relativePath, data: fileData)
        }
    }
}

// 扩展URL获取相对路径
extension URL {
    func relativePath(from baseURL: URL) -> String {
        guard let relativeURL = self.relativePath(to: baseURL) else {
            return self.lastPathComponent
        }
        return relativeURL.path
    }
}

方案2:使用第三方库ZipArchive(兼容旧版本系统)

如果需要支持macOS 11以下版本,ZipArchive是成熟的选择,可直接控制单个文件的压缩级别:

  1. 通过Swift Package Manager或CocoaPods引入ZipArchive
  2. 使用以下代码构建epub:
import ZipArchive

func createEPUBWithZipArchive(from sourceDirURL: URL, to destinationURL: URL) throws {
    // 清理已存在的目标文件
    if FileManager.default.fileExists(atPath: destinationURL.path) {
        try FileManager.default.removeItem(at: destinationURL)
    }
    
    // 先创建基础zip包
    guard !SSZipArchive.createZipFile(atPath: destinationURL.path, withContentsOfDirectory: sourceDirURL.path) else {
        throw NSError(domain: "EPUBBuilder", code: 3, userInfo: [NSLocalizedDescriptionKey: "初始zip创建失败"])
    }
    
    // 打开zip包,替换mimetype为无压缩版本
    guard SSZipArchive.unzipOpenFile(destinationURL.path) else {
        throw NSError(domain: "EPUBBuilder", code: 4, userInfo: [NSLocalizedDescriptionKey: "无法打开zip文件"])
    }
    
    let mimetypeURL = sourceDirURL.appendingPathComponent("mimetype")
    let mimetypeData = try Data(contentsOf: mimetypeURL)
    SSZipArchive.unzipAddData(mimetypeData, fileName: "mimetype", compressionLevel: 0) // 0代表无压缩
    
    SSZipArchive.unzipCloseFile()
}

两种方案都能确保mimetype文件以无压缩状态作为zip的第一个条目,完全符合ePub规范,生成的文件可正常被Apple Books识别。

内容的提问来源于stack exchange,提问作者KevinF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:50:25