Compare commits

...

2 Commits

8 changed files with 499 additions and 112 deletions

View File

@ -1,37 +1,76 @@
# VideoTools Makefile
# Cross-platform installer for Linux and Windows (Git Bash/WSL)
# root/Makefile
# Cross-platform installer for VideoTools (Linux / Windows via Git Bash or WSL)
PREFIX ?= /usr/local
INSTALL_DIR = $(PREFIX)/bin
DOC_DIR = $(PREFIX)/share/doc/videotools
SCRIPT = video-tools.sh
DOCS = docs
VERSION_FILE = VERSION
install:
# ------------------------
# Installation
# ------------------------
install: verify
@echo "Installing VideoTools..."
mkdir -p "$(INSTALL_DIR)"
cp "$(SCRIPT)" "$(INSTALL_DIR)/video-tools"
chmod +x "$(INSTALL_DIR)/video-tools"
@echo "Copied script to $(INSTALL_DIR)/video-tools"
@mkdir -p "$(INSTALL_DIR)"
@install -m 755 "$(SCRIPT)" "$(INSTALL_DIR)/video-tools"
@echo "✔ Installed script to $(INSTALL_DIR)/video-tools"
mkdir -p "$(DOC_DIR)"
cp -r "$(DOCS)"/* "$(DOC_DIR)/"
@echo "Documentation installed to $(DOC_DIR)"
@mkdir -p "$(DOC_DIR)"
@cp -r "$(DOCS)"/* "$(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:
@echo "Removing VideoTools..."
rm -f "$(INSTALL_DIR)/video-tools"
rm -rf "$(DOC_DIR)"
@echo "VideoTools uninstalled."
@rm -f "$(INSTALL_DIR)/video-tools"
@rm -rf "$(DOC_DIR)"
@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:
@echo "Opening documentation..."
@ls -1 $(DOCS)
@echo "Available documentation files:"
@ls -1 "$(DOCS)"
# ------------------------
# Help
# ------------------------
help:
@echo "Available targets:"
@echo " make install Install the toolkit system-wide"
@echo " make uninstall Remove the toolkit"
@echo " make docs List documentation files"
@echo " make help Show this message"
@echo " make install Install the toolkit system-wide"
@echo " make uninstall Remove the toolkit"
@echo " make verify Check if FFmpeg and environment are ready"
@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`.
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-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
**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**
```bash
@ -42,14 +43,15 @@ video-tools convert-single \
**Behavior**
- Converts older formats (AVI, MPG, MOV, MKV, etc.) into MP4.
- Ensures output uses the H.264 codec (libx264) and AAC audio.
- Rebuilds timestamps to avoid sync issues.
- Adds `+faststart` flag for quicker playback when streamed or loaded in players.
- Uses H.264 (`libx264`) for video and AAC for audio.
- Rebuilds timestamps with `-fflags +genpts` to prevent sync issues.
- Adds `-movflags +faststart` to improve playback and streaming performance.
- Displays detailed FFmpeg progress and conversion status.
**When to use**
- When you want to upgrade old videos to a more efficient format.
- When a single video wont play on mobile or modern devices.
- To reduce file size without losing visible quality.
**When to Use**
- To modernize older video files.
- When a video fails to play properly on mobile or modern devices.
- To reduce file size while retaining visual quality.
---
@ -78,14 +80,15 @@ video-tools convert-multiple \
```
**Behavior**
- Reads all listed files in order and merges them seamlessly.
- Each input is re-encoded using the same H.264/AAC settings.
- Temporary file list is automatically created and deleted.
- Logs detailed FFmpeg progress to the terminal.
- Merges all listed videos sequentially.
- Re-encodes each input using the same H.264/AAC settings.
- Creates and deletes a temporary file list automatically.
- Displays each added file and overall progress in the terminal.
- Ensures the output file is playable immediately after completion.
**When to use**
- When combining multi-part video discs, episodes, or scene splits.
- When creating a single playable MP4 from segmented source material.
**When to Use**
- To combine multi-part video discs, episodes, or segmented clips.
- When creating a single playback file from multiple source files.
---

View File

@ -1,10 +1,12 @@
# 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.
---
## Output Format
All conversions produce:
```
Format: MP4
@ -13,10 +15,10 @@ Video Codec: H.264 (libx264)
Audio Codec: AAC
```
MP4 was chosen because:
- Its universally supported across modern devices.
- It balances size and compatibility well.
- H.264 encoding provides excellent quality at smaller bitrates.
**Why MP4?**
- Universally supported across modern devices.
- Balances quality, compression, and compatibility.
- H.264 encoding provides excellent visual quality at modest bitrates.
---
@ -25,8 +27,8 @@ MP4 was chosen because:
| Parameter | Value | Purpose |
|------------|--------|----------|
| `-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 |
| `-preset slow` | Encoding preset | Improves compression efficiency; trades some speed |
| `-crf 18` | Constant Rate Factor | Maintains near-lossless visual quality (lower = better) |
| `-preset slow` | Encoding preset | Improves compression efficiency at moderate CPU cost |
| `-c:a aac` | Audio codec | Standard high-quality stereo audio |
| `-b:a 192k` | Audio bitrate | Balances fidelity and file size |
| `-movflags +faststart` | MP4 optimization | Enables faster playback start in players or web streams |
@ -36,38 +38,39 @@ MP4 was chosen because:
## 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 |
|------|-------------|--------------|
| 1416 | Near lossless | Archival or professional mastering |
| 1820 | High quality | Everyday use, visually lossless |
| 2124 | Medium quality | Small file sizes, light compression |
| 25+ | Low quality | Fast compression, heavy size reduction |
| 2124 | Medium quality | Smaller file sizes with light compression |
| 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.
Options range from `ultrafast``veryslow`.
The preset defines the trade-off between encoding speed and compression efficiency.
Range: `ultrafast``veryslow`.
- **slow** provides strong compression without extreme CPU time.
- Encoding speed is roughly 23× real-time on a midrange CPU.
- File sizes are typically 1020% smaller than `medium` preset at the same quality.
- `slow` offers excellent compression without excessive CPU time.
- Typical speed: ~23× real-time on a midrange CPU.
- Output files are usually **1020% smaller** than with `medium` preset at the same quality.
---
## Audio Strategy
AAC at 192k is chosen for:
- Wide playback compatibility (phones, TVs, players).
- Transparent stereo sound for most sources.
- Reasonable storage size (~2MB/minute of stereo audio).
**AAC at 192k** is chosen for:
- Broad device compatibility (phones, TVs, web players).
- Transparent stereo sound for most content.
- 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.
- `-b:a 320k` for high-fidelity preservation.
@ -75,13 +78,12 @@ If future needs arise, the tool can later support:
## Color Space & Scaling
No scaling is applied by default.
The video retains original resolution and color profile.
No scaling is applied by default — the video retains its 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.
- `lanczos` resampling (for sharp, clean upscale).
- Optional integration with ML-based models (Real-ESRGAN, waifu2x).
- `lanczos` resampling for sharp, clean upscales.
- 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.
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:
```bash
git clone https://github.com/YourName/VideoTools.git
git clone https://git.leaktechnologies.dev/Leak_Technologies/VideoTools.git
cd VideoTools
```
@ -67,10 +68,11 @@ video-tools convert-single "input.avi" "output.mp4"
| Video codec | `libx264` | High-quality H.264 encoding |
| Audio codec | `aac` | High-quality AAC stereo audio |
| 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 |
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"
```
Output:
**Output**
```
/home/stu/Videos/Example Movie.mp4
```
@ -101,7 +103,7 @@ video-tools convert-multiple \
"Example Movie Combined.mp4"
```
Output:
**Output**
```
/home/stu/Videos/Example Movie Combined.mp4
```
@ -123,15 +125,29 @@ Output:
| Feature | Status |
|----------|--------|
| convert-single | ✅ Done |
| convert-multiple | ✅ Done |
| upscale-video | 🔜 Planned |
| batch conversion | 🔜 Planned |
| automatic format detection | 🔜 Planned |
| `convert-single` | ✅ Done |
| `convert-multiple` | ✅ Done |
| `upscale-video` | 🔜 Planned |
| `batch conversion` | 🔜 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
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.
@ -11,7 +12,7 @@ This module will allow upscaling of existing videos to higher resolutions such a
- 1080p (Full HD)
- 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,9 +34,9 @@ Optional parameters:
## 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: 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
```
- `scale=1920:1080:flags=lanczos` → high-quality resampling
- `lanczos` offers sharp, low-artifact upscale
- Later versions may support `zscale` or ML-based filters
**Explanation:**
- `scale=1920:1080:flags=lanczos` → performs high-quality resampling.
- `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 |
|----------|--------|
| FFmpeg scaler (lanczos) | 🔜 Planned |
| FFmpeg scaler (`lanczos`) | 🔜 Planned |
| ML-based upscaling (Real-ESRGAN / waifu2x) | 🚧 Research |
| Auto resolution detection | 🔜 Planned |
| Auto-resolution detection | 🔜 Planned |
| GPU acceleration support | 🔜 Planned |
| Configurable presets | 🔜 Planned |

View File

@ -1,17 +1,19 @@
#!/usr/bin/env bash
#
# video-tools.sh
# ----------------------------------------------------------
# Simple FFmpeg CLI Toolset
# -------------------------
# Provides easy commands for single or multi-file video conversion.
# Works on Linux and Windows (Git Bash / WSL) as long as ffmpeg is installed.
#
# ----------------------------------------------------------
set -euo pipefail
# ==========================================================
# 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
@ -22,7 +24,7 @@ AUDIO_CODEC="aac"
CRF="18" # lower = higher quality
PRESET="slow" # slower = better compression
AUDIO_BITRATE="192k"
VERSION="0.1.0"
VERSION="0.1.1"
# ==========================================================
# Utility: Colour logging
@ -39,7 +41,7 @@ warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# ==========================================================
# Check dependencies early
# Dependency check
# ==========================================================
if ! command -v ffmpeg >/dev/null 2>&1; then
error "ffmpeg is not installed or not in PATH."
@ -62,6 +64,33 @@ else
fi
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
@ -78,6 +107,81 @@ print_header() {
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
# ==========================================================
@ -85,85 +189,243 @@ convert_single() {
local input="$1"
local output_name="$2"
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
info "Converting single video..."
info "Input : $input"
info "Output: $output_path"
info "Log : $log_file"
if [[ ! -f "$input" ]]; then
error "Input file not found: $input"
exit 1
fi
start_time=$(date +%s)
ffmpeg -hide_banner -loglevel info -fflags +genpts \
-i "$input" \
-c:v "$VIDEO_CODEC" -crf "$CRF" -preset "$PRESET" \
-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"
print_summary "$log_file" "$safe_input" "$output_path" "$start_time" "$end_time"
}
# ==========================================================
# Combine multiple files
# ==========================================================
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]}"
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
list_file=$(mktemp)
local output_path="$OUTPUT_DIR/$output_name"
local log_file
log_file=$(generate_logfile "$output_name")
local start_time end_time
print_header
info "Combining multiple videos..."
info "Output: $output_path"
info "Log : $log_file"
# Verify each input and build concat list
for input in "${args[@]}"; do
if [[ ! -f "$input" ]]; then
error "Missing input file: $input"
error "Missing input file: '$input'"
rm -f "$list_file"
exit 1
fi
echo "file '$input'" >> "$list_file"
printf "file '%s'\n" "$(safe_path "$input")" >> "$list_file"
info " + Added: $input"
done
echo "----------------------------------------------"
# Run FFmpeg and record timing
start_time=$(date +%s)
ffmpeg -hide_banner -loglevel info -f concat -safe 0 -i "$list_file" \
-c:v "$VIDEO_CODEC" -crf "$CRF" -preset "$PRESET" \
-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"
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
# ==========================================================
case "${1:-}" in
convert-single)
shift
if [[ $# -ne 2 ]]; then
error "Usage: $0 convert-single <input> <output.mp4>"
if [[ $# -lt 2 ]]; then
error "Usage: $0 convert-single <input> <output.mp4> [--hi-rate|--portable]"
exit 1
fi
convert_single "$@"
# Optional profile flag
if [[ $# -eq 3 ]]; then
apply_profile "${3:-}"
fi
convert_single "$1" "$2"
;;
convert-multiple)
shift
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
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 "$@"
;;
videoinfo|--videoinfo|--fileinfo)
shift
show_videoinfo "$@"
;;
*)
echo "Video Tools v$VERSION"
echo "Usage:"
echo " $0 convert-single <input> <output.mp4>"
echo " $0 convert-multiple <input1> <input2> ... <output.mp4>"
echo " $0 convert-single <input> <output.mp4> [--hi-rate|--portable]"
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 "All outputs will be saved in: $OUTPUT_DIR"
echo "Logs stored in: $LOG_DIR"
;;
esac