mediacript 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUICK_START.md +154 -0
- package/README.md +240 -0
- package/cli.mjs +7 -0
- package/convert.js +257 -0
- package/package.json +33 -0
- package/src/config/index.ts +136 -0
- package/src/index.ts +278 -0
- package/src/transcript/groq.ts +50 -0
- package/src/transcript/index.ts +60 -0
- package/src/transcript/openai.ts +34 -0
- package/src/types/index.ts +34 -0
- package/src/utils/ffmpegCheck.ts +78 -0
- package/src/utils/ffmpegOperations.ts +144 -0
- package/src/utils/fileHelpers.ts +60 -0
- package/src/workflow/state.ts +140 -0
- package/tsconfig.json +21 -0
package/QUICK_START.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# 🚀 Quick Start Guide
|
|
2
|
+
|
|
3
|
+
## First Time
|
|
4
|
+
|
|
5
|
+
1. **Install dependencies**:
|
|
6
|
+
```bash
|
|
7
|
+
npm install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
2. **Run the CLI**:
|
|
11
|
+
```bash
|
|
12
|
+
npm start
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
3. **Configure your API Keys** (when prompted):
|
|
16
|
+
- Groq (recommended): https://console.groq.com
|
|
17
|
+
- OpenAI: https://platform.openai.com
|
|
18
|
+
|
|
19
|
+
## Available Workflows
|
|
20
|
+
|
|
21
|
+
### 🎬 For Videos
|
|
22
|
+
|
|
23
|
+
#### Complete Pipeline
|
|
24
|
+
```
|
|
25
|
+
Convert video → Extract audio → Transcribe
|
|
26
|
+
```
|
|
27
|
+
Best for: Fully processing a video, optimizing and transcribing
|
|
28
|
+
|
|
29
|
+
#### Extract and Transcribe
|
|
30
|
+
```
|
|
31
|
+
Extract audio → Transcribe
|
|
32
|
+
```
|
|
33
|
+
Best for: Transcribing videos without converting them
|
|
34
|
+
|
|
35
|
+
#### Only Convert
|
|
36
|
+
```
|
|
37
|
+
Convert video (H.264/AAC)
|
|
38
|
+
```
|
|
39
|
+
Best for: Optimizing videos for web/streaming
|
|
40
|
+
|
|
41
|
+
#### Only Extract Audio
|
|
42
|
+
```
|
|
43
|
+
Extract audio (MP3)
|
|
44
|
+
```
|
|
45
|
+
Best for: Extracting soundtrack from videos
|
|
46
|
+
|
|
47
|
+
### 🎵 For Audio
|
|
48
|
+
|
|
49
|
+
#### Convert and Transcribe
|
|
50
|
+
```
|
|
51
|
+
Convert audio → Transcribe
|
|
52
|
+
```
|
|
53
|
+
Best for: Processing audio in non-optimized format
|
|
54
|
+
|
|
55
|
+
#### Only Transcribe
|
|
56
|
+
```
|
|
57
|
+
Transcribe audio
|
|
58
|
+
```
|
|
59
|
+
Best for: Transcribing podcasts, interviews, etc.
|
|
60
|
+
|
|
61
|
+
#### Only Convert
|
|
62
|
+
```
|
|
63
|
+
Convert audio (MP3)
|
|
64
|
+
```
|
|
65
|
+
Best for: Standardizing audio format
|
|
66
|
+
|
|
67
|
+
## Practical Examples
|
|
68
|
+
|
|
69
|
+
### Transcribe a lecture video
|
|
70
|
+
```bash
|
|
71
|
+
# Place lecture_video.mp4 in the folder
|
|
72
|
+
npm start
|
|
73
|
+
# Select: "Extract audio from video + Transcribe"
|
|
74
|
+
# Result: lecture_video_audio.mp3 and lecture_video_audio.txt
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Convert podcast and transcribe
|
|
78
|
+
```bash
|
|
79
|
+
# Place podcast.wav in the folder
|
|
80
|
+
npm start
|
|
81
|
+
# Select: "Convert audio + Transcribe"
|
|
82
|
+
# Result: podcast_converted.mp3 and podcast_converted.txt
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Complete video pipeline
|
|
86
|
+
```bash
|
|
87
|
+
# Place presentation.mkv in the folder
|
|
88
|
+
npm start
|
|
89
|
+
# Select: "Convert video + Extract audio + Transcribe"
|
|
90
|
+
# Result:
|
|
91
|
+
# - presentation_converted.mp4 (optimized video)
|
|
92
|
+
# - presentation_converted_audio.mp3
|
|
93
|
+
# - presentation_converted_audio.txt
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Tips
|
|
97
|
+
|
|
98
|
+
### 💡 Transcription
|
|
99
|
+
- **Groq is faster** and cheaper than OpenAI
|
|
100
|
+
- Configure both keys to have automatic fallback
|
|
101
|
+
- Transcription works best with clear audio
|
|
102
|
+
|
|
103
|
+
### 📊 Tracking
|
|
104
|
+
- Progress is displayed in real-time
|
|
105
|
+
- Each step shows elapsed time
|
|
106
|
+
- State is saved in `.workflow-state.json`
|
|
107
|
+
|
|
108
|
+
### 🗂️ Organization
|
|
109
|
+
- Generated files stay in the same directory as the original
|
|
110
|
+
- Suffixes are automatically added:
|
|
111
|
+
- `_converted` for conversions
|
|
112
|
+
- `_audio` for extracted audio
|
|
113
|
+
- `.txt` for transcriptions
|
|
114
|
+
|
|
115
|
+
### ⚡ Performance
|
|
116
|
+
- Video conversions can take time (depends on size)
|
|
117
|
+
- Audio extraction is fast
|
|
118
|
+
- Transcription depends on audio size and service
|
|
119
|
+
|
|
120
|
+
## Useful Commands
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Development mode (auto-reload)
|
|
124
|
+
npm run dev
|
|
125
|
+
|
|
126
|
+
# Compile for production
|
|
127
|
+
npm run build
|
|
128
|
+
|
|
129
|
+
# Simple converter (old)
|
|
130
|
+
npm run convert
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Troubleshooting
|
|
134
|
+
|
|
135
|
+
### "FFmpeg not found"
|
|
136
|
+
- Install FFmpeg following the README instructions
|
|
137
|
+
- Restart the terminal after installing
|
|
138
|
+
|
|
139
|
+
### "No API key configured"
|
|
140
|
+
- Run again and configure when prompted
|
|
141
|
+
- Or manually edit: `~/.config/ffmpeg-simple-converter/config.json`
|
|
142
|
+
|
|
143
|
+
### Transcription failed
|
|
144
|
+
- Check if you have credits/quota in the API
|
|
145
|
+
- Try the other service (Groq or OpenAI)
|
|
146
|
+
- Check internet connection
|
|
147
|
+
|
|
148
|
+
### File doesn't appear in the list
|
|
149
|
+
- Check if the extension is supported
|
|
150
|
+
- Make sure you're in the correct directory
|
|
151
|
+
|
|
152
|
+
## 📚 More Information
|
|
153
|
+
|
|
154
|
+
See the complete [README.md](README.md) for technical details and advanced topics.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# FFmpeg Simple Converter
|
|
2
|
+
|
|
3
|
+
Powerful and flexible CLI for converting videos/audio and AI transcription, working on Linux, Mac, and Windows.
|
|
4
|
+
|
|
5
|
+
## 🌟 Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Cross-platform**: Works on Linux, macOS, and Windows
|
|
8
|
+
- 🔄 **Multi-Step Workflow**: Combine multiple operations in a single flow
|
|
9
|
+
- 🎙️ **AI Transcription**: Support for Groq (fast) and OpenAI Whisper
|
|
10
|
+
- 💾 **State Management**: Saves progress of each workflow step
|
|
11
|
+
- 🔑 **Persistent Configuration**: API keys saved locally and securely
|
|
12
|
+
- 📊 **Visual Progress**: Track each step of the process
|
|
13
|
+
|
|
14
|
+
## 📋 Requirements
|
|
15
|
+
|
|
16
|
+
- **Node.js** `>= 16`
|
|
17
|
+
- **FFmpeg** installed and available in PATH
|
|
18
|
+
|
|
19
|
+
### Installing FFmpeg
|
|
20
|
+
|
|
21
|
+
#### Windows
|
|
22
|
+
```bash
|
|
23
|
+
# With Chocolatey
|
|
24
|
+
choco install ffmpeg
|
|
25
|
+
|
|
26
|
+
# With Scoop
|
|
27
|
+
scoop install ffmpeg
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### macOS
|
|
31
|
+
```bash
|
|
32
|
+
brew install ffmpeg
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Linux
|
|
36
|
+
```bash
|
|
37
|
+
# Ubuntu/Debian
|
|
38
|
+
sudo apt update && sudo apt install ffmpeg
|
|
39
|
+
|
|
40
|
+
# Fedora
|
|
41
|
+
sudo dnf install ffmpeg
|
|
42
|
+
|
|
43
|
+
# Arch Linux
|
|
44
|
+
sudo pacman -S ffmpeg
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Verify installation:
|
|
48
|
+
```bash
|
|
49
|
+
ffmpeg -version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🚀 Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 💡 Usage
|
|
59
|
+
|
|
60
|
+
### Interactive Mode (Recommended)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm start
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The CLI will:
|
|
67
|
+
1. ✅ Check if FFmpeg is installed
|
|
68
|
+
2. 🔑 Request API keys on first run (optional)
|
|
69
|
+
3. 📁 List media files in the current directory
|
|
70
|
+
4. 🎯 Allow you to choose the desired workflow
|
|
71
|
+
|
|
72
|
+
### Available Workflows
|
|
73
|
+
|
|
74
|
+
#### For Videos 🎬
|
|
75
|
+
- **Convert video + Extract audio + Transcribe**: Complete pipeline
|
|
76
|
+
- **Extract audio from video + Transcribe**: To transcribe videos
|
|
77
|
+
- **Only convert video**: Optimize video (H.264/AAC)
|
|
78
|
+
- **Only extract audio from video**: Extract audio as MP3
|
|
79
|
+
|
|
80
|
+
#### For Audio 🎵
|
|
81
|
+
- **Convert audio + Transcribe**: Convert and transcribe
|
|
82
|
+
- **Only transcribe audio**: Direct transcription
|
|
83
|
+
- **Only convert audio**: Convert to MP3
|
|
84
|
+
|
|
85
|
+
## 🔑 API Keys Configuration
|
|
86
|
+
|
|
87
|
+
### First Run
|
|
88
|
+
|
|
89
|
+
The first time you run it, you'll be asked if you want to configure your API keys:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
⚠️ No API key found.
|
|
93
|
+
? Do you want to configure your API keys now? (Y/n)
|
|
94
|
+
|
|
95
|
+
🔑 Configure your API keys (optional - press Enter to skip)
|
|
96
|
+
|
|
97
|
+
? Groq API Key (recommended - faster): sk-proj-...
|
|
98
|
+
? OpenAI API Key: sk-...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Where Keys Are Saved
|
|
102
|
+
|
|
103
|
+
- **Linux/Mac**: `~/.config/ffmpeg-simple-converter/config.json`
|
|
104
|
+
- **Windows**: `%APPDATA%/ffmpeg-simple-converter/config.json`
|
|
105
|
+
|
|
106
|
+
### Getting API Keys
|
|
107
|
+
|
|
108
|
+
#### Groq (Recommended - Faster and Cheaper)
|
|
109
|
+
1. Visit: https://console.groq.com
|
|
110
|
+
2. Create a free account
|
|
111
|
+
3. Generate an API key in "API Keys"
|
|
112
|
+
|
|
113
|
+
#### OpenAI
|
|
114
|
+
1. Visit: https://platform.openai.com
|
|
115
|
+
2. Create an account
|
|
116
|
+
3. Add credits
|
|
117
|
+
4. Generate an API key in "API Keys"
|
|
118
|
+
|
|
119
|
+
### Transcription Priority
|
|
120
|
+
|
|
121
|
+
The system automatically tries in the following order:
|
|
122
|
+
1. **Groq** (if configured) - faster and cheaper
|
|
123
|
+
2. **OpenAI** (fallback) - if Groq fails or is not configured
|
|
124
|
+
|
|
125
|
+
## 📊 Usage Example
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
$ npm start
|
|
129
|
+
|
|
130
|
+
🎬 FFmpeg Simple Converter - Multi-Step Workflow
|
|
131
|
+
|
|
132
|
+
✓ FFmpeg is installed (version: 6.0)
|
|
133
|
+
|
|
134
|
+
📁 Found 3 media file(s)
|
|
135
|
+
|
|
136
|
+
? Select file:
|
|
137
|
+
🎬 lecture_video.mp4
|
|
138
|
+
❯ 🎵 podcast.mp3
|
|
139
|
+
🎬 presentation.mkv
|
|
140
|
+
|
|
141
|
+
? Select what you want to do:
|
|
142
|
+
❯ 🎬 Convert video + Extract audio + Transcribe
|
|
143
|
+
🎬 Extract audio from video + Transcribe
|
|
144
|
+
🎵 Convert audio + Transcribe
|
|
145
|
+
🎙️ Only transcribe audio
|
|
146
|
+
|
|
147
|
+
🚀 Starting workflow: Convert video + Extract audio + Transcribe
|
|
148
|
+
📁 Input file: lecture_video.mp4
|
|
149
|
+
|
|
150
|
+
[1/3] Convert video...
|
|
151
|
+
🎬 Converting video to optimized format...
|
|
152
|
+
✓ Video converted: lecture_video_converted.mp4
|
|
153
|
+
|
|
154
|
+
[2/3] Extract audio...
|
|
155
|
+
🎵 Extracting audio from video...
|
|
156
|
+
✓ Audio extracted: lecture_video_converted_audio.mp3
|
|
157
|
+
|
|
158
|
+
[3/3] Transcribe audio...
|
|
159
|
+
🎙️ Transcribing: lecture_video_converted_audio.mp3
|
|
160
|
+
📡 Trying Groq Whisper (fast)...
|
|
161
|
+
✓ Transcription completed with Groq
|
|
162
|
+
✓ Transcription saved: lecture_video_converted_audio.txt
|
|
163
|
+
|
|
164
|
+
📊 Workflow Progress:
|
|
165
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
166
|
+
✓ 1. Convert video (12.3s)
|
|
167
|
+
✓ 2. Extract audio (3.1s)
|
|
168
|
+
✓ 3. Transcribe audio (8.7s)
|
|
169
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
170
|
+
|
|
171
|
+
📦 Generated files:
|
|
172
|
+
• Video: lecture_video_converted.mp4
|
|
173
|
+
• Audio: lecture_video_converted_audio.mp3
|
|
174
|
+
• Transcription: lecture_video_converted_audio.txt
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 🗂️ Project Structure
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
src/
|
|
181
|
+
├── config/ # Configuration and API keys management
|
|
182
|
+
├── transcript/ # Transcription modules (Groq and OpenAI)
|
|
183
|
+
├── types/ # TypeScript definitions
|
|
184
|
+
├── utils/ # Utilities (ffmpeg, files, etc)
|
|
185
|
+
├── workflow/ # Workflow management system
|
|
186
|
+
└── index.ts # Main CLI
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 🔧 Available Scripts
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npm start # Run the interactive CLI
|
|
193
|
+
npm run build # Compile TypeScript to JavaScript
|
|
194
|
+
npm run dev # Development mode with watch
|
|
195
|
+
npm run convert # Run the old converter (convert.js)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 📦 Supported Formats
|
|
199
|
+
|
|
200
|
+
### Audio
|
|
201
|
+
`.ogg`, `.wav`, `.mp3`, `.m4a`, `.aac`, `.flac`
|
|
202
|
+
|
|
203
|
+
### Video
|
|
204
|
+
`.mp4`, `.mov`, `.mkv`, `.webm`, `.avi`
|
|
205
|
+
|
|
206
|
+
## 🛠️ State Management
|
|
207
|
+
|
|
208
|
+
Each workflow saves its state to `.workflow-state.json` in the output directory:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"steps": [
|
|
213
|
+
{
|
|
214
|
+
"id": "step-0",
|
|
215
|
+
"name": "Convert video",
|
|
216
|
+
"status": "completed",
|
|
217
|
+
"startTime": 1675436400000,
|
|
218
|
+
"endTime": 1675436412300
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"intermediateFiles": {
|
|
222
|
+
"convertedVideo": "video_converted.mp4",
|
|
223
|
+
"extractedAudio": "video_audio.mp3",
|
|
224
|
+
"transcriptionText": "video_audio.txt"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 🤝 Contributing
|
|
230
|
+
|
|
231
|
+
Contributions are welcome! Feel free to open issues and pull requests.
|
|
232
|
+
|
|
233
|
+
## 📄 License
|
|
234
|
+
|
|
235
|
+
MIT
|
|
236
|
+
|
|
237
|
+
## 🙏 Acknowledgments
|
|
238
|
+
|
|
239
|
+
- FFmpeg for the amazing tool
|
|
240
|
+
- OpenAI and Groq for transcription services
|
package/cli.mjs
ADDED
package/convert.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
|
|
8
|
+
const AUDIO_EXTS = new Set(['.ogg', '.wav', '.mp3', '.m4a', '.aac', '.flac']);
|
|
9
|
+
const VIDEO_EXTS = new Set(['.mp4', '.mov', '.mkv', '.webm', '.avi']);
|
|
10
|
+
|
|
11
|
+
function isFile(p) {
|
|
12
|
+
try {
|
|
13
|
+
return fs.statSync(p).isFile();
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function detectKindByExt(ext) {
|
|
20
|
+
const lower = ext.toLowerCase();
|
|
21
|
+
if (AUDIO_EXTS.has(lower)) return 'audio';
|
|
22
|
+
if (VIDEO_EXTS.has(lower)) return 'video';
|
|
23
|
+
return 'unknown';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function listCandidateFiles(dir) {
|
|
27
|
+
return fs
|
|
28
|
+
.readdirSync(dir)
|
|
29
|
+
.map((name) => ({ name, fullPath: path.join(dir, name) }))
|
|
30
|
+
.filter((f) => isFile(f.fullPath))
|
|
31
|
+
.filter((f) => {
|
|
32
|
+
const ext = path.extname(f.name).toLowerCase();
|
|
33
|
+
return AUDIO_EXTS.has(ext) || VIDEO_EXTS.has(ext);
|
|
34
|
+
})
|
|
35
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function uniqueOutputPath(dir, baseName, extWithDot) {
|
|
39
|
+
let candidate = path.join(dir, `${baseName}${extWithDot}`);
|
|
40
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
41
|
+
|
|
42
|
+
for (let i = 1; i < 10_000; i++) {
|
|
43
|
+
candidate = path.join(dir, `${baseName}_${i}${extWithDot}`);
|
|
44
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error('Nao foi possivel gerar um nome de saida unico.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildPresets() {
|
|
51
|
+
/**
|
|
52
|
+
* Preset shape:
|
|
53
|
+
* - id: string
|
|
54
|
+
* - label: string
|
|
55
|
+
* - kinds: Array<'audio'|'video'>
|
|
56
|
+
* - build({ inputPath, outputPath }): { cmd: 'ffmpeg', args: string[] }
|
|
57
|
+
*/
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
id: 'audio_mp3_q2',
|
|
61
|
+
label: 'Audio → MP3 (boa qualidade, q:a 2)',
|
|
62
|
+
kinds: ['audio'],
|
|
63
|
+
outputExt: '.mp3',
|
|
64
|
+
build: ({ inputPath, outputPath }) => ({
|
|
65
|
+
cmd: 'ffmpeg',
|
|
66
|
+
args: ['-hide_banner', '-n', '-i', inputPath, '-c:a', 'libmp3lame', '-q:a', '2', outputPath]
|
|
67
|
+
})
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'audio_mp3_small',
|
|
71
|
+
label: 'Audio → MP3 (menor, q:a 5)',
|
|
72
|
+
kinds: ['audio'],
|
|
73
|
+
outputExt: '.mp3',
|
|
74
|
+
build: ({ inputPath, outputPath }) => ({
|
|
75
|
+
cmd: 'ffmpeg',
|
|
76
|
+
args: ['-hide_banner', '-n', '-i', inputPath, '-c:a', 'libmp3lame', '-q:a', '5', outputPath]
|
|
77
|
+
})
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'video_1080p_medium',
|
|
81
|
+
label: 'Video → MP4 1080p (razoavel/menor: preset medium, CRF 28)',
|
|
82
|
+
kinds: ['video'],
|
|
83
|
+
outputExt: '.mp4',
|
|
84
|
+
build: ({ inputPath, outputPath }) => ({
|
|
85
|
+
cmd: 'ffmpeg',
|
|
86
|
+
args: [
|
|
87
|
+
'-hide_banner',
|
|
88
|
+
'-n',
|
|
89
|
+
'-i',
|
|
90
|
+
inputPath,
|
|
91
|
+
'-vf',
|
|
92
|
+
"scale='min(1080,iw)':-2",
|
|
93
|
+
'-c:v',
|
|
94
|
+
'libx264',
|
|
95
|
+
'-profile:v',
|
|
96
|
+
'baseline',
|
|
97
|
+
'-level',
|
|
98
|
+
'3.1',
|
|
99
|
+
'-preset',
|
|
100
|
+
'medium',
|
|
101
|
+
'-crf',
|
|
102
|
+
'28',
|
|
103
|
+
'-r',
|
|
104
|
+
'30',
|
|
105
|
+
'-c:a',
|
|
106
|
+
'aac',
|
|
107
|
+
'-b:a',
|
|
108
|
+
'128k',
|
|
109
|
+
'-movflags',
|
|
110
|
+
'+faststart',
|
|
111
|
+
outputPath
|
|
112
|
+
]
|
|
113
|
+
})
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'video_1080p_better',
|
|
117
|
+
label: 'Video → MP4 1080p (melhor/maior: preset slow, CRF 23)',
|
|
118
|
+
kinds: ['video'],
|
|
119
|
+
outputExt: '.mp4',
|
|
120
|
+
build: ({ inputPath, outputPath }) => ({
|
|
121
|
+
cmd: 'ffmpeg',
|
|
122
|
+
args: [
|
|
123
|
+
'-hide_banner',
|
|
124
|
+
'-n',
|
|
125
|
+
'-i',
|
|
126
|
+
inputPath,
|
|
127
|
+
'-vf',
|
|
128
|
+
"scale='min(1080,iw)':-2",
|
|
129
|
+
'-c:v',
|
|
130
|
+
'libx264',
|
|
131
|
+
'-profile:v',
|
|
132
|
+
'baseline',
|
|
133
|
+
'-level',
|
|
134
|
+
'3.1',
|
|
135
|
+
'-preset',
|
|
136
|
+
'slow',
|
|
137
|
+
'-crf',
|
|
138
|
+
'23',
|
|
139
|
+
'-r',
|
|
140
|
+
'30',
|
|
141
|
+
'-c:a',
|
|
142
|
+
'aac',
|
|
143
|
+
'-b:a',
|
|
144
|
+
'128k',
|
|
145
|
+
'-movflags',
|
|
146
|
+
'+faststart',
|
|
147
|
+
outputPath
|
|
148
|
+
]
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function spawnAndStream(cmd, args) {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const child = spawn(cmd, args, { stdio: 'inherit' });
|
|
157
|
+
child.on('error', reject);
|
|
158
|
+
child.on('close', (code) => {
|
|
159
|
+
if (code === 0) resolve();
|
|
160
|
+
else reject(new Error(`${cmd} saiu com codigo ${code}`));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function main() {
|
|
166
|
+
const cwd = process.cwd();
|
|
167
|
+
const files = listCandidateFiles(cwd);
|
|
168
|
+
|
|
169
|
+
if (files.length === 0) {
|
|
170
|
+
console.log('Nenhum arquivo de audio/video suportado encontrado na pasta atual.');
|
|
171
|
+
console.log('Extensoes de audio:', Array.from(AUDIO_EXTS).join(', '));
|
|
172
|
+
console.log('Extensoes de video:', Array.from(VIDEO_EXTS).join(', '));
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const { selectedFiles } = await inquirer.prompt([
|
|
177
|
+
{
|
|
178
|
+
type: 'checkbox',
|
|
179
|
+
name: 'selectedFiles',
|
|
180
|
+
message: 'Selecione os arquivos para converter:',
|
|
181
|
+
pageSize: 15,
|
|
182
|
+
choices: files.map((f) => ({
|
|
183
|
+
name: f.name,
|
|
184
|
+
value: f.fullPath
|
|
185
|
+
})),
|
|
186
|
+
validate: (arr) => (arr && arr.length > 0 ? true : 'Selecione pelo menos 1 arquivo.')
|
|
187
|
+
}
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
const presets = buildPresets();
|
|
191
|
+
|
|
192
|
+
const kindByInput = new Map();
|
|
193
|
+
const selectedKinds = new Set();
|
|
194
|
+
for (const inputPath of selectedFiles) {
|
|
195
|
+
const kind = detectKindByExt(path.extname(inputPath));
|
|
196
|
+
kindByInput.set(inputPath, kind);
|
|
197
|
+
selectedKinds.add(kind);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (selectedKinds.has('unknown')) {
|
|
201
|
+
console.log('Selecao contem arquivos com extensao nao suportada.');
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Escolhe 1 preset por tipo detectado (audio/video)
|
|
206
|
+
const presetByKind = new Map();
|
|
207
|
+
for (const kind of selectedKinds) {
|
|
208
|
+
const applicable = presets.filter((p) => p.kinds.includes(kind));
|
|
209
|
+
if (applicable.length === 0) {
|
|
210
|
+
console.log(`Nao ha presets disponiveis para tipo: ${kind}`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const { presetId } = await inquirer.prompt([
|
|
215
|
+
{
|
|
216
|
+
type: 'list',
|
|
217
|
+
name: 'presetId',
|
|
218
|
+
message: `Selecione o preset para ${kind}:`,
|
|
219
|
+
choices: applicable.map((p) => ({ name: p.label, value: p.id }))
|
|
220
|
+
}
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const preset = presets.find((p) => p.id === presetId);
|
|
224
|
+
if (!preset) throw new Error('Preset selecionado nao encontrado.');
|
|
225
|
+
presetByKind.set(kind, preset);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const inputPath of selectedFiles) {
|
|
229
|
+
const parsed = path.parse(inputPath);
|
|
230
|
+
const inputExt = parsed.ext.toLowerCase();
|
|
231
|
+
const baseName = parsed.name;
|
|
232
|
+
|
|
233
|
+
const kind = kindByInput.get(inputPath);
|
|
234
|
+
const preset = presetByKind.get(kind);
|
|
235
|
+
if (!preset) throw new Error(`Preset nao encontrado para tipo: ${kind}`);
|
|
236
|
+
|
|
237
|
+
// Evita sobrescrever quando a extensao final for igual
|
|
238
|
+
const outputBase = inputExt === preset.outputExt ? `${baseName}_converted` : baseName;
|
|
239
|
+
const outputPath = uniqueOutputPath(parsed.dir, outputBase, preset.outputExt);
|
|
240
|
+
|
|
241
|
+
const { cmd, args } = preset.build({ inputPath, outputPath });
|
|
242
|
+
|
|
243
|
+
console.log('\n----------------------------------------');
|
|
244
|
+
console.log('Entrada:', path.basename(inputPath));
|
|
245
|
+
console.log('Saida :', path.basename(outputPath));
|
|
246
|
+
console.log('Comando:', [cmd, ...args].join(' '));
|
|
247
|
+
|
|
248
|
+
await spawnAndStream(cmd, args);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log('\nConcluido.');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
main().catch((err) => {
|
|
255
|
+
console.error('\nErro:', err && err.message ? err.message : err);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mediacript",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to convert media files to performat formats and extract text using Groq and OpenAI's Whisper API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mediacript": "./cli.mjs",
|
|
8
|
+
"mediacript-convert": "./convert.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"convert": "node convert.js",
|
|
12
|
+
"start": "node --loader ts-node/esm src/index.ts",
|
|
13
|
+
"build": "tsc && chmod +x dist/index.js && chmod +x cli.mjs",
|
|
14
|
+
"dev": "node --loader ts-node/esm --watch src/index.ts",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=16"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"axios": "^1.13.4",
|
|
22
|
+
"dotenv": "^17.2.3",
|
|
23
|
+
"form-data": "^4.0.5",
|
|
24
|
+
"inquirer": "^8.2.6",
|
|
25
|
+
"openai": "^4.104.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/inquirer": "^8.2.12",
|
|
29
|
+
"@types/node": "^25.0.10",
|
|
30
|
+
"ts-node": "^10.9.2",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
33
|
+
}
|