Node.js × FFmpeg で音声抽出・変換:MP3/AAC/Opus への最短手順

動画コンテンツから音声だけを抽出したい、異なる音声フォーマットに変換したい、そんなニーズが日々増加しています。YouTube の動画を音声として保存したり、ポッドキャスト配信のために音声ファイルを最適化したりと、音声処理の需要は多岐にわたります。
本記事では、Node.js と FFmpeg を組み合わせて、動画から音声を抽出し、MP3、AAC、Opus といった主要な音声フォーマットに変換する方法を詳しく解説いたします。初心者の方でも実装できるよう、環境構築から具体的なコード例まで段階的にご説明しますので、ぜひ最後までお読みください。
背景
動画コンテンツから音声のみを取得する需要の増加
近年、動画プラットフォームの普及により、動画コンテンツから音声のみを抽出したいというニーズが急激に増加しています。特に以下のような用途で需要が高まっているのです。
- ポッドキャスト制作: 動画配信の音声部分をポッドキャスト用に変換
- 学習コンテンツ: オンライン講義の音声を移動中に聞くため
- 音楽制作: 動画から楽器演奏部分の音声を抽出
- アクセシビリティ向上: 視覚障害者向けの音声コンテンツ制作
これらの用途において、音声の品質を保ちながら効率的に変換できる仕組みが求められています。
様々な音声フォーマットの特徴と用途
音声フォーマットにはそれぞれ異なる特徴があり、用途に応じて使い分けることが重要です。
以下の図で、主要な音声フォーマットの関係性を確認してみましょう。
mermaidgraph TB
source[元動画ファイル] --> extract[音声抽出]
extract --> mp3[MP3<br/>汎用性重視]
extract --> aac[AAC<br/>効率性重視]
extract --> opus[Opus<br/>品質重視]
mp3 --> use1[ポッドキャスト<br/>音楽配信]
aac --> use2[ストリーミング<br/>モバイル配信]
opus --> use3[VoIP通話<br/>リアルタイム配信]
各フォーマットの特徴を表で整理すると以下のようになります。
# | フォーマット | 圧縮効率 | 音質 | 対応デバイス | 主な用途 |
---|---|---|---|---|---|
1 | MP3 | 標準 | 良好 | ほぼ全て | 汎用的な音声配信 |
2 | AAC | 高い | 優秀 | 多くのデバイス | ストリーミング配信 |
3 | Opus | 最高 | 最高品質 | 対応機器限定 | 高品質通話・配信 |
Node.js での音声処理の優位性
Node.js を使用することで、音声処理において以下のメリットを享受できます。
- 非同期処理: 大量の音声ファイルを効率的に並行処理
- 豊富なライブラリ: fluent-ffmpeg など実用性の高いライブラリが充実
- Web アプリケーション連携: API サーバーとして音声変換機能を提供
- スケーラビリティ: クラウド環境での大規模処理に適している
これらの特徴により、Node.js は音声処理システムの構築において非常に有効な選択肢となっています。
課題
FFmpeg の複雑なコマンド構文
FFmpeg は非常に強力な音声・動画処理ツールですが、その反面、コマンド構文が複雑で習得に時間がかかるという課題があります。
以下のような課題が挙げられます。
mermaidflowchart TD
problem[FFmpegの課題] --> syntax[複雑な構文]
problem --> options[膨大なオプション]
problem --> format[フォーマット固有の設定]
syntax --> difficulty1[学習コストが高い]
options --> difficulty2[設定ミスが起きやすい]
format --> difficulty3[最適化が困難]
例えば、単純な音声抽出でも以下のような複雑なコマンドが必要になります。
bash# 基本的な音声抽出
ffmpeg -i input.mp4 -vn -acodec copy output.aac
# 品質を指定した変換
ffmpeg -i input.mp4 -vn -ab 128k -ar 44100 -ac 2 output.mp3
この複雑さが、初心者にとって大きな障壁となっているのです。
Node.js と FFmpeg の連携方法
Node.js から FFmpeg を呼び出す方法にも、いくつかの課題があります。
- プロセス管理: 子プロセスとして FFmpeg を実行する際のエラーハンドリング
- リソース管理: メモリ使用量と CPU 負荷の制御
- 進捗監視: 長時間の変換処理における進捗状況の把握
- エラー処理: FFmpeg のエラーメッセージの適切な処理
これらの課題を解決しないと、安定した音声処理システムを構築することはできません。
フォーマット別の最適な設定の選択
各音声フォーマットには、用途に応じた最適な設定があります。しかし、適切な設定を選択するためには、以下のような専門知識が必要になります。
- ビットレート設定: 音質とファイルサイズのバランス
- サンプリングレート: 対象コンテンツに適した周波数設定
- エンコーダー選択: フォーマット毎の最適なエンコーダー
- 品質パラメーター: VBR/CBR の選択と品質レベル設定
これらの設定を間違えると、音質の劣化や不必要に大きなファイルサイズになってしまう可能性があります。
解決策
fluent-ffmpeg ライブラリの活用
複雑な FFmpeg のコマンド構文を簡素化するために、fluent-ffmpeg ライブラリを活用します。このライブラリにより、JavaScript の直感的な API で FFmpeg の機能を利用できるようになります。
mermaidsequenceDiagram
participant App as Node.jsアプリ
participant Fluent as fluent-ffmpeg
participant FFmpeg as FFmpeg本体
App->>Fluent: 音声変換リクエスト
Fluent->>FFmpeg: コマンド生成・実行
FFmpeg->>Fluent: 処理結果
Fluent->>App: 変換完了通知
fluent-ffmpeg の主な利点は以下の通りです。
- 簡潔な API: メソッドチェーンによる直感的な記述
- エラーハンドリング: Node.js スタイルのエラー処理
- 進捗監視: リアルタイムでの変換進捗取得
- 豊富なオプション: FFmpeg の全機能を JavaScript から利用可能
環境構築からコード実装までの手順
必要なツールのインストール
まず、FFmpeg と Node.js 環境を準備します。
bash# macOSの場合(Homebrew)
brew install ffmpeg
# Ubuntuの場合
sudo apt update
sudo apt install ffmpeg
# Windowsの場合
# https://ffmpeg.org/download.html からダウンロード
次に、Node.js プロジェクトを初期化し、必要なパッケージをインストールします。
bash# プロジェクトの初期化
yarn init -y
# fluent-ffmpegのインストール
yarn add fluent-ffmpeg
# TypeScript環境の場合(推奨)
yarn add -D @types/fluent-ffmpeg typescript ts-node
基本的な設定ファイルの作成
TypeScript 環境での設定ファイルを作成します。
typescript// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
基本的なディレクトリ構造を以下のように設定します。
graphqlproject/
├── src/
│ ├── converters/
│ │ ├── mp3Converter.ts
│ │ ├── aacConverter.ts
│ │ └── opusConverter.ts
│ ├── utils/
│ │ └── audioUtils.ts
│ └── index.ts
├── input/ # 変換元ファイル
├── output/ # 変換後ファイル
└── package.json
エラーハンドリングとパフォーマンス最適化
安定したシステムを構築するために、適切なエラーハンドリングとパフォーマンス最適化が必要です。
エラーハンドリングの実装
fluent-ffmpeg でのエラーハンドリングを実装します。
typescript// src/utils/audioUtils.ts
import ffmpeg from 'fluent-ffmpeg';
export interface ConversionOptions {
inputPath: string;
outputPath: string;
format: 'mp3' | 'aac' | 'opus';
quality?: string;
bitrate?: string;
}
export class AudioConverter {
/**
* 基本的な音声変換処理
* @param options 変換オプション
* @returns Promise<void>
*/
static async convertAudio(
options: ConversionOptions
): Promise<void> {
return new Promise((resolve, reject) => {
const command = ffmpeg(options.inputPath);
command
.on('start', (commandLine) => {
console.log('変換開始:', commandLine);
})
.on('progress', (progress) => {
console.log(
`進捗: ${progress.percent?.toFixed(2)}%`
);
})
.on('end', () => {
console.log('変換完了');
resolve();
})
.on('error', (err, stdout, stderr) => {
console.error('変換エラー:', err.message);
console.error('FFmpeg stderr:', stderr);
reject(
new Error(
`音声変換に失敗しました: ${err.message}`
)
);
})
.output(options.outputPath);
this.applyFormatSettings(command, options);
command.run();
});
}
}
パフォーマンス最適化の実装
大量のファイルを処理する際のパフォーマンス最適化を行います。
typescript// src/utils/batchProcessor.ts
import {
AudioConverter,
ConversionOptions,
} from './audioUtils';
import { promiseLimit } from 'promise-limit';
export class BatchAudioProcessor {
private concurrencyLimit: number;
constructor(concurrencyLimit = 3) {
this.concurrencyLimit = concurrencyLimit;
}
/**
* 複数ファイルの並行変換処理
* @param conversions 変換設定の配列
* @returns Promise<void>
*/
async processBatch(
conversions: ConversionOptions[]
): Promise<void> {
const limit = promiseLimit(this.concurrencyLimit);
const promises = conversions.map((options) =>
limit(() => this.processWithRetry(options))
);
await Promise.allSettled(promises);
}
/**
* リトライ機能付きの変換処理
*/
private async processWithRetry(
options: ConversionOptions,
maxRetries = 3
): Promise<void> {
for (
let attempt = 1;
attempt <= maxRetries;
attempt++
) {
try {
await AudioConverter.convertAudio(options);
return;
} catch (error) {
console.warn(
`試行 ${attempt}/${maxRetries} 失敗:`,
error
);
if (attempt === maxRetries) {
throw error;
}
// 指数バックオフで待機
await this.delay(1000 * Math.pow(2, attempt - 1));
}
}
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) =>
setTimeout(resolve, ms)
);
}
}
具体例
MP3 への変換実装
MP3 は最も汎用性の高い音声フォーマットです。ポッドキャストや一般的な音楽配信に適しています。
MP3 変換クラスの実装
typescript// src/converters/mp3Converter.ts
import ffmpeg from 'fluent-ffmpeg';
import path from 'path';
export interface Mp3ConversionOptions {
inputPath: string;
outputPath: string;
bitrate?: '128k' | '192k' | '256k' | '320k';
quality?: 'low' | 'medium' | 'high';
}
export class Mp3Converter {
/**
* MP3形式への変換
* 音質とファイルサイズのバランスを考慮した設定を適用
*/
static async convertToMp3(
options: Mp3ConversionOptions
): Promise<void> {
const {
inputPath,
outputPath,
bitrate = '192k',
quality = 'medium',
} = options;
return new Promise((resolve, reject) => {
const command = ffmpeg(inputPath)
.audioBitrate(bitrate)
.audioCodec('libmp3lame')
.audioChannels(2)
.audioFrequency(44100)
.format('mp3');
// 品質設定の適用
this.applyQualitySettings(command, quality);
command
.on('start', (commandLine) => {
console.log('MP3変換開始:', commandLine);
})
.on('progress', (progress) => {
console.log(
`MP3変換進捗: ${progress.percent?.toFixed(2)}%`
);
})
.on('end', () => {
console.log('MP3変換完了:', outputPath);
resolve();
})
.on('error', (err) => {
reject(
new Error(`MP3変換エラー: ${err.message}`)
);
})
.save(outputPath);
});
}
/**
* 品質レベルに応じた詳細設定
*/
private static applyQualitySettings(
command: ffmpeg.FfmpegCommand,
quality: string
): void {
switch (quality) {
case 'high':
command.audioBitrate('320k').audioQuality(0);
break;
case 'medium':
command.audioBitrate('192k').audioQuality(2);
break;
case 'low':
command.audioBitrate('128k').audioQuality(4);
break;
}
}
}
MP3 変換の使用例
typescript// src/examples/mp3Example.ts
import { Mp3Converter } from '../converters/mp3Converter';
import path from 'path';
async function convertVideoToMp3() {
const inputPath = path.join(
__dirname,
'../../input/sample-video.mp4'
);
const outputPath = path.join(
__dirname,
'../../output/audio-output.mp3'
);
try {
await Mp3Converter.convertToMp3({
inputPath,
outputPath,
bitrate: '192k',
quality: 'medium',
});
console.log('MP3変換が正常に完了しました!');
} catch (error) {
console.error('MP3変換に失敗しました:', error);
}
}
convertVideoToMp3();
AAC への変換実装
AAC は MP3 よりも効率的な圧縮を実現し、同じビットレートでより高い音質を提供します。ストリーミング配信やモバイルデバイス向けの配信に最適です。
AAC 変換クラスの実装
typescript// src/converters/aacConverter.ts
import ffmpeg from 'fluent-ffmpeg';
export interface AacConversionOptions {
inputPath: string;
outputPath: string;
bitrate?: '96k' | '128k' | '192k' | '256k';
profile?: 'aac_low' | 'aac_he' | 'aac_he_v2';
}
export class AacConverter {
/**
* AAC形式への変換
* 高効率な圧縮とストリーミング配信に最適化
*/
static async convertToAac(
options: AacConversionOptions
): Promise<void> {
const {
inputPath,
outputPath,
bitrate = '128k',
profile = 'aac_low',
} = options;
return new Promise((resolve, reject) => {
const command = ffmpeg(inputPath)
.audioCodec('aac')
.audioBitrate(bitrate)
.audioChannels(2)
.audioFrequency(44100)
.format('aac');
// AACプロファイル設定
this.configureAacProfile(command, profile);
command
.on('start', (commandLine) => {
console.log('AAC変換開始:', commandLine);
})
.on('progress', (progress) => {
console.log(
`AAC変換進捗: ${progress.percent?.toFixed(2)}%`
);
})
.on('end', () => {
console.log('AAC変換完了:', outputPath);
resolve();
})
.on('error', (err) => {
reject(
new Error(`AAC変換エラー: ${err.message}`)
);
})
.save(outputPath);
});
}
/**
* AACプロファイルの設定
* 用途に応じて最適なプロファイルを選択
*/
private static configureAacProfile(
command: ffmpeg.FfmpegCommand,
profile: string
): void {
switch (profile) {
case 'aac_low':
// 汎用的なAAC-LC設定
command.addOption('-profile:a', 'aac_low');
break;
case 'aac_he':
// 低ビットレート用HE-AAC
command.addOption('-profile:a', 'aac_he');
break;
case 'aac_he_v2':
// 超低ビットレート用HE-AAC v2
command.addOption('-profile:a', 'aac_he_v2');
break;
}
}
}
AAC 変換の使用例
typescript// src/examples/aacExample.ts
import { AacConverter } from '../converters/aacConverter';
import path from 'path';
async function convertVideoToAac() {
const inputPath = path.join(
__dirname,
'../../input/sample-video.mp4'
);
const outputPath = path.join(
__dirname,
'../../output/audio-output.aac'
);
try {
await AacConverter.convertToAac({
inputPath,
outputPath,
bitrate: '128k',
profile: 'aac_low',
});
console.log('AAC変換が正常に完了しました!');
} catch (error) {
console.error('AAC変換に失敗しました:', error);
}
}
convertVideoToAac();
Opus への変換実装
Opus は最新の音声コーデックで、特に音声通話やリアルタイム配信において優秀な性能を発揮します。低遅延と高音質を両立させた設計が特徴です。
Opus 変換クラスの実装
typescript// src/converters/opusConverter.ts
import ffmpeg from 'fluent-ffmpeg';
export interface OpusConversionOptions {
inputPath: string;
outputPath: string;
bitrate?: '64k' | '96k' | '128k' | '160k';
complexity?: number; // 0-10の範囲
application?: 'voip' | 'audio' | 'lowdelay';
}
export class OpusConverter {
/**
* Opus形式への変換
* 高品質かつ低遅延の音声配信に最適化
*/
static async convertToOpus(
options: OpusConversionOptions
): Promise<void> {
const {
inputPath,
outputPath,
bitrate = '128k',
complexity = 10,
application = 'audio',
} = options;
return new Promise((resolve, reject) => {
const command = ffmpeg(inputPath)
.audioCodec('libopus')
.audioBitrate(bitrate)
.audioChannels(2)
.audioFrequency(48000) // Opusは48kHzが推奨
.format('opus');
// Opus固有の設定を適用
this.configureOpusSettings(
command,
complexity,
application
);
command
.on('start', (commandLine) => {
console.log('Opus変換開始:', commandLine);
})
.on('progress', (progress) => {
console.log(
`Opus変換進捗: ${progress.percent?.toFixed(2)}%`
);
})
.on('end', () => {
console.log('Opus変換完了:', outputPath);
resolve();
})
.on('error', (err) => {
reject(
new Error(`Opus変換エラー: ${err.message}`)
);
})
.save(outputPath);
});
}
/**
* Opus固有の詳細設定
* 用途に応じてエンコーダーパラメーターを最適化
*/
private static configureOpusSettings(
command: ffmpeg.FfmpegCommand,
complexity: number,
application: string
): void {
// 複雑度設定(品質vs速度のトレードオフ)
command.addOption(
'-compression_level',
complexity.toString()
);
// アプリケーション用途の設定
switch (application) {
case 'voip':
// 音声通話最適化
command.addOption('-application', 'voip');
break;
case 'audio':
// 一般音声コンテンツ最適化
command.addOption('-application', 'audio');
break;
case 'lowdelay':
// 低遅延配信最適化
command.addOption('-application', 'lowdelay');
break;
}
// VBR(可変ビットレート)を有効化
command.addOption('-vbr', 'on');
}
}
Opus 変換の使用例
typescript// src/examples/opusExample.ts
import { OpusConverter } from '../converters/opusConverter';
import path from 'path';
async function convertVideoToOpus() {
const inputPath = path.join(
__dirname,
'../../input/sample-video.mp4'
);
const outputPath = path.join(
__dirname,
'../../output/audio-output.opus'
);
try {
await OpusConverter.convertToOpus({
inputPath,
outputPath,
bitrate: '128k',
complexity: 10,
application: 'audio',
});
console.log('Opus変換が正常に完了しました!');
} catch (error) {
console.error('Opus変換に失敗しました:', error);
}
}
convertVideoToOpus();
バッチ処理の実装
複数のファイルを効率的に処理するためのバッチ処理システムを実装します。
バッチ処理管理クラス
typescript// src/batch/batchManager.ts
import { Mp3Converter } from '../converters/mp3Converter';
import { AacConverter } from '../converters/aacConverter';
import { OpusConverter } from '../converters/opusConverter';
import { promiseLimit } from 'promise-limit';
import path from 'path';
import fs from 'fs/promises';
export interface BatchConversionJob {
inputPath: string;
outputPath: string;
format: 'mp3' | 'aac' | 'opus';
options?: any;
}
export class BatchConversionManager {
private concurrencyLimit: number;
constructor(concurrencyLimit = 3) {
this.concurrencyLimit = concurrencyLimit;
}
/**
* バッチ変換の実行
* 複数ファイルの並行処理を制御
*/
async executeBatch(
jobs: BatchConversionJob[]
): Promise<void> {
const limit = promiseLimit(this.concurrencyLimit);
console.log(`${jobs.length}件の変換ジョブを開始します`);
const results = await Promise.allSettled(
jobs.map((job) => limit(() => this.executeJob(job)))
);
// 結果の集計
const successful = results.filter(
(r) => r.status === 'fulfilled'
).length;
const failed = results.filter(
(r) => r.status === 'rejected'
).length;
console.log(
`バッチ処理完了: 成功 ${successful}件, 失敗 ${failed}件`
);
// 失敗したジョブの詳細を出力
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(
`ジョブ ${index + 1} 失敗:`,
result.reason
);
}
});
}
/**
* 単一ジョブの実行
*/
private async executeJob(
job: BatchConversionJob
): Promise<void> {
const {
inputPath,
outputPath,
format,
options = {},
} = job;
// 出力ディレクトリの作成
await fs.mkdir(path.dirname(outputPath), {
recursive: true,
});
// フォーマットに応じた変換処理
switch (format) {
case 'mp3':
await Mp3Converter.convertToMp3({
inputPath,
outputPath,
...options,
});
break;
case 'aac':
await AacConverter.convertToAac({
inputPath,
outputPath,
...options,
});
break;
case 'opus':
await OpusConverter.convertToOpus({
inputPath,
outputPath,
...options,
});
break;
default:
throw new Error(`未対応のフォーマット: ${format}`);
}
}
}
バッチ処理の実用例
typescript// src/examples/batchExample.ts
import {
BatchConversionManager,
BatchConversionJob,
} from '../batch/batchManager';
import path from 'path';
import fs from 'fs/promises';
async function runBatchConversion() {
const inputDir = path.join(__dirname, '../../input');
const outputDir = path.join(__dirname, '../../output');
// 入力ディレクトリからファイル一覧を取得
const files = await fs.readdir(inputDir);
const videoFiles = files.filter((file) =>
/\.(mp4|avi|mov|mkv)$/i.test(file)
);
// バッチジョブの作成
const jobs: BatchConversionJob[] = [];
for (const file of videoFiles) {
const baseName = path.parse(file).name;
const inputPath = path.join(inputDir, file);
// 各フォーマットでの変換ジョブを作成
jobs.push(
{
inputPath,
outputPath: path.join(outputDir, `${baseName}.mp3`),
format: 'mp3',
options: { bitrate: '192k', quality: 'medium' },
},
{
inputPath,
outputPath: path.join(outputDir, `${baseName}.aac`),
format: 'aac',
options: { bitrate: '128k', profile: 'aac_low' },
},
{
inputPath,
outputPath: path.join(
outputDir,
`${baseName}.opus`
),
format: 'opus',
options: { bitrate: '128k', complexity: 10 },
}
);
}
// バッチ処理の実行
const batchManager = new BatchConversionManager(2); // 同時実行数: 2
await batchManager.executeBatch(jobs);
}
runBatchConversion().catch(console.error);
バッチ処理の流れを図で表すと以下のようになります。
mermaidflowchart TD
start[バッチ処理開始] --> scan[入力フォルダスキャン]
scan --> create[変換ジョブ作成]
create --> queue[ジョブキューに追加]
queue --> parallel[並行処理開始]
parallel --> job1[ジョブ1: MP3変換]
parallel --> job2[ジョブ2: AAC変換]
parallel --> job3[ジョブ3: Opus変換]
job1 --> result1[変換結果1]
job2 --> result2[変換結果2]
job3 --> result3[変換結果3]
result1 --> summary[結果集計]
result2 --> summary
result3 --> summary
summary --> finish[バッチ処理完了]
このバッチ処理システムにより、大量の動画ファイルから音声を一括で抽出し、複数のフォーマットに同時変換することが可能になります。
まとめ
本記事では、Node.js と FFmpeg を組み合わせた音声抽出・変換システムの構築方法を詳しく解説いたしました。
fluent-ffmpeg ライブラリを活用することで、複雑な FFmpeg のコマンドラインインターフェースを簡潔な JavaScriptAPI に置き換えることができました。また、MP3、AAC、Opus という主要な音声フォーマットそれぞれに最適化された変換処理を実装し、品質とパフォーマンスのバランスを取った実用的なシステムを構築できました。
特に重要なポイントは以下の通りです。
- エラーハンドリング: 変換処理の安定性を確保するための適切なエラー処理
- パフォーマンス最適化: 並行処理とリソース管理による効率的な変換処理
- フォーマット最適化: 各音声フォーマットの特徴を活かした設定の適用
- バッチ処理: 大量ファイルの一括変換を可能にする実用的な機能
これらの実装により、ポッドキャスト制作、動画コンテンツの音声化、ストリーミング配信など、様々な用途に対応できる音声処理システムを構築することができます。
今後は、Web アプリケーションとしての API 化や、クラウド環境での大規模処理、リアルタイム変換機能の実装なども検討していただけるでしょう。本記事が皆様の音声処理システム開発の一助となれば幸いです。
関連リンク
- article
Node.js × FFmpeg で音声抽出・変換:MP3/AAC/Opus への最短手順
- article
Node.js × FFmpeg でサムネイル自動生成:キーフレーム抽出とスプライト化
- article
Node.js × FFmpeg で H.264/H.265/AV1 最適化:ビットレートと CRF の正解
- article
Node.js × FFmpeg の基本操作:エンコード・デコード・リマックス徹底解説
- article
Node.js × FFmpeg 入門:環境構築から最初の動画変換まで完全ガイド
- article
GitHub Actions × Node.js:テストとデプロイを自動化する
- article
Node.js × FFmpeg で音声抽出・変換:MP3/AAC/Opus への最短手順
- article
Node.js のロギング設計:winston・pino の活用法
- article
Node.js × FFmpeg でサムネイル自動生成:キーフレーム抽出とスプライト化
- article
Node.js × FFmpeg で H.264/H.265/AV1 最適化:ビットレートと CRF の正解
- article
Node.js × FFmpeg の基本操作:エンコード・デコード・リマックス徹底解説
- article
React 開発を加速する GitHub Copilot 活用レシピ 20 選
- article
Prisma の公式ドキュメントを使い倒すためのコツ
- article
GitHub Actions × Node.js:テストとデプロイを自動化する
- article
Pinia × TypeScript:型安全なストア設計入門
- article
Obsidian デイリーノート活用術:毎日の思考ログを資産に変える方法
- article
Git で特定のコミットを打ち消す!git revert の正しい使い方
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来