JavaScript >> Javascript 文檔 >  >> Node.js

如何在 Node.js 中使用 FFmpeg 轉換視頻

如何在Node.js中構建命令行界面,使用FFmpeg命令行工具轉換視頻。

開始使用

對於本教程,我們將從頭開始構建一個 Node.js 項目。確保您的機器上安裝了最新的 LTS 版本的 Node.js。如果您沒有安裝 Node.js,請先閱讀本教程,然後再繼續。

如果您安裝了 Node.js,接下來,我們要為我們的項目創建一個新文件夾。這應該放在您在計算機上保存項目的任何位置(例如,~/projects 其中 ~ 是您計算機上的主文件夾或根目錄)。

終端

mkdir video-converter

接下來,cd 進入該文件夾並運行 npm init -f

終端

cd video-converter && npm init -f

這將自動初始化一個 package.json 項目文件夾中的文件。 -f 代表“強制”並跳過生成此文件的自動嚮導(為了速度,我們在這裡跳過它,但可以隨意省略 -f 並按照提示進行操作)。

接下來,我們要修改package.json 創建用於設置項目 typemodule

終端

{
  "name": "video-converter",
  "type": "module",
  "version": "1.0.0",
  ...
}

這樣做可以在 Node.js 中啟用 ESModules 支持,從而允許我們使用 importexport 在我們的代碼中(而不是 require()modules.export .

接下來,我們需要通過 NPM 安裝一個依賴,inquirer

終端

npm i inquirer

我們將使用這個包創建一個命令行提示符,用於收集有關我們要轉換的視頻、我們要輸出的格式以及輸出文件的位置的信息。

要完成我們的設置,我們需要做的最後一件事是下載 ffmpeg 的二進製文件 命令行工具,這將是我們工作的核心。可以在這裡下載(本教程使用的版本是 4.2.1——確保為您的操作系統選擇二進製文件)。

當您下載此文件時,它將是一個 zip 文件。解壓並獲取 ffmpeg 文件(這是二進制腳本)並將其放在項目文件夾的根目錄下(例如,~/video-converter/ffmpeg )。

這就是我們開始構建視頻轉換器所需的全部內容。或者,您可以在此處下載測試視頻進行轉換(確保將其放在項目文件夾的根目錄中以便於訪問)。

添加命令行提示符

為了使我們的轉換腳本對用戶更加友好,我們將實現一個命令行提示符,該提示符詢問用戶問題,然後收集和構造他們的輸入,以便在我們的代碼中輕鬆使用。首先,讓我們創建一個名為 index.js 的文件 在我們的項目內部:

/index.js

import inquirer from 'inquirer'

try {
  // We'll write the code for our script here...
} catch (exception) {
  console.warn(exception.message);
}

首先,我們要為我們的腳本設置一個樣板。因為我們將直接通過 Node.js 在命令行中運行我們的代碼,所以這裡我們沒有導出函數,而是直接在文件中編寫代碼。

為了防止出現任何錯誤,我們使用了 try/catch 堵塞。這將允許我們在 try 中編寫代碼 塊的一部分,如果失敗,“捕獲”任何錯誤並將它們重定向到 catch 語句塊(我們正在註銷 message 錯誤/exception )。

先發製人,在我們文件的頂部,我們正在導入 inquirer 我們之前安裝的軟件包。接下來,我們將使用它來啟動我們的腳本並實現我們在運行 FFmpeg 轉換視頻之前會詢問用戶的問題。

/index.js

import inquirer from 'inquirer';

try {
  inquirer.prompt([
    { type: 'input', name: 'fileToConvert', message: 'What is the path of the file you want to convert?' },
    {
      type: 'list',
      name: 'outputFormat',
      message: 'What format do you want to convert this to?',
      choices: [
        'mp4',
        'mov',
        'mkv',
      ],
    },
    { type: 'input', name: 'outputName', message: 'What should the name of the file be (without format)?' },
    { type: 'input', name: 'outputPath', message: 'Where do you want to store the converted file?' },
  ]).then((answers) => {
    const fileToConvert = answers?.fileToConvert;
    const outputPath = answers?.outputPath;
    const outputName = answers?.outputName;
    const outputFormat = answers?.outputFormat;

    // We'll call to FFmpeg here...
  });
} catch (exception) {
  console.warn(exception.message);
}

在這裡,我們使用 .prompt() inquirer 上的方法 我們從 inquirer 導入 包裹。向它傳遞一個對像數組,每個對象描述一個我們想問用戶的問題。我們為用戶提供兩種類型的問題:inputlist .

input 問題是我們希望用戶輸入(或粘貼)文本作為響應的問題,而 list 問題要求用戶從我們控制的預定義選項列表中進行選擇(例如多項選擇題)。

以下是每個選項的作用:

  • type 將問題類型傳達給詢問者。
  • name 定義了我們從 Inquirer 返回的 answers 對象的屬性,問題的答案將被存儲在其中。
  • message 定義向用戶顯示的問題文本。
  • 對於list 輸入問題,choices 定義用戶可以從中選擇以回答問題的選項列表。

這就是我們定義問題所需要做的一切——Inquirer 將從這里處理剩下的問題。用戶完成所有問題後,我們期望 inquirer.prompt() 方法返回一個 JavaScript Promise,所以在這裡,我們鏈接到對 .then() 的調用 說“在回答完問題後,調用我們傳遞給 .then() 的函數 。”

那個 函數,我們期望 inqurier.prompt() 向我們傳遞一個包含 answers 的對象 用戶給了我們。當我們開始集成 FFmpeg 時,為了使這些值更易於訪問和理解,我們打破了 answers 對象轉換成單獨的變量,每個變量名都與我們期望的 answers 上的屬性名相同 對象(請記住,這些將是 name 我們在每個問題對像上設置的屬性)。

有了這個,在我們繼續實現 FFmpeg 之前,讓我們為我們的變量添加一點驗證,以防用戶跳過問題或將其留空。

/index.js

import inquirer from 'inquirer';
import fs from 'fs';

try {
  inquirer.prompt([
    { type: 'input', name: 'fileToConvert', message: 'What is the path of the file you want to convert?' },
    {
      type: 'list',
      name: 'outputFormat',
      message: 'What format do you want to convert this to?',
      choices: [
        'mp4',
        'mov',
        'mkv',
      ],
    },
    { type: 'input', name: 'outputName', message: 'What should the name of the file be (without format)?' },
    { type: 'input', name: 'outputPath', message: 'Where do you want to store the converted file?' },
  ]).then((answers) => {
    const fileToConvert = answers?.fileToConvert;
    const outputPath = answers?.outputPath;
    const outputName = answers?.outputName;
    const outputFormat = answers?.outputFormat;

    if (!fileToConvert || (fileToConvert && !fs.existsSync(fileToConvert))) {
      console.warn('\nMust pass a video file to convert.\n');
      process.exit(0);
    }

    // We'll implement FFmpeg here...
  });
} catch (exception) {
  console.warn(exception.message);
}

在文件的頂部,首先,我們添加了 fs (內置的 Node.js 文件系統包)。回到 .then() 回調我們對 inquirer.prompt() 的調用 ,我們可以看到一個if 語句被定義在我們的變量下面。

在這裡,我們關心的一個變量是 fileToConvert .這是我們要轉換為三種不同格式之一的原始視頻文件(mp4 , mov , 或 mkv )。為了避免破壞 FFmpeg,我們需要驗證兩件事:首先,用戶輸入了文件路徑(或者我們假設 是文件路徑)並且文件實際上存在 在那條路上。

在這裡,這正是我們正在驗證的內容。首先,fileToConvert 變量包含一個真實值,其次,如果我們將輸入的路徑傳遞給 fs.existsSync() Node.js 可以看到該位置的文件。如果其中任何一個返回錯誤值,我們希望向用戶返回錯誤並立即退出我們的腳本。為此,我們調用 .exit() 傳遞 0 的 Node.js 進程上的方法 作為退出代碼(這告訴 Node.js 退出而沒有任何輸出)。

有了這個,我們就可以開始使用 FFmpeg 了。

連接 FFmpeg

回想一下,在設置我們的項目時,我們下載了所謂的 binary FFmpeg 並將其作為 ffmpeg 放在我們項目的根目錄下 .二進製文件是在單個文件中包含整個程序的文件(與我們在使用 JavaScript 和 Node.js 時可能習慣的通過導入鏈接在一起的一組文件相反)。

為了運行該文件中的代碼,我們需要調用它。在 Node.js 中,我們可以使用 execexecSync child_process 上可用的功能 從 child_process 導出的對象 包(內置在 Node.js 中)。讓我們導入 child_process 現在看看我們如何調用 FFmpeg(這非常簡單):

/index.js

import child_process from 'child_process';
import inquirer from 'inquirer';
import fs from 'fs';

try {
  inquirer.prompt([ ... ]).then((answers) => {
    const fileToConvert = answers?.fileToConvert;
    const outputPath = answers?.outputPath;
    const outputName = answers?.outputName;
    const outputFormat = answers?.outputFormat;

    if (!fileToConvert || (fileToConvert && !fs.existsSync(fileToConvert))) {
      console.warn('\nMust pass a video file to convert.\n');
      process.exit(0);
    }

    child_process.execSync(`./ffmpeg -i ${fileToConvert} ${outputName ? `${outputPath}/${outputName}.${outputFormat}` : `${outputPath}/video.${outputFormat}`}`, {
      stdio: Object.values({
        stdin: 'inherit',
        stdout: 'inherit',
        stderr: 'inherit',
      })
    });
  });
} catch (exception) {
  console.warn(exception.message);
}

在這裡,就在我們的 if 下方 檢查以確保我們的 fileToConvert 存在,我們調用 child_process.execSync() 使用反引號傳遞字符串(這允許我們利用 JavaScript 的字符串插值,或者,將變量的值動態嵌入到字符串中)。

在該字符串中,我們首先編寫 ./ffmpeg .這是告訴 execSync 函數說“找到文件ffmpeg 在當前目錄中並運行它。”緊隨其後,因為我們期望 ffmpeg 為了存在,我們開始傳遞參數(在使用命令行工具時也稱為“標誌”)來告訴 FFmpeg 我們想要做什麼。

在這種情況下,我們首先說我們希望 FFmpeg 轉換輸入文件 -i 這是 fileToConvert 我們從用戶那裡收到。緊接著——用空格分隔——我們將輸出文件的名稱與我們想要將原始文件轉換為該文件的擴展名的格式一起傳遞(例如,如果我們輸入 homer-ice-cream.webm 我們可以將此輸出文件作為 homer.mkv 假設我們在提示中選擇了“mkv”格式)。

因為我們不能 100% 確定我們會從用戶那裡得到什麼輸入,所以我們將輸出值傳遞給 ffmpeg 更有彈性。為此,我們使用 JavaScript 三元運算符(一個濃縮的 if/else 語句)來表示“如果用戶給了我們一個 outputName 對於文件,我們希望將其與 outputPath 連接在一起 和 outputFormat 作為單個字符串,例如 ${outputPath}/${outputName}.${outputFormat} .

如果他們這樣做沒有 給我們一個 outputName ,在三元運算符的“else”部分,我們連接 outputPath 用硬編碼替換 outputName “視頻”以及 outputFormat${outputPath}/video.${outputFormat} .

所有這些都傳遞給 child_process.execSync() 在我們認為我們的工作完成之前,我們的最後一步是將一個選項傳遞給 execSync() 也就是告訴函數如何處理stdio 或來自我們對 ffmpeg 的調用的“標準輸入和輸出” . stdio 是用於引用在 shell 中註銷的輸入、輸出或錯誤的名稱(當我們使用 execSync 時,我們的代碼運行的環境 )。

在這裡,我們需要傳遞 stdio execSync 的選項 它接受一個由三個字符串組成的數組,每個字符串描述如何處理 stdio 的三種類型之一 :stdin (標準輸入),stdout (標準輸出),stderr (標準錯誤)。對於我們的需求,我們不想為這些做任何特殊的事情,而是希望將任何輸出直接記錄到我們執行 Node 腳本的終端。

為此,我們需要傳遞一個類似於 ['inherit', 'inherit', 'inherit'] 的數組 .雖然我們當然可以直接這樣做,但坦率地說:這沒有任何意義。所以,為了添加上下文,這裡我們取一個鍵名等於 stdio 類型的對象 我們要配置的輸出設置和值等於我們要處理輸出的方式(在本例中為 'inherit' 或“只需將 stdio 交給運行此代碼的父級。”)。

接下來,我們將該對像傳遞給 Object.values() 告訴 JavaScript 給我們返回一個數組,該數組只包含對像中每個屬性的值('inherit' 字符串)。換句話說,我們實現了 execSync 的期望 同時還在代碼中為我們添加了一些上下文,這樣我們以後就不會感到困惑了。

而已!作為最後一步,在我們運行代碼之前,讓我們在 package.json 中添加一個 NPM 腳本 用於快速運行我們的轉換器的文件:

包.json

{
  "name": "video-converter",
  "type": "module",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "convert": ""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "inquirer": "^8.2.0"
  }
}

這是一個很小的補充。在這裡,我們添加了一個新屬性 "start""scripts" 對象設置為包含 node index.js 的字符串 .這表示“當我們運行 npm start 在我們的終端中,我們希望您使用 Node.js 來運行 index.js 項目根目錄下的文件。”

而已!讓我們對這一切進行測試,看看我們的轉換器如何工作:

總結

在本教程中,我們學習瞭如何使用 Node.js 編寫命令行腳本來運行 FFmpeg。作為該過程的一部分,我們學習瞭如何設置提示以從用戶那裡收集數據,然後在使用 Node.js child_process.execSync() 運行 FFmpeg 時將該信息傳遞給 FFmpeg 功能。


Tutorial JavaScript 教程
  1. 打字稿:為什麼你應該使用未知而不是任何

  2. Javascript 對像如何引用自身的值?

  3. 使用 http-proxy-middleware 包的自定義響應

  4. Angular 2 組件:輸入和輸出

  5. 我用 React 編寫了一個星球大戰尤達翻譯應用程序

  6. 如何從 Chrome 中的文件輸入中刪除“未選擇文件”工具提示?

  7. 未捕獲的類型錯誤:無法讀取未定義的屬性(讀取“公司名稱”)JS 對象

  1. React Hooks API vs Vue Composition API,通過 useState 探索

  2. 代碼的出現 - 第 8 天

  3. 使用 HTML 和 CSS 製作現代註冊表單

  4. React Round-Up Podcast:使用和教授 React

  5. 使用 StencilJS 創建一個 Web 組件以跟踪您的輸入草稿

  6. 如何使用 Ionic 3 創建 CRUD 待辦事項應用程序

  7. 6個新挑戰

  1. 從前端開發人員到 DevOps:CI/CD 簡介

  2. Redux vs. React Context API vs. AppRun

  3. 慢速模式命令

  4. JavaScript 轉義 HTML |示例代碼