diff --git a/Makefile b/Makefile
index a958bce..cebec4f 100644
--- a/Makefile
+++ b/Makefile
@@ -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"
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..17e51c3
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.1
diff --git a/docs/CLI_Functions.md b/docs/CLI_Functions.md
index a9ee50a..02268a0 100644
--- a/docs/CLI_Functions.md
+++ b/docs/CLI_Functions.md
@@ -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 ` | Converts a single video to MP4. |
| `convert-multiple` | `video-tools convert-multiple ... ` | 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
@@ -41,15 +42,16 @@ 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.
+- Converts older formats (AVI, MPG, MOV, MKV, etc.) into MP4.
+- 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 won’t 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.
---
@@ -131,8 +134,8 @@ video-tools convert-multiple \
- Re-encoding ensures compatibility and stable playback.
### File Safety
-- Original files are untouched.
-- All intermediate list files are removed automatically.
+- Original files are untouched.
+- All intermediate list files are removed automatically.
- FFmpeg handles SIGINT (Ctrl+C) gracefully—partially written files are still playable.
---
diff --git a/docs/Conversion_Settings.md b/docs/Conversion_Settings.md
index 13ddd35..4a390c4 100644
--- a/docs/Conversion_Settings.md
+++ b/docs/Conversion_Settings.md
@@ -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.
---
## Output Format
+
All conversions produce:
```
-Format: MP4
-Container: MPEG-4 Part 14
-Video Codec: H.264 (libx264)
+Format: MP4
+Container: MPEG-4 Part 14
+Video Codec: H.264 (libx264)
Audio Codec: AAC
```
-MP4 was chosen because:
-- It’s 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,52 +38,52 @@ 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 |
|------|-------------|--------------|
| 14–16 | Near lossless | Archival or professional mastering |
| 18–20 | High quality | Everyday use, visually lossless |
-| 21–24 | Medium quality | Small file sizes, light compression |
-| 25+ | Low quality | Fast compression, heavy size reduction |
+| 21–24 | 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 **40–60% smaller** sizes.
+**Default CRF: 18**
+Provides visually lossless results while reducing most AVI/MPG files to **40–60% 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 2–3× real-time on a midrange CPU.
-- File sizes are typically 10–20% smaller than `medium` preset at the same quality.
+- `slow` offers excellent compression without excessive CPU time.
+- Typical speed: ~2–3× real-time on a midrange CPU.
+- Output files are usually **10–20% 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:
-- `-c:a copy` for untouched audio streams.
-- `-b:a 320k` for high-fidelity preservation.
+Future options may include:
+- `-c:a copy` for untouched audio streams.
+- `-b:a 320k` for high-fidelity preservation.
---
## 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:
-- FFmpeg’s `scale` and `zscale` filters.
-- `lanczos` resampling (for sharp, clean upscale).
-- Optional integration with ML-based models (Real-ESRGAN, waifu2x).
+Planned future command: `upscale-video`, supporting:
+- FFmpeg’s `scale` and `zscale` filters.
+- `lanczos` resampling for sharp, clean upscales.
+- Optional ML upscaling (Real-ESRGAN, waifu2x).
---
diff --git a/docs/README.md b/docs/README.md
index d007b43..d719425 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -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.
+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
diff --git a/docs/Upscale.md b/docs/Upscale.md
index 8e3057a..b5dce4c 100644
--- a/docs/Upscale.md
+++ b/docs/Upscale.md
@@ -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 FFmpeg’s native scaling filters first and optionally integrate machine learning tools later.
+It will begin with FFmpeg’s native scaling filters and may later support machine learning-based methods.
---
@@ -33,10 +34,10 @@ 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:
-- Input: 960×540 → Output: 1280×720
+**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 |
diff --git a/video-tools.sh b/video-tools.sh
index f5c1d1e..302cfc1 100755
--- a/video-tools.sh
+++ b/video-tools.sh
@@ -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 ... [--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 [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 "
+ if [[ $# -lt 2 ]]; then
+ error "Usage: $0 convert-single [--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 ... "
+ error "Usage: $0 convert-multiple ... [--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 "
- echo " $0 convert-multiple ... "
+ echo " $0 convert-single [--hi-rate|--portable]"
+ echo " $0 convert-multiple ... [--hi-rate|--portable]"
+ echo " $0 videoinfo [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