From b8c07a4247a338379823540d2d9324539db79e19 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 5 Nov 2025 14:15:53 -0500 Subject: [PATCH] Add cross-platform videoinfo command with verbose option --- Makefile | 79 +++++++--- VERSION | 1 + docs/CLI_Functions.md | 43 +++--- docs/Conversion_Settings.md | 68 +++++---- docs/README.md | 40 +++-- docs/Upscale.md | 22 +-- video-tools.sh | 296 +++++++++++++++++++++++++++++++++--- 7 files changed, 437 insertions(+), 112 deletions(-) create mode 100644 VERSION 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