Compare commits

..

2 Commits

8 changed files with 499 additions and 112 deletions

View File

@ -1,37 +1,76 @@
# VideoTools Makefile # root/Makefile
# Cross-platform installer for Linux and Windows (Git Bash/WSL) # Cross-platform installer for VideoTools (Linux / Windows via Git Bash or WSL)
PREFIX ?= /usr/local PREFIX ?= /usr/local
INSTALL_DIR = $(PREFIX)/bin INSTALL_DIR = $(PREFIX)/bin
DOC_DIR = $(PREFIX)/share/doc/videotools DOC_DIR = $(PREFIX)/share/doc/videotools
SCRIPT = video-tools.sh SCRIPT = video-tools.sh
DOCS = docs DOCS = docs
VERSION_FILE = VERSION
install: # ------------------------
# Installation
# ------------------------
install: verify
@echo "Installing VideoTools..." @echo "Installing VideoTools..."
mkdir -p "$(INSTALL_DIR)" @mkdir -p "$(INSTALL_DIR)"
cp "$(SCRIPT)" "$(INSTALL_DIR)/video-tools" @install -m 755 "$(SCRIPT)" "$(INSTALL_DIR)/video-tools"
chmod +x "$(INSTALL_DIR)/video-tools" @echo "✔ Installed script to $(INSTALL_DIR)/video-tools"
@echo "Copied script to $(INSTALL_DIR)/video-tools"
mkdir -p "$(DOC_DIR)" @mkdir -p "$(DOC_DIR)"
cp -r "$(DOCS)"/* "$(DOC_DIR)/" @cp -r "$(DOCS)"/* "$(DOC_DIR)/"
@echo "Documentation installed to $(DOC_DIR)" @echo "✔ Installed documentation to $(DOC_DIR)"
@if [ -f "$(VERSION_FILE)" ]; then \
VERSION=$$(cat $(VERSION_FILE)); \
echo "✔ VideoTools v$$VERSION installed successfully."; \
else \
echo "⚠ Version file not found. Install completed without version info."; \
fi
@echo ""
# ------------------------
# Uninstall
# ------------------------
uninstall: uninstall:
@echo "Removing VideoTools..." @echo "Removing VideoTools..."
rm -f "$(INSTALL_DIR)/video-tools" @rm -f "$(INSTALL_DIR)/video-tools"
rm -rf "$(DOC_DIR)" @rm -rf "$(DOC_DIR)"
@echo "VideoTools uninstalled." @echo "✔ VideoTools uninstalled."
# ------------------------
# Verification
# ------------------------
verify:
@echo "Checking environment..."
@if ! command -v ffmpeg >/dev/null 2>&1; then \
echo "✖ FFmpeg not found. Please install FFmpeg before continuing."; \
exit 1; \
else \
echo "✔ FFmpeg found."; \
fi
@echo "✔ Environment OK."
@echo ""
# ------------------------
# Documentation
# ------------------------
docs: docs:
@echo "Opening documentation..." @echo "Available documentation files:"
@ls -1 $(DOCS) @ls -1 "$(DOCS)"
# ------------------------
# Help
# ------------------------
help: help:
@echo "Available targets:" @echo "Available targets:"
@echo " make install Install the toolkit system-wide" @echo " make install Install the toolkit system-wide"
@echo " make uninstall Remove the toolkit" @echo " make uninstall Remove the toolkit"
@echo " make docs List documentation files" @echo " make verify Check if FFmpeg and environment are ready"
@echo " make help Show this message" @echo " make docs List documentation files"
@echo " make help Show this message"

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.1.1

62
docs/CHANGELOG.md Normal file
View File

@ -0,0 +1,62 @@
# docs/CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This project adheres to [Semantic Versioning](https://semver.org/).
---
## [0.1.0] - 2025-11-04
### Added
- Initial release of **Video Tools CLI**
- `convert-single` command:
- Converts a single input file (AVI, MPG, MKV, MOV, etc.) into MP4
- Uses FFmpeg with modern compression (`libx264`, `AAC`, CRF 18)
- Automatically corrects timestamps (`-fflags +genpts`)
- Outputs to `~/Videos`
- `convert-multiple` command:
- Merges multiple video inputs into one MP4
- Re-encodes using same compression settings
- Creates and cleans temporary file lists automatically
- Config system (`config/config.json`) with optional `jq` parsing
- Version tracking via `VERSION` file
- Color-coded CLI feedback for INFO, WARN, ERROR, OK
- Graceful error handling with safe exits and cleanup
- Compatible with:
- Linux (bash/zsh/fish shells)
- Windows (via Git Bash or WSL)
### Documentation
- `docs/README.md` — general usage and installation guide
- `docs/CLI_Functions.md` — detailed command reference
- `docs/Conversion_Settings.md` — technical breakdown of FFmpeg defaults
- `docs/Upscale.md` — planned module outline for future upscaling support
### Build System
- `Makefile` with:
- `install` / `uninstall` / `verify` / `docs` / `help` targets
- Version-aware output and FFmpeg dependency check
- Installs both binary and documentation to system paths
---
## Planned for [0.2.0]
### Added
- `upscale-video` command:
- FFmpeg `lanczos` scaling for 720p / 1080p / 4K
- Optional H.265 (HEVC) encoding for smaller files
- `convert-batch` for folder-wide conversions
- JSON-driven override for per-run configuration (custom CRF, bitrate, etc.)
- Optional parallel encoding support
- Automatic codec detection before re-encoding
### Improvements
- More detailed progress bar during FFmpeg execution
- Unified cross-platform installer for Windows (PowerShell script)
- Expanded error handling and logging to `logs/` directory
---
End of File

View File

@ -1,4 +1,5 @@
# CLI Function Reference # docs/CLI_Functions.md
# CLI Function Reference (v0.1.0)
This document describes the available commands in `video-tools.sh`. This document describes the available commands in `video-tools.sh`.
Each command can be run from any terminal once the tool is installed. Each command can be run from any terminal once the tool is installed.
@ -12,7 +13,7 @@ Each command can be run from any terminal once the tool is installed.
| `convert-single` | `video-tools convert-single <input> <output.mp4>` | Converts a single video to MP4. | | `convert-single` | `video-tools convert-single <input> <output.mp4>` | Converts a single video to MP4. |
| `convert-multiple` | `video-tools convert-multiple <input1> <input2> ... <output.mp4>` | Combines multiple video files into one MP4. | | `convert-multiple` | `video-tools convert-multiple <input1> <input2> ... <output.mp4>` | Combines multiple video files into one MP4. |
All outputs are saved in your default `~/Videos` folder. All outputs are saved in your default `~/Videos` folder, unless overridden in `config/config.json`.
--- ---
@ -21,7 +22,7 @@ All outputs are saved in your default `~/Videos` folder.
### 1. convert-single ### 1. convert-single
**Purpose** **Purpose**
Convert a single input video file into an MP4 with modern compression and audio standards. Convert a single input video file into an MP4 using modern compression and audio standards.
**Usage** **Usage**
```bash ```bash
@ -41,15 +42,16 @@ video-tools convert-single \
``` ```
**Behavior** **Behavior**
- Converts older formats (AVI, MPG, MOV, MKV, etc.) into MP4. - Converts older formats (AVI, MPG, MOV, MKV, etc.) into MP4.
- Ensures output uses the H.264 codec (libx264) and AAC audio. - Uses H.264 (`libx264`) for video and AAC for audio.
- Rebuilds timestamps to avoid sync issues. - Rebuilds timestamps with `-fflags +genpts` to prevent sync issues.
- Adds `+faststart` flag for quicker playback when streamed or loaded in players. - Adds `-movflags +faststart` to improve playback and streaming performance.
- Displays detailed FFmpeg progress and conversion status.
**When to use** **When to Use**
- When you want to upgrade old videos to a more efficient format. - To modernize older video files.
- When a single video wont play on mobile or modern devices. - When a video fails to play properly on mobile or modern devices.
- To reduce file size without losing visible quality. - To reduce file size while retaining visual quality.
--- ---
@ -78,14 +80,15 @@ video-tools convert-multiple \
``` ```
**Behavior** **Behavior**
- Reads all listed files in order and merges them seamlessly. - Merges all listed videos sequentially.
- Each input is re-encoded using the same H.264/AAC settings. - Re-encodes each input using the same H.264/AAC settings.
- Temporary file list is automatically created and deleted. - Creates and deletes a temporary file list automatically.
- Logs detailed FFmpeg progress to the terminal. - Displays each added file and overall progress in the terminal.
- Ensures the output file is playable immediately after completion.
**When to use** **When to Use**
- When combining multi-part video discs, episodes, or scene splits. - To combine multi-part video discs, episodes, or segmented clips.
- When creating a single playable MP4 from segmented source material. - When creating a single playback file from multiple source files.
--- ---
@ -131,8 +134,8 @@ video-tools convert-multiple \
- Re-encoding ensures compatibility and stable playback. - Re-encoding ensures compatibility and stable playback.
### File Safety ### File Safety
- Original files are untouched. - Original files are untouched.
- All intermediate list files are removed automatically. - All intermediate list files are removed automatically.
- FFmpeg handles SIGINT (Ctrl+C) gracefully—partially written files are still playable. - FFmpeg handles SIGINT (Ctrl+C) gracefully—partially written files are still playable.
--- ---

View File

@ -1,22 +1,24 @@
# Conversion Settings # docs/Conversion_Settings.md
# Conversion Settings (v0.1.0)
This file documents the default FFmpeg parameters used in `video-tools.sh` and explains why they are chosen. This file documents the default FFmpeg parameters used in `video-tools.sh` and explains why they are chosen.
--- ---
## Output Format ## Output Format
All conversions produce: All conversions produce:
``` ```
Format: MP4 Format: MP4
Container: MPEG-4 Part 14 Container: MPEG-4 Part 14
Video Codec: H.264 (libx264) Video Codec: H.264 (libx264)
Audio Codec: AAC Audio Codec: AAC
``` ```
MP4 was chosen because: **Why MP4?**
- Its universally supported across modern devices. - Universally supported across modern devices.
- It balances size and compatibility well. - Balances quality, compression, and compatibility.
- H.264 encoding provides excellent quality at smaller bitrates. - H.264 encoding provides excellent visual quality at modest bitrates.
--- ---
@ -25,8 +27,8 @@ MP4 was chosen because:
| Parameter | Value | Purpose | | Parameter | Value | Purpose |
|------------|--------|----------| |------------|--------|----------|
| `-c:v libx264` | H.264 video encoding | Modern, efficient codec with wide hardware support | | `-c:v libx264` | H.264 video encoding | Modern, efficient codec with wide hardware support |
| `-crf 18` | Constant Rate Factor | Maintains near-lossless visual quality; lower = better | | `-crf 18` | Constant Rate Factor | Maintains near-lossless visual quality (lower = better) |
| `-preset slow` | Encoding preset | Improves compression efficiency; trades some speed | | `-preset slow` | Encoding preset | Improves compression efficiency at moderate CPU cost |
| `-c:a aac` | Audio codec | Standard high-quality stereo audio | | `-c:a aac` | Audio codec | Standard high-quality stereo audio |
| `-b:a 192k` | Audio bitrate | Balances fidelity and file size | | `-b:a 192k` | Audio bitrate | Balances fidelity and file size |
| `-movflags +faststart` | MP4 optimization | Enables faster playback start in players or web streams | | `-movflags +faststart` | MP4 optimization | Enables faster playback start in players or web streams |
@ -36,52 +38,52 @@ MP4 was chosen because:
## Quality vs. File Size ## Quality vs. File Size
The CRF scale (used by FFmpeg) defines quality and compression balance: The **CRF scale** (used by FFmpeg) defines the balance between quality and compression:
| CRF | Description | Typical Use | | CRF | Description | Typical Use |
|------|-------------|--------------| |------|-------------|--------------|
| 1416 | Near lossless | Archival or professional mastering | | 1416 | Near lossless | Archival or professional mastering |
| 1820 | High quality | Everyday use, visually lossless | | 1820 | High quality | Everyday use, visually lossless |
| 2124 | Medium quality | Small file sizes, light compression | | 2124 | Medium quality | Smaller file sizes with light compression |
| 25+ | Low quality | Fast compression, heavy size reduction | | 25+ | Low quality | Fast compression, noticeable loss |
The default of **CRF 18** keeps visual clarity virtually identical to the original while cutting most AVI or MPG files to **4060% smaller** sizes. **Default CRF: 18**
Provides visually lossless results while reducing most AVI/MPG files to **4060% smaller sizes**.
--- ---
## Why Preset "slow" ## Why Use Preset “slow”
The preset defines encoding effort vs. compression efficiency. The preset defines the trade-off between encoding speed and compression efficiency.
Options range from `ultrafast``veryslow`. Range: `ultrafast``veryslow`.
- **slow** provides strong compression without extreme CPU time. - `slow` offers excellent compression without excessive CPU time.
- Encoding speed is roughly 23× real-time on a midrange CPU. - Typical speed: ~23× real-time on a midrange CPU.
- File sizes are typically 1020% smaller than `medium` preset at the same quality. - Output files are usually **1020% smaller** than with `medium` preset at the same quality.
--- ---
## Audio Strategy ## Audio Strategy
AAC at 192k is chosen for: **AAC at 192k** is chosen for:
- Wide playback compatibility (phones, TVs, players). - Broad device compatibility (phones, TVs, web players).
- Transparent stereo sound for most sources. - Transparent stereo sound for most content.
- Reasonable storage size (~2MB/minute of stereo audio). - Efficient storage (~2 MB/min of stereo audio).
If future needs arise, the tool can later support: Future options may include:
- `-c:a copy` for untouched audio streams. - `-c:a copy` for untouched audio streams.
- `-b:a 320k` for high-fidelity preservation. - `-b:a 320k` for high-fidelity preservation.
--- ---
## Color Space & Scaling ## Color Space & Scaling
No scaling is applied by default. No scaling is applied by default — the video retains its original resolution and color profile.
The video retains original resolution and color profile.
Upscaling and filtering will be handled in a separate command (`upscale-video`) in future versions, with support for: Planned future command: `upscale-video`, supporting:
- FFmpegs `scale` and `zscale` filters. - FFmpegs `scale` and `zscale` filters.
- `lanczos` resampling (for sharp, clean upscale). - `lanczos` resampling for sharp, clean upscales.
- Optional integration with ML-based models (Real-ESRGAN, waifu2x). - Optional ML upscaling (Real-ESRGAN, waifu2x).
--- ---

View File

@ -1,4 +1,5 @@
# Video Tools CLI # docs/README.md
# Video Tools CLI (v0.1.0)
A simple command-line utility for video conversion and merging using FFmpeg. A simple command-line utility for video conversion and merging using FFmpeg.
Designed for personal use and sharing with friends. Designed for personal use and sharing with friends.
@ -38,7 +39,7 @@ sudo apt install ffmpeg # For Debian or Ubuntu
1. Clone or copy the repository: 1. Clone or copy the repository:
```bash ```bash
git clone https://github.com/YourName/VideoTools.git git clone https://git.leaktechnologies.dev/Leak_Technologies/VideoTools.git
cd VideoTools cd VideoTools
``` ```
@ -67,10 +68,11 @@ video-tools convert-single "input.avi" "output.mp4"
| Video codec | `libx264` | High-quality H.264 encoding | | Video codec | `libx264` | High-quality H.264 encoding |
| Audio codec | `aac` | High-quality AAC stereo audio | | Audio codec | `aac` | High-quality AAC stereo audio |
| Quality (CRF) | 18 | Visually lossless quality | | Quality (CRF) | 18 | Visually lossless quality |
| Preset | slow | Balances speed and compression | | Preset | `slow` | Balances speed and compression |
| Audio bitrate | 192k | High-quality stereo output | | Audio bitrate | 192k | High-quality stereo output |
These defaults prioritize quality while still reducing file sizes compared to older formats such as AVI or MPG. These defaults prioritize quality while still reducing file sizes compared to older formats such as AVI or MPG.
For more details, see [`docs/Conversion_Settings.md`](./Conversion_Settings.md).
--- ---
@ -84,7 +86,7 @@ video-tools convert-single \
"Example Movie.mp4" "Example Movie.mp4"
``` ```
Output: **Output**
``` ```
/home/stu/Videos/Example Movie.mp4 /home/stu/Videos/Example Movie.mp4
``` ```
@ -101,7 +103,7 @@ video-tools convert-multiple \
"Example Movie Combined.mp4" "Example Movie Combined.mp4"
``` ```
Output: **Output**
``` ```
/home/stu/Videos/Example Movie Combined.mp4 /home/stu/Videos/Example Movie Combined.mp4
``` ```
@ -123,15 +125,29 @@ Output:
| Feature | Status | | Feature | Status |
|----------|--------| |----------|--------|
| convert-single | ✅ Done | | `convert-single` | ✅ Done |
| convert-multiple | ✅ Done | | `convert-multiple` | ✅ Done |
| upscale-video | 🔜 Planned | | `upscale-video` | 🔜 Planned |
| batch conversion | 🔜 Planned | | `batch conversion` | 🔜 Planned |
| automatic format detection | 🔜 Planned | | `automatic format detection` | 🔜 Planned |
---
## Documentation Index
| File | Description |
|------|--------------|
| [`docs/CLI_Functions.md`](./CLI_Functions.md) | Command usage reference |
| [`docs/Conversion_Settings.md`](./Conversion_Settings.md) | Technical breakdown of FFmpeg defaults |
| [`docs/Upscale.md`](./Upscale.md) | Planned module for loss-minimized video upscaling |
--- ---
## License ## License
Free for personal use. Free for personal use.
You can modify or share this tool with anyone. You may modify or share this tool with anyone.
---
End of File

View File

@ -1,4 +1,5 @@
# Upscale Module (Planned) # docs/Upscale.md
# Upscale Module (Planned) — v0.1.0
The `upscale-video` command will provide loss-minimized video upscaling. The `upscale-video` command will provide loss-minimized video upscaling.
@ -11,7 +12,7 @@ This module will allow upscaling of existing videos to higher resolutions such a
- 1080p (Full HD) - 1080p (Full HD)
- 2160p (4K) - 2160p (4K)
It will use FFmpegs native scaling filters first and optionally integrate machine learning tools later. It will begin with FFmpegs native scaling filters and may later support machine learning-based methods.
--- ---
@ -33,10 +34,10 @@ Optional parameters:
## Default Behavior ## Default Behavior
If no resolution is provided, the tool will upscale intelligently to the nearest standard resolution above the input video. If no explicit resolution is provided, the tool will upscale intelligently to the nearest standard resolution above the source.
Example: **Examples:**
- Input: 960×540 → Output: 1280×720 - Input: 960×540 → Output: 1280×720
- Input: 1280×720 → Output: 1920×1080 - Input: 1280×720 → Output: 1920×1080
--- ---
@ -50,9 +51,10 @@ ffmpeg -i input.mp4 -vf scale=1920:1080:flags=lanczos \
-c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k output_1080p.mp4 -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k output_1080p.mp4
``` ```
- `scale=1920:1080:flags=lanczos` → high-quality resampling **Explanation:**
- `lanczos` offers sharp, low-artifact upscale - `scale=1920:1080:flags=lanczos` → performs high-quality resampling.
- Later versions may support `zscale` or ML-based filters - `lanczos` produces sharp, low-artifact results.
- Future versions may introduce `zscale` or machine-learning upscalers.
--- ---
@ -60,9 +62,9 @@ ffmpeg -i input.mp4 -vf scale=1920:1080:flags=lanczos \
| Feature | Status | | Feature | Status |
|----------|--------| |----------|--------|
| FFmpeg scaler (lanczos) | 🔜 Planned | | FFmpeg scaler (`lanczos`) | 🔜 Planned |
| ML-based upscaling (Real-ESRGAN / waifu2x) | 🚧 Research | | ML-based upscaling (Real-ESRGAN / waifu2x) | 🚧 Research |
| Auto resolution detection | 🔜 Planned | | Auto-resolution detection | 🔜 Planned |
| GPU acceleration support | 🔜 Planned | | GPU acceleration support | 🔜 Planned |
| Configurable presets | 🔜 Planned | | Configurable presets | 🔜 Planned |

View File

@ -1,17 +1,19 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# # video-tools.sh
# ----------------------------------------------------------
# Simple FFmpeg CLI Toolset # Simple FFmpeg CLI Toolset
# -------------------------
# Provides easy commands for single or multi-file video conversion. # Provides easy commands for single or multi-file video conversion.
# Works on Linux and Windows (Git Bash / WSL) as long as ffmpeg is installed. # Works on Linux and Windows (Git Bash / WSL) as long as ffmpeg is installed.
# # ----------------------------------------------------------
set -euo pipefail set -euo pipefail
# ========================================================== # ==========================================================
# Configuration system # Configuration system
# ========================================================== # ==========================================================
CONFIG_FILE="$(dirname "$0")/config/config.json" SCRIPT_DIR="$(dirname "$0")"
CONFIG_FILE="$SCRIPT_DIR/config/config.json"
LOG_DIR="$HOME/.local/share/video-tools/logs"
# ------------------------- # -------------------------
# Default configuration # Default configuration
@ -22,7 +24,7 @@ AUDIO_CODEC="aac"
CRF="18" # lower = higher quality CRF="18" # lower = higher quality
PRESET="slow" # slower = better compression PRESET="slow" # slower = better compression
AUDIO_BITRATE="192k" AUDIO_BITRATE="192k"
VERSION="0.1.0" VERSION="0.1.1"
# ========================================================== # ==========================================================
# Utility: Colour logging # Utility: Colour logging
@ -39,7 +41,7 @@ warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# ========================================================== # ==========================================================
# Check dependencies early # Dependency check
# ========================================================== # ==========================================================
if ! command -v ffmpeg >/dev/null 2>&1; then if ! command -v ffmpeg >/dev/null 2>&1; then
error "ffmpeg is not installed or not in PATH." error "ffmpeg is not installed or not in PATH."
@ -62,6 +64,33 @@ else
fi fi
mkdir -p "$OUTPUT_DIR" || { error "Failed to create output directory $OUTPUT_DIR"; exit 1; } mkdir -p "$OUTPUT_DIR" || { error "Failed to create output directory $OUTPUT_DIR"; exit 1; }
mkdir -p "$LOG_DIR" || { error "Failed to create log directory $LOG_DIR"; exit 1; }
# ==========================================================
# Conversion Profiles
# ==========================================================
apply_profile() {
local profile="$1"
case "$profile" in
hi-rate|--hi-rate)
info "Applying high bitrate profile..."
CRF="14"
PRESET="veryslow"
AUDIO_BITRATE="320k"
;;
portable|--portable)
info "Applying portable/mobile profile..."
CRF="24"
PRESET="faster"
AUDIO_BITRATE="128k"
;;
default|--default|"")
;;
*)
warn "Unknown profile: $profile — ignoring."
;;
esac
}
# ========================================================== # ==========================================================
# Header output # Header output
@ -78,6 +107,81 @@ print_header() {
echo "----------------------------------------------" echo "----------------------------------------------"
} }
# ==========================================================
# Path sanitization (handles spaces, quotes, etc.)
# ==========================================================
safe_path() {
local p="$1"
printf "%s" "$p" | sed "s/'/'\\\\''/g"
}
sanitize_name() {
local name="$1"
echo "$name" | sed 's/[^A-Za-z0-9._-]/_/g'
}
# ==========================================================
# Summary report parser
# ==========================================================
print_summary() {
local log_file="$1"
local input="$2"
local output="$3"
local start_time="$4"
local end_time="$5"
local elapsed=$((end_time - start_time))
local elapsed_min=$((elapsed / 60))
local elapsed_sec=$((elapsed % 60))
local duration bitrate speed fps frames mux_overhead vbitrate abitrate
duration=$(grep -Eo "time=[0-9:.]+" "$log_file" | tail -1 | cut -d= -f2)
bitrate=$(grep -Eo "bitrate=[0-9.]+kbits/s" "$log_file" | tail -1 | awk -F= '{print $2}')
speed=$(grep -Eo "speed=[0-9.]+x" "$log_file" | tail -1 | cut -d= -f2)
fps=$(grep -Eo "fps=[0-9.]+" "$log_file" | tail -1 | cut -d= -f2)
frames=$(grep -Eo "frame=[0-9]+" "$log_file" | tail -1 | awk -F= '{print $2}')
mux_overhead=$(grep -Eo "muxing overhead: [0-9.]+%" "$log_file" | tail -1 | awk '{print $3}')
vbitrate=$(grep -Eo "kb/s:[0-9.]+" "$log_file" | tail -1 | cut -d: -f2)
abitrate=$(echo "$AUDIO_BITRATE" | sed 's/k//')
echo ""
echo "----------------------------------------------"
echo "Conversion Summary"
echo "----------------------------------------------"
echo "Input File : $input"
echo "Output File : $output"
echo "Log File : $log_file"
echo "Duration : ${duration:-unknown}"
echo "Encoding Speed : ${speed:-unknown}"
echo "Elapsed Time : ${elapsed_min}m ${elapsed_sec}s"
echo "Status : ✅ Successful"
echo "----------------------------------------------"
echo "[Technical Stats]"
echo "Average Bitrate : ${bitrate:-N/A}"
echo "Video Bitrate : ${vbitrate:-N/A} kb/s"
echo "Audio Bitrate : ${abitrate:-N/A} kb/s"
echo "Frames Encoded : ${frames:-N/A}"
echo "FPS Achieved : ${fps:-N/A}"
echo "Mux Overhead : ${mux_overhead:-N/A}"
echo "CRF Setting : $CRF"
echo "----------------------------------------------"
echo ""
}
# ==========================================================
# Generate timestamped log file
# ==========================================================
generate_logfile() {
local output_name="$1"
local timestamp
timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
local base
base=$(basename "$output_name" .mp4)
local safe_base
safe_base=$(sanitize_name "$base")
echo "$LOG_DIR/${timestamp}_${safe_base}.log"
}
# ========================================================== # ==========================================================
# Convert one file # Convert one file
# ========================================================== # ==========================================================
@ -85,85 +189,243 @@ convert_single() {
local input="$1" local input="$1"
local output_name="$2" local output_name="$2"
local output_path="$OUTPUT_DIR/$output_name" local output_path="$OUTPUT_DIR/$output_name"
local safe_input
safe_input="$(safe_path "$input")"
local log_file
log_file=$(generate_logfile "$output_name")
local start_time end_time
print_header print_header
info "Converting single video..." info "Converting single video..."
info "Input : $input" info "Input : $input"
info "Output: $output_path" info "Output: $output_path"
info "Log : $log_file"
if [[ ! -f "$input" ]]; then if [[ ! -f "$input" ]]; then
error "Input file not found: $input" error "Input file not found: $input"
exit 1 exit 1
fi fi
start_time=$(date +%s)
ffmpeg -hide_banner -loglevel info -fflags +genpts \ ffmpeg -hide_banner -loglevel info -fflags +genpts \
-i "$input" \ -i "$input" \
-c:v "$VIDEO_CODEC" -crf "$CRF" -preset "$PRESET" \ -c:v "$VIDEO_CODEC" -crf "$CRF" -preset "$PRESET" \
-c:a "$AUDIO_CODEC" -b:a "$AUDIO_BITRATE" -movflags +faststart \ -c:a "$AUDIO_CODEC" -b:a "$AUDIO_BITRATE" -movflags +faststart \
"$output_path" || { error "Conversion failed."; exit 1; } "$output_path" 2>&1 | tee "$log_file"
end_time=$(date +%s)
ln -sf "$log_file" "$LOG_DIR/latest.log" 2>/dev/null || true
success "Conversion complete: $output_path" success "Conversion complete: $output_path"
print_summary "$log_file" "$safe_input" "$output_path" "$start_time" "$end_time"
} }
# ========================================================== # ==========================================================
# Combine multiple files # Combine multiple files
# ========================================================== # ==========================================================
convert_multiple() { convert_multiple() {
local args=("$@") # Rebuild args array safely to preserve quoted file paths
local args=()
while [[ $# -gt 0 ]]; do
args+=("$1")
shift
done
# Ensure at least two inputs + one output
if [[ ${#args[@]} -lt 3 ]]; then
error "Usage: $0 convert-multiple <input1> <input2> ... <output.mp4> [--hi-rate|--portable]"
exit 1
fi
# Extract and verify output filename
local output_name="${args[-1]}" local output_name="${args[-1]}"
unset 'args[-1]' unset 'args[-1]'
if [[ -z "${output_name:-}" ]]; then
warn "Output name missing — defaulting to combined_output.mp4"
output_name="combined_output.mp4"
fi
# Setup working paths
local list_file local list_file
list_file=$(mktemp) list_file=$(mktemp)
local output_path="$OUTPUT_DIR/$output_name" local output_path="$OUTPUT_DIR/$output_name"
local log_file
log_file=$(generate_logfile "$output_name")
local start_time end_time
print_header print_header
info "Combining multiple videos..." info "Combining multiple videos..."
info "Output: $output_path"
info "Log : $log_file"
# Verify each input and build concat list
for input in "${args[@]}"; do for input in "${args[@]}"; do
if [[ ! -f "$input" ]]; then if [[ ! -f "$input" ]]; then
error "Missing input file: $input" error "Missing input file: '$input'"
rm -f "$list_file" rm -f "$list_file"
exit 1 exit 1
fi fi
echo "file '$input'" >> "$list_file" printf "file '%s'\n" "$(safe_path "$input")" >> "$list_file"
info " + Added: $input" info " + Added: $input"
done done
echo "----------------------------------------------" echo "----------------------------------------------"
# Run FFmpeg and record timing
start_time=$(date +%s)
ffmpeg -hide_banner -loglevel info -f concat -safe 0 -i "$list_file" \ ffmpeg -hide_banner -loglevel info -f concat -safe 0 -i "$list_file" \
-c:v "$VIDEO_CODEC" -crf "$CRF" -preset "$PRESET" \ -c:v "$VIDEO_CODEC" -crf "$CRF" -preset "$PRESET" \
-c:a "$AUDIO_CODEC" -b:a "$AUDIO_BITRATE" -movflags +faststart \ -c:a "$AUDIO_CODEC" -b:a "$AUDIO_BITRATE" -movflags +faststart \
"$output_path" || { error "Merge failed."; rm -f "$list_file"; exit 1; } "$output_path" 2>&1 | tee "$log_file"
end_time=$(date +%s)
# Cleanup and summary
ln -sf "$log_file" "$LOG_DIR/latest.log" 2>/dev/null || true
rm -f "$list_file" rm -f "$list_file"
success "Combined video created: $output_path" success "Combined video created: $output_path"
print_summary "$log_file" "Multiple inputs" "$output_path" "$start_time" "$end_time"
} }
# ==========================================================
# Show concise or verbose video information (cross-platform)
# ==========================================================
show_videoinfo() {
if ! command -v ffprobe >/dev/null 2>&1; then
error "ffprobe is not installed or not in PATH."
exit 1
fi
if [[ $# -lt 1 ]]; then
error "Usage: $0 videoinfo <file1> [file2 ...] [--verbose]"
exit 1
fi
# Detect verbose flag
local verbose=false
for arg in "$@"; do
[[ "$arg" == "--verbose" ]] && verbose=true
done
for input in "$@"; do
[[ "$input" == "--verbose" ]] && continue
if [[ ! -f "$input" ]]; then
error "File not found: $input"
continue
fi
echo "----------------------------------------------"
echo "File: $input"
echo "----------------------------------------------"
if $verbose; then
ffprobe -v error \
-show_entries format=filename,format_name,duration,size,bit_rate \
-show_streams \
-of default=noprint_wrappers=1:nokey=0 \
"$input" | sed 's/^/ /'
else
# Collect metadata safely
local duration size format vcodec acodec width height fps profile pix_fmt dar sar vbitrate abitrate channels arate bitrate
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input")
size=$(ffprobe -v error -show_entries format=size -of default=noprint_wrappers=1:nokey=1 "$input")
format=$(ffprobe -v error -show_entries format=format_name -of default=noprint_wrappers=1:nokey=1 "$input")
bitrate=$(ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 "$input")
vcodec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 "$input")
profile=$(ffprobe -v error -select_streams v:0 -show_entries stream=profile -of default=noprint_wrappers=1:nokey=1 "$input")
width=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "$input")
height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$input")
fps=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 "$input" | awk -F'/' '{if ($2>0) printf "%.2f", $1/$2; else print $1}')
pix_fmt=$(ffprobe -v error -select_streams v:0 -show_entries stream=pix_fmt -of default=noprint_wrappers=1:nokey=1 "$input")
dar=$(ffprobe -v error -select_streams v:0 -show_entries stream=display_aspect_ratio -of default=noprint_wrappers=1:nokey=1 "$input")
sar=$(ffprobe -v error -select_streams v:0 -show_entries stream=sample_aspect_ratio -of default=noprint_wrappers=1:nokey=1 "$input")
vbitrate=$(ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 "$input")
acodec=$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 "$input")
abitrate=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 "$input")
channels=$(ffprobe -v error -select_streams a:0 -show_entries stream=channels -of default=noprint_wrappers=1:nokey=1 "$input")
arate=$(ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate -of default=noprint_wrappers=1:nokey=1 "$input")
# Compute duration minutes without bc
local mins secs
mins=$(( ${duration%.*} / 60 ))
secs=$(( ${duration%.*} % 60 ))
# Simple size + bitrate formatting (cross-platform safe)
local size_mb=$(( size / 1048576 ))
local bitrate_mbps=$(( bitrate / 1000000 ))
local vbitrate_mbps=$(( vbitrate / 1000000 ))
local abitrate_kbps=$(( abitrate / 1000 ))
echo " Duration: ${duration:-N/A} sec (${mins}m ${secs}s)"
echo " Size: ${size_mb} MB"
echo " Container: ${format:-N/A}"
echo " Video Codec: ${vcodec:-N/A} (${profile:-N/A})"
echo " Resolution: ${width:-N/A}x${height:-N/A} @ ${fps:-N/A} fps"
echo " Aspect Ratio: ${dar:-N/A} (SAR ${sar:-N/A})"
echo " Pixel Format: ${pix_fmt:-N/A}"
echo " Video Bitrate: ${vbitrate_mbps} Mbps"
echo " Audio Codec: ${acodec:-N/A}"
echo " Audio: ${channels:-N/A} ch @ ${arate:-N/A} Hz"
echo " Audio Bitrate: ${abitrate_kbps} kbps"
echo " Overall Rate: ${bitrate_mbps} Mbps"
fi
echo ""
done
}
# ========================================================== # ==========================================================
# Dispatcher # Dispatcher
# ========================================================== # ==========================================================
case "${1:-}" in case "${1:-}" in
convert-single) convert-single)
shift shift
if [[ $# -ne 2 ]]; then if [[ $# -lt 2 ]]; then
error "Usage: $0 convert-single <input> <output.mp4>" error "Usage: $0 convert-single <input> <output.mp4> [--hi-rate|--portable]"
exit 1 exit 1
fi fi
convert_single "$@" # Optional profile flag
if [[ $# -eq 3 ]]; then
apply_profile "${3:-}"
fi
convert_single "$1" "$2"
;; ;;
convert-multiple) convert-multiple)
shift shift
if [[ $# -lt 3 ]]; then if [[ $# -lt 3 ]]; then
error "Usage: $0 convert-multiple <input1> <input2> ... <output.mp4>" error "Usage: $0 convert-multiple <input1> <input2> ... <output.mp4> [--hi-rate|--portable]"
exit 1 exit 1
fi fi
# Handle optional profile flag
last_arg="${@: -1}"
case "$last_arg" in
--hi-rate|--portable)
apply_profile "$last_arg"
set -- "${@:1:$(($#-1))}"
;;
esac
convert_multiple "$@" convert_multiple "$@"
;; ;;
videoinfo|--videoinfo|--fileinfo)
shift
show_videoinfo "$@"
;;
*) *)
echo "Video Tools v$VERSION" echo "Video Tools v$VERSION"
echo "Usage:" echo "Usage:"
echo " $0 convert-single <input> <output.mp4>" echo " $0 convert-single <input> <output.mp4> [--hi-rate|--portable]"
echo " $0 convert-multiple <input1> <input2> ... <output.mp4>" echo " $0 convert-multiple <input1> <input2> ... <output.mp4> [--hi-rate|--portable]"
echo " $0 videoinfo <file1> [file2 ...]"
echo ""
echo "Profiles:"
echo " --hi-rate Highest bitrate and quality (CRF 14, 320k audio)"
echo " --portable Smaller file size for mobile (CRF 24, 128k audio)"
echo "" echo ""
echo "All outputs will be saved in: $OUTPUT_DIR" echo "All outputs will be saved in: $OUTPUT_DIR"
echo "Logs stored in: $LOG_DIR"
;; ;;
esac esac