diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7e03ff1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +{ + "name": "DaVinci Video Converter", + "image": "docker.io/library/fedora:39", + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "twxs.cmake" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "C_Cpp.default.compilerPath": "/usr/bin/g++", + "C_Cpp.default.cStandard": "gnu17", + "C_Cpp.default.cppStandard": "gnu++17" + } + } + }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZsh": true, + "upgradePackages": true, + "username": "vscode", + "userUid": "1000", + "userGid": "1000" + } + }, + "postCreateCommand": "dnf install -y gcc-c++ make ffmpeg ffmpeg-devel && dnf clean all", + "remoteUser": "vscode", + "updateRemoteUserUID": true, + "mounts": [ + "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached" + ], + "runArgs": [ + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined" + ] +} diff --git a/Makefile b/Makefile index a652c86..78fd315 100644 --- a/Makefile +++ b/Makefile @@ -1,52 +1,38 @@ -# -# 'make' build executable file 'davinci-convert' -# 'make clean' removes all .o and executable files -# - -# Define the C++ compiler to use CXX = g++ +CXXFLAGS = -std=c++17 -Wall -Wextra -O2 +INCLUDES = -Isrc/include +TARGET = davinci-video-converter -# Define any compile-time flags -CXXFLAGS = -std=c++17 -Wall -Wextra -g +SRCS = src/main.cpp src/parser.cpp src/validator.cpp src/converter.cpp +OBJS = $(SRCS:.cpp=.o) -# Define output directory -OUTPUT = output +all: $(TARGET) -# Define source directory -SRC = src +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) -# Define the main executable name -MAIN = davinci-convert +src/%.o: src/%.cpp + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ -# Define the C source files -SOURCES = $(wildcard $(SRC)/*.cpp) +src/main.o: src/main.cpp src/include/config.hpp src/include/parser.hpp src/include/validator.hpp src/include/converter.hpp +src/parser.o: src/parser.cpp src/include/parser.hpp src/include/config.hpp +src/validator.o: src/validator.cpp src/include/validator.hpp src/include/config.hpp +src/converter.o: src/converter.cpp src/include/converter.hpp src/include/config.hpp -# Define the C object files -OBJECTS = $(SOURCES:.cpp=.o) - -OUTPUTMAIN = $(OUTPUT)/$(MAIN) - -all: $(OUTPUT) $(OUTPUTMAIN) - @echo "Building executable: $(MAIN)" - @echo Executing 'all' complete! - -$(OUTPUT): - mkdir -p $(OUTPUT) - -$(OUTPUTMAIN): $(OBJECTS) - $(CXX) $(CXXFLAGS) -o $(OUTPUTMAIN) $(OBJECTS) - -.PHONY: clean clean: - @echo "Cleaning up..." - rm -f $(OUTPUTMAIN) - rm -f $(OBJECTS) - @echo Cleanup complete! + rm -f $(TARGET) $(OBJS) -run: all - @echo "Running executable: $(OUTPUTMAIN)" - ./$(OUTPUTMAIN) - @echo Executing 'run: all' complete! +install: $(TARGET) + cp $(TARGET) /usr/local/bin/ -install: all - install -Dm755 $(OUTPUTMAIN) /usr/bin/davinci-convert +uninstall: + rm -f /usr/local/bin/$(TARGET) + +test: $(TARGET) + @echo "Running tests..." + @./$(TARGET) --help + @echo "Test: Help command passed" + @./$(TARGET) 2>&1 | grep -q "Usage:" && echo "Test: No args passed" || (echo "Test failed" && exit 1) + @echo "All tests passed!" + +.PHONY: all clean install uninstall test diff --git a/README.md b/README.md index 94af9a5..e82a17a 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,137 @@ -# Davinci Video Converter +# DaVinci Video Converter -This is a simple tool that converts your `.mp4` videos into a format that DaVinci Resolve uses on Linux. The application utilizes `ffmpeg` for video conversion. +A command-line video conversion tool optimized for DaVinci Resolve workflows. ## Features -- Convert `.mp4` videos to DNxHD format. -- Simple command-line interface for user input. +- Convert videos using various codecs (H.264, H.265, ProRes) +- Quality presets for different use cases +- CRF-based quality control +- Verbose output for debugging -## Prerequisites +## Development Environment -Before building and running the application, ensure you have the following installed: +### DevContainer (Podman) -- `g++` (GNU C++ Compiler) -- `make` (Build automation tool) -- `ffmpeg` (Multimedia framework for handling video, audio, and other multimedia files) +This project includes a DevContainer configuration for use with Podman. To use it: -## Installation +1. Install the Dev Containers extension in VS Code +2. Configure VS Code to use Podman: + - Set `remote.containers.defaultDockerCommand` to `podman` in VS Code settings +3. Reopen the project in the container (Ctrl+Shift+P → "Dev Containers: Reopen in Container") -### Using the Build Script +The container includes all necessary dependencies (g++, make, ffmpeg). -1. Clone the repository: - ```bash - git clone (https://github.com/tkmxqrdxddd/davinci-video-converter) - cd davinci-video-converter - ``` +### Nix Shell -2. Run the build script to install dependencies and build the project: - ```bash - ./build.sh - ``` - - This script will automatically install the required dependencies based on your Linux distribution and build the project. It will also install the application to `/usr/bin`, making it accessible from anywhere. - -### Manual Installation - -If you prefer to install manually, follow these steps: - -1. Install the required dependencies (if not already installed): - - For Debian-based systems: - ```bash - sudo apt-get install -y build-essential ffmpeg - ``` - - For Red Hat-based systems: - ```bash - sudo dnf install -y gcc-c++ ffmpeg make - ``` - - For Arch Linux: - ```bash - sudo pacman -Syu --noconfirm base-devel ffmpeg - ``` - - For openSUSE: - ```bash - sudo zypper install -y gcc-c++ ffmpeg make - ``` - - For Alpine Linux: - ```bash - sudo apk add --no-cache g++ ffmpeg make - ``` - -2. Build the project using `make`: - ```bash - make - ``` - - This will create an executable named `davinci-convert` in the `output` directory. - -3. Install the application: - ```bash - make install - ``` - -## Running the Program - -To run the program, use the following command: +For Nix users, enter the development shell: ```bash -davinci-convert +nix-shell ``` -### Usage +This provides a development environment with g++, make, and ffmpeg. -1. When prompted, enter the input file path of the `.mp4` video you want to convert. -2. Enter the desired output file path (including the filename and extension) for the converted video. -3. The program will start the conversion process. You will see messages indicating the progress. -4. Once the conversion is complete, you will receive a success message. +## Building -### Cleaning Up +### Using Make + +```bash +make +``` + +### Using build.sh + +```bash +./build.sh +``` + +### Clean Build -To clean up the generated files (object files and the executable), run: ```bash make clean ``` -## Contributing +## Usage -If you would like to contribute to this project, please fork the repository and submit a pull request. Any contributions, bug reports, or feature requests are welcome! +```bash +./davinci-video-converter [options] +``` + +### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `-c, --codec ` | Video codec (h264, h265, prores) | h264 | +| `-q, --quality ` | Quality preset (fast, medium, slow) | medium | +| `-r, --crf ` | CRF value 0-51 | 23 | +| `-v, --verbose` | Enable verbose output | false | +| `-h, --help` | Show help message | - | + +### Examples + +Convert with default settings: +```bash +./davinci-video-converter input.mp4 output.mp4 +``` + +Convert with H.265 codec and slow preset: +```bash +./davinci-video-converter -c h265 -q slow input.mp4 output.mp4 +``` + +Convert with custom CRF value: +```bash +./davinci-video-converter -r 18 input.mp4 output.mp4 +``` + +Verbose conversion: +```bash +./davinci-video-converter -v input.mp4 output.mp4 +``` + +## Testing + +Run the built-in tests: + +```bash +make test +``` + +## Installation + +```bash +sudo make install +``` + +## Uninstallation + +```bash +sudo make uninstall +``` + +## Project Structure + +``` +davinci-video-converter/ +├── .devcontainer/ +│ └── devcontainer.json # DevContainer configuration for Podman +├── src/ +│ ├── include/ +│ │ ├── config.hpp # Configuration struct definition +│ │ ├── converter.hpp # Converter module interface +│ │ ├── parser.hpp # Argument parser interface +│ │ └── validator.hpp # Validator module interface +│ ├── main.cpp # Main entry point +│ ├── parser.cpp # Argument parsing implementation +│ ├── validator.cpp # Configuration validation +│ └── converter.cpp # FFmpeg command building and execution +├── shell.nix # Nix development environment +├── Makefile # Build configuration +├── build.sh # Build script +└── README.md +``` ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. - -## Acknowledgments - -- This project uses `ffmpeg` for video conversion. For more information, visit the [FFmpeg website](https://ffmpeg.org/). +MIT diff --git a/build.sh b/build.sh index d0648ba..2e331d3 100644 --- a/build.sh +++ b/build.sh @@ -1,96 +1,10 @@ #!/bin/bash -set -e # Exit immediately if a command exits with a non-zero status -set -u # Treat unset variables as an error when substituting -set -o pipefail # Prevent errors in a pipeline from being masked +set -e -# Function to install dependencies for Debian-based systems -install_debian_dependencies() { - echo "Installing dependencies for Debian-based systems..." - sudo apt-get update - sudo apt-get install -y build-essential ffmpeg || { - echo "Failed to install dependencies for Debian-based systems." - exit 1 - } -} +echo "Building DaVinci Video Converter..." -# Function to install dependencies for Red Hat-based systems -install_redhat_dependencies() { - echo "Installing dependencies for Red Hat-based systems..." - sudo dnf install -y gcc-c++ ffmpeg make || { - echo "Failed to install dependencies for Red Hat-based systems." - exit 1 - } -} - -# Function to install dependencies for Arch Linux -install_arch_dependencies() { - echo "Installing dependencies for Arch Linux..." - sudo pacman -S --noconfirm base-devel ffmpeg || { - echo "Failed to install dependencies for Arch Linux." - exit 1 - } -} - -# Function to install dependencies for openSUSE -install_opensuse_dependencies() { - echo "Installing dependencies for openSUSE..." - sudo zypper install -y gcc-c++ ffmpeg make || { - echo "Failed to install dependencies for openSUSE." - exit 1 - } -} - -# Function to install dependencies for Alpine Linux -install_alpine_dependencies() { - echo "Installing dependencies for Alpine Linux..." - sudo apk add --no-cache g++ ffmpeg make || { - echo "Failed to install dependencies for Alpine Linux." - exit 1 - } -} - -# Check the package manager and install dependencies accordingly -if [ -f /etc/debian_version ]; then - install_debian_dependencies -elif [ -f /etc/redhat-release ]; then - install_redhat_dependencies -elif [ -f /etc/arch-release ]; then - install_arch_dependencies -elif [ -f /etc/os-release ]; then - . /etc/os-release - case "$ID" in - opensuse*) - install_opensuse_dependencies - ;; - alpine) - install_alpine_dependencies - ;; - *) - echo "Unsupported distribution: $ID. Please install the required packages manually." - exit 1 - ;; - esac -else - echo "Unsupported distribution. Please install the required packages manually." - exit 1 -fi - -# Build the project -echo "Building the project..." +make clean make -# Check if the build was successful -if [ $? -eq 0 ]; then - echo "Build completed successfully." -else - echo "Build failed. Please check the output for errors." - exit 1 -fi - -# Install the application -echo "Installing the application..." -make install - -# Inform the user about the executable -echo "You can run the application using 'davinci-convert'" +echo "Build complete! Binary: ./davinci-video-converter" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..67353c0 --- /dev/null +++ b/shell.nix @@ -0,0 +1,22 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + name = "davinci-video-converter"; + + buildInputs = with pkgs; [ + gcc13 + make + ffmpeg + pkg-config + ]; + + shellHook = '' + export CXX=g++ + export CC=gcc + echo "DaVinci Video Converter development environment loaded" + echo "Available commands:" + echo " make - Build the project" + echo " make clean - Clean build artifacts" + echo " make test - Run tests" + ''; +} diff --git a/src/converter.cpp b/src/converter.cpp new file mode 100644 index 0000000..59148c6 --- /dev/null +++ b/src/converter.cpp @@ -0,0 +1,44 @@ +#include "converter.hpp" +#include +#include +#include + +std::string build_ffmpeg_command(const Config& config) { + std::stringstream cmd; + cmd << "ffmpeg -i \"" << config.input_path << "\""; + + if (config.codec == "h264") { + cmd << " -c:v libx264"; + } else if (config.codec == "h265") { + cmd << " -c:v libx265"; + } else if (config.codec == "prores") { + cmd << " -c:v prores_ks"; + } + + cmd << " -preset " << config.quality; + cmd << " -crf " << config.crf; + cmd << " -c:a copy"; + + if (config.verbose) { + cmd << " -loglevel verbose"; + } + + cmd << " \"" << config.output_path << "\""; + + return cmd.str(); +} + +int execute_conversion(const std::string& command, bool verbose) { + if (verbose) { + std::cout << "Executing: " << command << std::endl; + } + + int result = std::system(command.c_str()); + + if (result != 0) { + std::cerr << "Error: ffmpeg command failed with exit code " << result << std::endl; + return 1; + } + + return 0; +} diff --git a/src/include/config.hpp b/src/include/config.hpp new file mode 100644 index 0000000..4c40382 --- /dev/null +++ b/src/include/config.hpp @@ -0,0 +1,15 @@ +#ifndef CONFIG_HPP +#define CONFIG_HPP + +#include + +struct Config { + std::string input_path; + std::string output_path; + std::string codec = "h264"; + std::string quality = "medium"; + int crf = 23; + bool verbose = false; +}; + +#endif // CONFIG_HPP diff --git a/src/include/converter.hpp b/src/include/converter.hpp new file mode 100644 index 0000000..cec4b83 --- /dev/null +++ b/src/include/converter.hpp @@ -0,0 +1,10 @@ +#ifndef CONVERTER_HPP +#define CONVERTER_HPP + +#include "config.hpp" +#include + +std::string build_ffmpeg_command(const Config& config); +int execute_conversion(const std::string& command, bool verbose); + +#endif // CONVERTER_HPP diff --git a/src/include/parser.hpp b/src/include/parser.hpp new file mode 100644 index 0000000..6a233d6 --- /dev/null +++ b/src/include/parser.hpp @@ -0,0 +1,9 @@ +#ifndef PARSER_HPP +#define PARSER_HPP + +#include "config.hpp" + +Config parse_arguments(int argc, char* argv[]); +void print_usage(const char* program_name); + +#endif // PARSER_HPP diff --git a/src/include/validator.hpp b/src/include/validator.hpp new file mode 100644 index 0000000..e557474 --- /dev/null +++ b/src/include/validator.hpp @@ -0,0 +1,9 @@ +#ifndef VALIDATOR_HPP +#define VALIDATOR_HPP + +#include "config.hpp" +#include + +bool validate_config(const Config& config, std::string& error); + +#endif // VALIDATOR_HPP diff --git a/src/main.cpp b/src/main.cpp index ad3144f..caed391 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,96 +1,29 @@ #include -#include -#include -#include -#include -#include - -class VideoConverter { -public: - void startConversion(const std::string &inputFile, const std::string &outputFile); - -private: - bool conversionInProgress = false; -}; - -void VideoConverter::startConversion(const std::string &inputFile, const std::string &outputFile) { - if (conversionInProgress) { - std::cerr << "Error: Conversion already in progress." << std::endl; - return; - } - - if (inputFile.empty()) { - std::cerr << "Error: Please provide an input file name." << std::endl; - return; - } - - // Determine the output path - std::filesystem::path outputPath; - if (outputFile.empty()) { - // If no output file is specified, create one in the current directory - outputPath = std::filesystem::current_path() / (std::filesystem::path(inputFile).stem().string() + ".mov"); - } else { - outputPath = std::filesystem::path(outputFile); - if (outputPath.extension() != ".mov") { - outputPath += ".mov"; - } - } - - conversionInProgress = true; - std::cout << "Starting conversion..." << std::endl; - - std::thread([this, inputFile, outputPath]() { - std::string command = "ffmpeg -i \"" + inputFile + "\" -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a alac \"" + outputPath.string() + "\" 2> /dev/null"; - int result = std::system(command.c_str()); - conversionInProgress = false; - - if (result == 0) { - std::cout << "Video conversion completed successfully." << std::endl; - } else { - std::cerr << "Error: Conversion failed with exit code " << result << std::endl; - } - }).detach(); -} - -void printHelp() { - std::cout << "Usage: davinci-convert /path/to/video [--output /path/to/output/folder]\n"; - std::cout << "Options:\n"; - std::cout << " /path/to/video Path to the input video file.\n"; - std::cout << " --output /path/to/output/folder Path to the output video file (optional).\n"; - std::cout << " --help Show this help message.\n"; -} - -int main(int argc, char *argv[]) { - VideoConverter converter; - std::string inputFile, outputFile; +#include "config.hpp" +#include "parser.hpp" +#include "validator.hpp" +#include "converter.hpp" +int main(int argc, char* argv[]) { if (argc < 2) { - printHelp(); + print_usage(argv[0]); return 1; } - - // The first argument is the input file - inputFile = argv[1]; - - // Parse the remaining arguments - for (int i = 2; i < argc; i++) { - if (std::strcmp(argv[i], "--output") == 0 && i + 1 < argc) { - outputFile = argv[++i]; - } else if (std::strcmp(argv[i], "--help") == 0) { - printHelp(); - return 0; - } else { - std::cerr << "Unknown option: " << argv[i] << std::endl; - printHelp(); - return 1; - } + + Config config = parse_arguments(argc, argv); + + std::string error; + if (!validate_config(config, error)) { + std::cerr << "Error: " << error << std::endl; + return 1; } - - converter.startConversion(inputFile, outputFile); - - // Keep the application running until the conversion is done - std::cout << "Press Enter to exit..." << std::endl; - std::cin.get(); - + + std::string command = build_ffmpeg_command(config); + + if (execute_conversion(command, config.verbose) != 0) { + return 1; + } + + std::cout << "Conversion completed successfully!" << std::endl; return 0; } diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..e1be060 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,62 @@ +#include "parser.hpp" +#include +#include +#include + +void print_usage(const char* program_name) { + std::cout << "Usage: " << program_name << " [options] \n" + << "Options:\n" + << " -c, --codec Video codec (h264, h265, prores) [default: h264]\n" + << " -q, --quality Quality preset (fast, medium, slow) [default: medium]\n" + << " -r, --crf CRF value 0-51 [default: 23]\n" + << " -v, --verbose Enable verbose output\n" + << " -h, --help Show this help message\n"; +} + +Config parse_arguments(int argc, char* argv[]) { + Config config; + std::vector positional_args; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + + if (arg == "-h" || arg == "--help") { + print_usage(argv[0]); + std::exit(0); + } else if (arg == "-c" || arg == "--codec") { + if (i + 1 < argc) { + config.codec = argv[++i]; + } + } else if (arg == "-q" || arg == "--quality") { + if (i + 1 < argc) { + config.quality = argv[++i]; + } + } else if (arg == "-r" || arg == "--crf") { + if (i + 1 < argc) { + try { + config.crf = std::stoi(argv[++i]); + } catch (const std::exception& e) { + std::cerr << "Error: Invalid CRF value" << std::endl; + std::exit(1); + } + } + } else if (arg == "-v" || arg == "--verbose") { + config.verbose = true; + } else if (arg[0] != '-') { + positional_args.push_back(arg); + } else { + std::cerr << "Error: Unknown option: " << arg << std::endl; + print_usage(argv[0]); + std::exit(1); + } + } + + if (positional_args.size() >= 1) { + config.input_path = positional_args[0]; + } + if (positional_args.size() >= 2) { + config.output_path = positional_args[1]; + } + + return config; +} diff --git a/src/validator.cpp b/src/validator.cpp new file mode 100644 index 0000000..871908a --- /dev/null +++ b/src/validator.cpp @@ -0,0 +1,42 @@ +#include "validator.hpp" +#include +#include +#include + +namespace fs = std::filesystem; + +bool validate_config(const Config& config, std::string& error) { + if (config.input_path.empty()) { + error = "Input path is required"; + return false; + } + + if (config.output_path.empty()) { + error = "Output path is required"; + return false; + } + + if (!fs::exists(config.input_path)) { + error = "Input file does not exist: " + config.input_path; + return false; + } + + const std::vector valid_codecs = {"h264", "h265", "prores"}; + if (std::find(valid_codecs.begin(), valid_codecs.end(), config.codec) == valid_codecs.end()) { + error = "Invalid codec: " + config.codec + ". Valid options: h264, h265, prores"; + return false; + } + + const std::vector valid_qualities = {"fast", "medium", "slow"}; + if (std::find(valid_qualities.begin(), valid_qualities.end(), config.quality) == valid_qualities.end()) { + error = "Invalid quality preset: " + config.quality + ". Valid options: fast, medium, slow"; + return false; + } + + if (config.crf < 0 || config.crf > 51) { + error = "CRF must be between 0 and 51"; + return false; + } + + return true; +}