feat: add devcontainer/nix support and refactor into modules

- Add .devcontainer/devcontainer.json for Podman development
- Add shell.nix for Nix users
- Refactor monolithic main.cpp into modular components:
  - parser: argument parsing
  - validator: configuration validation
  - converter: ffmpeg command building/execution
- Update Makefile with proper dependencies and test target
- Simplify build.sh to use make
- Update README with development environment docs
This commit is contained in:
tkmxqrdxddd
2026-03-16 17:35:35 +01:00
parent 64cde37777
commit 75285276e4
13 changed files with 418 additions and 299 deletions

View File

@@ -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"
]
}

View File

@@ -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++ CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -O2
INCLUDES = -Isrc/include
TARGET = davinci-video-converter
# Define any compile-time flags SRCS = src/main.cpp src/parser.cpp src/validator.cpp src/converter.cpp
CXXFLAGS = -std=c++17 -Wall -Wextra -g OBJS = $(SRCS:.cpp=.o)
# Define output directory all: $(TARGET)
OUTPUT = output
# Define source directory $(TARGET): $(OBJS)
SRC = src $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS)
# Define the main executable name src/%.o: src/%.cpp
MAIN = davinci-convert $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
# Define the C source files src/main.o: src/main.cpp src/include/config.hpp src/include/parser.hpp src/include/validator.hpp src/include/converter.hpp
SOURCES = $(wildcard $(SRC)/*.cpp) 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: clean:
@echo "Cleaning up..." rm -f $(TARGET) $(OBJS)
rm -f $(OUTPUTMAIN)
rm -f $(OBJECTS)
@echo Cleanup complete!
run: all install: $(TARGET)
@echo "Running executable: $(OUTPUTMAIN)" cp $(TARGET) /usr/local/bin/
./$(OUTPUTMAIN)
@echo Executing 'run: all' complete!
install: all uninstall:
install -Dm755 $(OUTPUTMAIN) /usr/bin/davinci-convert 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

190
README.md
View File

@@ -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 ## Features
- Convert `.mp4` videos to DNxHD format. - Convert videos using various codecs (H.264, H.265, ProRes)
- Simple command-line interface for user input. - 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) This project includes a DevContainer configuration for use with Podman. To use it:
- `make` (Build automation tool)
- `ffmpeg` (Multimedia framework for handling video, audio, and other multimedia files)
## 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: ### Nix Shell
```bash
git clone (https://github.com/tkmxqrdxddd/davinci-video-converter)
cd davinci-video-converter
```
2. Run the build script to install dependencies and build the project: For Nix users, enter the development shell:
```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:
```bash ```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. ## Building
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.
### 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 ```bash
make clean 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] <input> <output>
```
### Options
| Option | Description | Default |
|--------|-------------|---------|
| `-c, --codec <codec>` | Video codec (h264, h265, prores) | h264 |
| `-q, --quality <qual>` | Quality preset (fast, medium, slow) | medium |
| `-r, --crf <value>` | 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 ## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. MIT
## Acknowledgments
- This project uses `ffmpeg` for video conversion. For more information, visit the [FFmpeg website](https://ffmpeg.org/).

View File

@@ -1,96 +1,10 @@
#!/bin/bash #!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status set -e
set -u # Treat unset variables as an error when substituting
set -o pipefail # Prevent errors in a pipeline from being masked
# Function to install dependencies for Debian-based systems echo "Building DaVinci Video Converter..."
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
}
}
# Function to install dependencies for Red Hat-based systems make clean
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 make
# Check if the build was successful echo "Build complete! Binary: ./davinci-video-converter"
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'"

22
shell.nix Normal file
View File

@@ -0,0 +1,22 @@
{ pkgs ? import <nixpkgs> {} }:
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"
'';
}

44
src/converter.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include "converter.hpp"
#include <sstream>
#include <cstdlib>
#include <iostream>
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;
}

15
src/include/config.hpp Normal file
View File

@@ -0,0 +1,15 @@
#ifndef CONFIG_HPP
#define CONFIG_HPP
#include <string>
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

10
src/include/converter.hpp Normal file
View File

@@ -0,0 +1,10 @@
#ifndef CONVERTER_HPP
#define CONVERTER_HPP
#include "config.hpp"
#include <string>
std::string build_ffmpeg_command(const Config& config);
int execute_conversion(const std::string& command, bool verbose);
#endif // CONVERTER_HPP

9
src/include/parser.hpp Normal file
View File

@@ -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

View File

@@ -0,0 +1,9 @@
#ifndef VALIDATOR_HPP
#define VALIDATOR_HPP
#include "config.hpp"
#include <string>
bool validate_config(const Config& config, std::string& error);
#endif // VALIDATOR_HPP

View File

@@ -1,96 +1,29 @@
#include <iostream> #include <iostream>
#include <string> #include "config.hpp"
#include <thread> #include "parser.hpp"
#include <cstdlib> #include "validator.hpp"
#include <cstring> #include "converter.hpp"
#include <filesystem>
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;
int main(int argc, char* argv[]) {
if (argc < 2) { if (argc < 2) {
printHelp(); print_usage(argv[0]);
return 1; return 1;
} }
// The first argument is the input file Config config = parse_arguments(argc, argv);
inputFile = argv[1];
// Parse the remaining arguments std::string error;
for (int i = 2; i < argc; i++) { if (!validate_config(config, error)) {
if (std::strcmp(argv[i], "--output") == 0 && i + 1 < argc) { std::cerr << "Error: " << error << std::endl;
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; return 1;
} }
std::string command = build_ffmpeg_command(config);
if (execute_conversion(command, config.verbose) != 0) {
return 1;
} }
converter.startConversion(inputFile, outputFile); std::cout << "Conversion completed successfully!" << std::endl;
// Keep the application running until the conversion is done
std::cout << "Press Enter to exit..." << std::endl;
std::cin.get();
return 0; return 0;
} }

62
src/parser.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include "parser.hpp"
#include <iostream>
#include <vector>
#include <cstdlib>
void print_usage(const char* program_name) {
std::cout << "Usage: " << program_name << " [options] <input> <output>\n"
<< "Options:\n"
<< " -c, --codec <codec> Video codec (h264, h265, prores) [default: h264]\n"
<< " -q, --quality <qual> Quality preset (fast, medium, slow) [default: medium]\n"
<< " -r, --crf <value> 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<std::string> 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;
}

42
src/validator.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "validator.hpp"
#include <filesystem>
#include <vector>
#include <algorithm>
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<std::string> 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<std::string> 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;
}