Compare commits

..

No commits in common. "b8c07a4247a338379823540d2d9324539db79e19" and "55382180a555831aa9740c9f8516897b978ba65f" have entirely different histories.

8 changed files with 112 additions and 499 deletions

View File

@ -1,76 +1,37 @@
# root/Makefile # VideoTools Makefile
# Cross-platform installer for VideoTools (Linux / Windows via Git Bash or WSL) # Cross-platform installer for Linux and Windows (Git Bash/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)"
@install -m 755 "$(SCRIPT)" "$(INSTALL_DIR)/video-tools" cp "$(SCRIPT)" "$(INSTALL_DIR)/video-tools"
@echo "✔ Installed script to $(INSTALL_DIR)/video-tools" chmod +x "$(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 "✔ Installed documentation to $(DOC_DIR)" @echo "Documentation installed 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 "Available documentation files:" @echo "Opening documentation..."
@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 verify Check if FFmpeg and environment are ready" @echo " make docs List documentation files"
@echo " make docs List documentation files" @echo " make help Show this message"
@echo " make help Show this message"

View File

@ -1 +0,0 @@
0.1.1

View File

@ -1,62 +0,0 @@
# 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,5 +1,4 @@
# docs/CLI_Functions.md # CLI Function Reference
# 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.
@ -13,7 +12,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, unless overridden in `config/config.json`. All outputs are saved in your default `~/Videos` folder.
--- ---
@ -22,7 +21,7 @@ All outputs are saved in your default `~/Videos` folder, unless overridden in `c
### 1. convert-single ### 1. convert-single
**Purpose** **Purpose**
Convert a single input video file into an MP4 using modern compression and audio standards. Convert a single input video file into an MP4 with modern compression and audio standards.
**Usage** **Usage**
```bash ```bash
@ -42,16 +41,15 @@ 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.
- Uses H.264 (`libx264`) for video and AAC for audio. - Ensures output uses the H.264 codec (libx264) and AAC audio.
- Rebuilds timestamps with `-fflags +genpts` to prevent sync issues. - Rebuilds timestamps to avoid sync issues.
- Adds `-movflags +faststart` to improve playback and streaming performance. - Adds `+faststart` flag for quicker playback when streamed or loaded in players.
- Displays detailed FFmpeg progress and conversion status.
**When to Use** **When to use**
- To modernize older video files. - When you want to upgrade old videos to a more efficient format.
- When a video fails to play properly on mobile or modern devices. - When a single video wont play on mobile or modern devices.
- To reduce file size while retaining visual quality. - To reduce file size without losing visible quality.
--- ---
@ -80,15 +78,14 @@ video-tools convert-multiple \
``` ```
**Behavior** **Behavior**
- Merges all listed videos sequentially. - Reads all listed files in order and merges them seamlessly.
- Re-encodes each input using the same H.264/AAC settings. - Each input is re-encoded using the same H.264/AAC settings.
- Creates and deletes a temporary file list automatically. - Temporary file list is automatically created and deleted.
- Displays each added file and overall progress in the terminal. - Logs detailed FFmpeg progress to the terminal.
- Ensures the output file is playable immediately after completion.
**When to Use** **When to use**
- To combine multi-part video discs, episodes, or segmented clips. - When combining multi-part video discs, episodes, or scene splits.
- When creating a single playback file from multiple source files. - When creating a single playable MP4 from segmented source material.
--- ---
@ -134,8 +131,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,24 +1,22 @@
# docs/Conversion_Settings.md # Conversion Settings
# 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
``` ```
**Why MP4?** MP4 was chosen because:
- Universally supported across modern devices. - Its universally supported across modern devices.
- Balances quality, compression, and compatibility. - It balances size and compatibility well.
- H.264 encoding provides excellent visual quality at modest bitrates. - H.264 encoding provides excellent quality at smaller bitrates.
--- ---
@ -27,8 +25,8 @@ Audio Codec: AAC
| 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 at moderate CPU cost | | `-preset slow` | Encoding preset | Improves compression efficiency; trades some speed |
| `-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 |
@ -38,52 +36,52 @@ Audio Codec: AAC
## Quality vs. File Size ## Quality vs. File Size
The **CRF scale** (used by FFmpeg) defines the balance between quality and compression: The CRF scale (used by FFmpeg) defines quality and compression balance:
| 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 | Smaller file sizes with light compression | | 2124 | Medium quality | Small file sizes, light compression |
| 25+ | Low quality | Fast compression, noticeable loss | | 25+ | Low quality | Fast compression, heavy size reduction |
**Default CRF: 18** The default of **CRF 18** keeps visual clarity virtually identical to the original while cutting most AVI or MPG files to **4060% smaller** sizes.
Provides visually lossless results while reducing most AVI/MPG files to **4060% smaller sizes**.
--- ---
## Why Use Preset “slow” ## Why Preset "slow"
The preset defines the trade-off between encoding speed and compression efficiency. The preset defines encoding effort vs. compression efficiency.
Range: `ultrafast``veryslow`. Options range from `ultrafast``veryslow`.
- `slow` offers excellent compression without excessive CPU time. - **slow** provides strong compression without extreme CPU time.
- Typical speed: ~23× real-time on a midrange CPU. - Encoding speed is roughly 23× real-time on a midrange CPU.
- Output files are usually **1020% smaller** than with `medium` preset at the same quality. - File sizes are typically 1020% smaller than `medium` preset at the same quality.
--- ---
## Audio Strategy ## Audio Strategy
**AAC at 192k** is chosen for: AAC at 192k is chosen for:
- Broad device compatibility (phones, TVs, web players). - Wide playback compatibility (phones, TVs, players).
- Transparent stereo sound for most content. - Transparent stereo sound for most sources.
- Efficient storage (~2 MB/min of stereo audio). - Reasonable storage size (~2MB/minute of stereo audio).
Future options may include: If future needs arise, the tool can later support:
- `-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 — the video retains its original resolution and color profile. No scaling is applied by default.
The video retains original resolution and color profile.
Planned future command: `upscale-video`, supporting: Upscaling and filtering will be handled in a separate command (`upscale-video`) in future versions, with support for:
- FFmpegs `scale` and `zscale` filters. - FFmpegs `scale` and `zscale` filters.
- `lanczos` resampling for sharp, clean upscales. - `lanczos` resampling (for sharp, clean upscale).
- Optional ML upscaling (Real-ESRGAN, waifu2x). - Optional integration with ML-based models (Real-ESRGAN, waifu2x).
--- ---

View File

@ -1,5 +1,4 @@
# docs/README.md # Video Tools CLI
# 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.
@ -39,7 +38,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://git.leaktechnologies.dev/Leak_Technologies/VideoTools.git git clone https://github.com/YourName/VideoTools.git
cd VideoTools cd VideoTools
``` ```
@ -68,11 +67,10 @@ 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).
--- ---
@ -86,7 +84,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
``` ```
@ -103,7 +101,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
``` ```
@ -125,29 +123,15 @@ video-tools convert-multiple \
| 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 may modify or share this tool with anyone. You can modify or share this tool with anyone.
---
End of File

View File

@ -1,5 +1,4 @@
# docs/Upscale.md # Upscale Module (Planned)
# 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.
@ -12,7 +11,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 begin with FFmpegs native scaling filters and may later support machine learning-based methods. It will use FFmpegs native scaling filters first and optionally integrate machine learning tools later.
--- ---
@ -34,10 +33,10 @@ Optional parameters:
## Default Behavior ## Default Behavior
If no explicit resolution is provided, the tool will upscale intelligently to the nearest standard resolution above the source. If no resolution is provided, the tool will upscale intelligently to the nearest standard resolution above the input video.
**Examples:** Example:
- Input: 960×540 → Output: 1280×720 - Input: 960×540 → Output: 1280×720
- Input: 1280×720 → Output: 1920×1080 - Input: 1280×720 → Output: 1920×1080
--- ---
@ -51,10 +50,9 @@ 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
``` ```
**Explanation:** - `scale=1920:1080:flags=lanczos` → high-quality resampling
- `scale=1920:1080:flags=lanczos` → performs high-quality resampling. - `lanczos` offers sharp, low-artifact upscale
- `lanczos` produces sharp, low-artifact results. - Later versions may support `zscale` or ML-based filters
- Future versions may introduce `zscale` or machine-learning upscalers.
--- ---
@ -62,9 +60,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,19 +1,17 @@
#!/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
# ========================================================== # ==========================================================
SCRIPT_DIR="$(dirname "$0")" CONFIG_FILE="$(dirname "$0")/config/config.json"
CONFIG_FILE="$SCRIPT_DIR/config/config.json"
LOG_DIR="$HOME/.local/share/video-tools/logs"
# ------------------------- # -------------------------
# Default configuration # Default configuration
@ -24,7 +22,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.1" VERSION="0.1.0"
# ========================================================== # ==========================================================
# Utility: Colour logging # Utility: Colour logging
@ -41,7 +39,7 @@ warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# ========================================================== # ==========================================================
# Dependency check # Check dependencies early
# ========================================================== # ==========================================================
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."
@ -64,33 +62,6 @@ 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
@ -107,81 +78,6 @@ 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
# ========================================================== # ==========================================================
@ -189,243 +85,85 @@ 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" 2>&1 | tee "$log_file" "$output_path" || { error "Conversion failed."; exit 1; }
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() {
# Rebuild args array safely to preserve quoted file paths local args=("$@")
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
printf "file '%s'\n" "$(safe_path "$input")" >> "$list_file" echo "file '$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" 2>&1 | tee "$log_file" "$output_path" || { error "Merge failed."; rm -f "$list_file"; exit 1; }
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 [[ $# -lt 2 ]]; then if [[ $# -ne 2 ]]; then
error "Usage: $0 convert-single <input> <output.mp4> [--hi-rate|--portable]" error "Usage: $0 convert-single <input> <output.mp4>"
exit 1 exit 1
fi fi
# Optional profile flag convert_single "$@"
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> [--hi-rate|--portable]" error "Usage: $0 convert-multiple <input1> <input2> ... <output.mp4>"
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> [--hi-rate|--portable]" echo " $0 convert-single <input> <output.mp4>"
echo " $0 convert-multiple <input1> <input2> ... <output.mp4> [--hi-rate|--portable]" echo " $0 convert-multiple <input1> <input2> ... <output.mp4>"
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