見出し画像

📦Node.jsでADM-ZIPを使ってZIPの圧縮と解凍や圧縮後のファイルサイズで分割

Node.js で ZIP ファイルを圧縮したり解凍するときに archiver モジュールを使った方法がよく知られていますが、もっと使いやすい adm-zip モジュールを見つけたので紹介します。

サンプルとしてダウンロードする画像は Lorem Picsum というサービスを利用し、ランダムに画像を取得しています。

npmモジュール

画像をダウンロードするために axios モジュール、レスポンスヘッダーの content-type からファイルの拡張子を取得するために mime-types モジュールを使用します。

npm i -D axios mime-types adm-zip

adm-zip モジュールで ZIP ファイルの圧縮と解凍を行います。

ZIPの圧縮

ここでは 50 枚の画像をダウンロードして圧縮してみます。

import path from 'path'
import axios from 'axios'
import mime from 'mime-types'
import admZip from 'adm-zip'

// adm-zipのインスタンス生成
const zip = new admZip()

// 50枚の画像を取得
for (let i = 1; i <= 50; i++) {
  // 画像をダウンロード
  const response = await axios.get('https://picsum.photos/1280/720', {responseType: 'arraybuffer'})

  console.log(`${i}番目の画像を取得中...`)
 
  // ファイル追加
  zip.addFile(`image-${i}.${mime.extension(response.headers['content-type'])}`, response.data)
}

// zipファイル書き出し
zip.writeZip(path.resolve(__dirname, './images.zip'))

画像をダウンロードするときに axios.get() のオプションで {responseType: 'arrayBuffer'} を指定し、Buffer として取得するようにします。

zip.addFile(ファイル名, Buffer データ) で圧縮対象に追加し、zip.writeZip(ファイルパス) で ZIP ファイルとして書き出します。

ZIPの解凍

先ほど書き出した images.zip を今度は解凍してみます。

import path from 'path'
import admZip from 'adm-zip'

// zipファイル読み込み
const zip = new admZip(path.resolve(__dirname, './images.zip'))
// すベて解凍
zip.extractAllTo(path.resolve(__dirname, './image-output'), true)

zip.extractAllTo(ファイルパス, 上書きするか) で ZIP を解凍できます。

import path from 'path'
import admZip from 'adm-zip'

// zipファイル読み込み
const zip = new admZip(path.resolve(__dirname, './images.zip'))

for (const zipEntry of zip.getEntries()) {
  // __MACOSXフォルダなど、フォルダは無視
  if (zipEntry.isDirectory) {
    continue
  }

  // ファイル名
  console.log(zipEntry.entryName)
  // Bufferデータ
  console.log(zipEntry.getData())
}

また、zip.getEntries() 関数を使えば、ファイルを解凍することなく、メモリ上で解凍後のファイルを取得できます。

ZIPファイルのサイズで分割

環境によっては一度に何 MB までしかアップロードできませんという感じでファイルサイズに制限をかけている場合があります。ここでは、1MB までのファイル制限があるとしてコードを書いていきます。

import path from 'path'
import axios from 'axios'
import mime from 'mime-types'
import admZip from 'adm-zip'

// adm-zipのインスタンス生成
let zip = new admZip()
// zipファイルの連番
let count = 0

// 50枚の画像を取得
for (let i = 1; i <= 50; i++) {
  // 画像をダウンロード
  const response = await axios.get('https://picsum.photos/1280/720', {responseType: 'arraybuffer'})

  console.log(`${i}番目の画像を取得中...`)

  // 1MB以上になるとき
  if (zip.toBuffer().byteLength + response.data.byteLength >= 1000000) {
    // zipファイル書き出し
    zip.writeZip(path.resolve(__dirname, `./images/image-${++count}.zip`))
    // リセット
    zip = admZip()
  }

  // ファイル追加
  zip.addFile(`image-${i}.${mime.extension(response.headers['content-type'])}`, response.data)
}

// まだzip化するファイルが存在するとき
if (zip.getEntryCount()) {
  // zipファイル書き出し
  zip.writeZip(path.resolve(__dirname, `./images/image-${++count}.zip`))
}

zip.toBuffer().byteLength で ZIP 化後のファイルサイズが取得できるので、それとダウンロードした画像のバイト数を足して 1MB を超える場合には、ファイルを書き出します。

これで、各 ZIP ファイルが 1MB 以内に収まるように分割できます。

API

実装されているプロパティや関数のまとめです。

constructor([filePath])

コンストラクタにはファイルパスを指定できます。

import path from 'path'
import admZip from 'adm-zip'

// メモリ上に作成
const zip = new admZip()

// ZIPファイル読み込み
const zip = new admZip(path.resolve(__dirname, './images.zip'))

引数を指定しない場合は、メモリ上に ZIP ファイルのインスタンスを作成します。

zip.getEntries()

ZIP ファイル内のフォルダやファイルを配列として返します。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))

for (const zipEntry of zip.getEntries()) {
  // 各フォルダやファイルの情報
  console.log(zipEntry)
}

zip.getEntry(name)

引数にファイル名を指定すると、zipEntry を返します。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))
// image-9.jpegの情報
const zipEntry = zip.getEntry('image-9.jpeg')

zip.getEntries() 関数ではすべての zipEntry を走査しますが、zip.getEntry() は ZIP ファイル内の特定のファイルの zipEntry を取得できます。

zip.getEntryCount()

ZIP 内のファイル数を返します。

zip.readFile(entry)

引数にファイル名または zipEntry を指定すると、Buffer データを返します。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))
// image-9.jpegのBufferデータ
const zipBuffer = zip.readFile('image-9.jpeg')

zip.readFileAsync(entry, callback)

zip.readFile() 関数の非同期版です。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))

zip.readFileAsync('image-9.jpeg', (data, err) => {
  // image-9.jpegのBufferデータ
  console.log(data)
})

zip.readAsText(entry[, encoding = 'utf8'])

第 1 引数にファイル名または zipEntry を指定するとテキストを返します。第 2 引数にはファイルのエンコード(初期値は utf8)を指定します。

zip.readAsTextAsync(entry, callback[, encoding = 'utf8'])

zip.readAsText() 関数の非同期版です。

zip.deleteFile(entry)

引数にファイル名または zipEntry を指定すると、ZIP ファイル内の特定のファイルを削除します。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))

// image-9.jpegを削除
zip.deleteFile('image-9.jpeg')

zip.addZipComment(comment)

ZIP ファイルにコメントを設定します。

zip.getZipComment()

ZIP ファイルのコメントを返します。

zip.addZipEntryComment(entry, comment)

第 1 引数にファイル名または zipEntry を指定すると、特定のファイルに対してコメントを設定します。ただし、最大 65535 文字までです。

zip.getZipEntryComment(entry)

引数にファイル名または zipEntry を指定すると、特定のファイルのコメントを返します。

zip.updateFile(entry, content)

第 1 引数にファイル名または zipEntry、第 2 引数に Buffer データを指定すると、特定のファイルのデータを更新します。ファイル名はそのままでデータだけ置き換えたいような場合に使います。

zip.addLocalFile(localPath[, zipPath = '', zipName])

第 1 引数にローカルファイルのパスを指定すると、ディスク上にあるファイルを追加します。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip()
// ファイル追加
zip.addLocalFile(path.resolve(__dirname, './image-9.jpeg'))

第 2 引数には ZIP ファイル内のどこのフォルダに追加するか(初期値はルート階層)を指定できます。また、第 3 引数にはファイル名を指定でき、何も指定しなければそのままファイル名が使われます。

zip.addLocalFolder(localPath[, zipPath = '', filter])

第 1 引数にローカルファイルのパスを指定すると、ディスク上にあるフォルダを入れ子になった中身もすべて含めて追加します。

第 2 引数には ZIP ファイル内のどこのフォルダに追加するか(初期値はルート階層)を指定できます。また、第 3 引数には正規表現または文字列を指定して追加するファイルを制限できます。

zip.addFile(entryName, content[, comment, attr])

第 1 引数にファイル名、第 2 引数に Buffer データを指定するとファイルを追加します。

import path from 'path'
import axios from 'axios'
import admZip from 'adm-zip'

const zip = new admZip()
// 画像ダウンロード
const response = await axios.get('https://picsum.photos/1280/720', {responseType: 'arraybuffer'})
// 画像を追加
zip.addFile('image-random.jpeg', response.data)

また、第 1 引数にスラッシュ終わりの文字列、第 2 引数に Null Buffer を指定するとフォルダを作成できます。

// フォルダ作成
zip.addFile('folder/', Buffer.from([null]))

zip.toBuffer()

ZIP ファイル全部の Buffer データを返します。

zip.extractEntryTo(entry, targetPath[, maintainEntryPath = true, overwrite = false])

第 1 引数にファイル名または zipEntry、第 2 引数にファイルパスを指定すると、特定のファイルを解凍できます。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))

// ファイル解凍
zip.extractEntryTo('image-9.jpeg', path.resolve(__dirname, './image'))

第 3 引数には true または false を指定できますが、それによって書き出し方が変わります。

// /home/user/folder/subfolder/image.jpeg
zip.extractEntryTo("folder/subfolder/image.jpeg", "/home/user/", true)

// /home/user/image.jpeg
zip.extractEntryTo("folder/subfolder/image.jpeg", "/home/user/", false)

また、第 4 引数には上書きするかどうかを指定します。

zip.extractAllTo(targetPath[, overwrite = false])

第 1 引数にファイルパスを指定すると、すべてを解凍します。

zip.writeZip(targetFileName)

引数にファイル名を指定すると、ZIP を圧縮します。

zipEntry.entryName

ファイルパスを返します。

import path from 'path'
import admZip from 'adm-zip'

const zip = new admZip(path.resolve(__dirname, './images.zip'))

for (const zipEntry of zip.getEntries()) {
  // ファイルパス
  console.log(zipEntry.entryName)
}

zipEntry.extra

拡張データを返します。

zipEntry.comment

コメントを返します。

zipEntry.name

ファイル名を返します。

zipEntry.isDirectory

フォルダかどうかを返します。

zipEntry.getCompressedData()

圧縮データを返します。

zipEntry.getData()

解凍データを返します。

zipEntry.setData(value)

引数に文字列または Buffer データを指定すると、データを設定します。

zipEntry.header

ファイル作成日やファイルサイズなど、ファイルに関する様々な情報が格納された zipEntryHeader オブジェクトを返します。

zipEntry.packHeader()

出力される ZIP ファイルに書き込まれる CEN ヘッダーと拡張データ、コメントを Buffer データとして返します。

zipEntry.toString()

zipEntry オブジェクトを JSON 形式の文字列として返します。

今すぐ始めるCSSレシピブック

この記事が気に入ったらサポートをしてみませんか?