Skip to content
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.22)

project(ffmpeg-version-manager
LANGUAGES C CXX
VERSION 0.1.6
VERSION 0.1.7
)

set(CMAKE_CXX_STANDARD 17)
Expand Down Expand Up @@ -129,6 +129,10 @@ add_executable(ffmpeg-version-manager
src/ui_elements.cc
src/cli.cc
src/tui.cc
src/curl_tools.cc
src/env_settings.linux.cc
src/env_settings.windows.cc
src/compress.cc
)

target_include_directories(ffmpeg-version-manager
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# FFmpeg Version Manager

Note: I made this code in a rush, this means that the code is hard to read.

A command-line tool to manage multiple versions of FFmpeg, enabling seamless switching between different versions for development and testing.

## Features
Expand All @@ -11,6 +9,11 @@ A command-line tool to manage multiple versions of FFmpeg, enabling seamless swi
- Build from source for custom versions
- Cross-platform support (Linux, Windows)

*Note: On Linux only works if you are using bash & ~/.bashrc, help me to improve this to more envs*

*Note: In runtime requires $HOME for bash or %USERPROFILE% for Windows (it is usually always available; this is only to advise if you are using scripts or any other env)*


## Installation

### Pre-built Binaries
Expand Down
2 changes: 1 addition & 1 deletion include/cli.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

int run_cli(int argc, char *argv[]);

#endif // CLI_H
#endif // CLI_H
13 changes: 13 additions & 0 deletions include/compress.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <string>
#include <filesystem>

#ifndef COMPRESS_H
#define COMPRESS_H

int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir, void* callback_data, void (*callback)(void*, size_t, size_t));
inline int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir)
{
return extract_from_memory_to_folder(filedata, destination_dir, NULL, NULL);
}

#endif // COMPRESS_H
16 changes: 16 additions & 0 deletions include/curl_tools.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <iostream>
#include <curl/curl.h>

#ifndef CURL_TOOLS_H
#define CURL_TOOLS_H

bool is_curl_cert_error(CURLcode res);
CURL* init_curl_request(std::string url);
void set_unsecure_curl(CURL* curl);
void set_progress_callback_curl(CURL* curl, void* callback_data, int (*callback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t));
void destroy_curl(CURL* curl);
CURLcode launch_curl_request(CURL* curl);
CURLcode launch_curl_request_result(CURL* curl, std::string* result);
int quick_curl_request(std::string url, std::string* result);

#endif // CURL_TOOLS_H
6 changes: 5 additions & 1 deletion include/environment.hh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <filesystem>
#include <string>

#ifndef ENVIRONMENT_H
#define ENVIRONMENT_H
Expand All @@ -7,4 +8,7 @@ int setup_env(std::string version);
int remove_env();
std::filesystem::path get_ffmpeg_vm_dir();

#endif // ENVIRONMENT_H
int os_setup_env(std::string version, std::filesystem::path ffmpeg_vm_dir, const char* home);
int os_remove_env(std::filesystem::path ffmpeg_vm_dir, const char* home);

#endif // ENVIRONMENT_H
16 changes: 14 additions & 2 deletions include/request.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,20 @@ typedef struct FFMPEG_VERSION_S {
std::string url;
} FFMPEG_VERSION;

inline std::ostream& operator<<(std::ostream& ostr, const FFMPEG_VERSION& ver)
{
ostr << ver.version << ": " << ver.url;

return ostr;
}

inline bool operator<(const FFMPEG_VERSION& a, const FFMPEG_VERSION& b)
{
return a.version < b.version;
}

std::vector<FFMPEG_VERSION> get_ffmpeg_versions();
std::string download_file(std::string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen);
int extract(const std::string &filedata, const std::filesystem::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen);
std::string display_download_file(std::string url, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen);
int display_extract(const std::string &filedata, const std::filesystem::path &destination_dir, ftxui::Element* display_slider, ftxui::ScreenInteractive* screen);

#endif // REQUEST_H
2 changes: 1 addition & 1 deletion include/tui.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

int run_tui();

#endif // TUI_H
#endif // TUI_H
2 changes: 1 addition & 1 deletion include/ui_elements.hh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ void display_alert(ftxui::Element content, const std::chrono::seconds t);
std::string generate_slider(float percent);
std::string center_text(std::string str);

#endif // UI_ELEMENTS_H
#endif // UI_ELEMENTS_H
21 changes: 18 additions & 3 deletions src/cli.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "environment.hh"
#include "request.hh"
#include "curl_tools.hh"
#include "compress.hh"

using namespace std;

Expand All @@ -28,7 +30,7 @@ OPTIONS default_options()

void print_help()
{
cout << "ffmpeg-version-manager v0.1.6" << endl;
cout << "ffmpeg-version-manager v0.1.7" << endl;
cout << "Arguments:" << endl;
cout << " -h/--help -> Print this screen." << endl;
cout << " -u/--uninstall -> Uninstall the current ffmpeg-vm installed and the current env." << endl;
Expand Down Expand Up @@ -155,10 +157,23 @@ int run_cli(int argc, char *argv[])
filesystem::path downloaddir = get_ffmpeg_vm_dir();

cout << "Downloading ffmpeg " + version.version + "..." << endl;
const string fdata = download_file(version.url, NULL, NULL);
string fdata;
int status = quick_curl_request(version.url, &fdata);

if(status != 0)
{
cout << "Error on download the file" << endl;
return 2;
}

cout << "Extracting files..." << endl;
extract(fdata, downloaddir, NULL, NULL);
status = extract_from_memory_to_folder(fdata, downloaddir);

if(status != 0)
{
cout << "Error on extracting the file" << endl;
return 3;
}

#ifdef _WIN32
cout << "Done! Please open a new terminal to load the custom env for ffmpeg-vm" << endl;
Expand Down
178 changes: 178 additions & 0 deletions src/compress.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include "compress.hh"

#include <iostream>
#include <fstream>

#include <archive.h>
#include <archive_entry.h>

using namespace std;
namespace fs = std::filesystem;

int extract_from_memory_to_folder(const std::string &filedata, const std::filesystem::path &destination_dir, void* callback_data, void (*callback)(void*, size_t, size_t))
{
struct archive *archiv;
struct archive_entry *entry;
int result;

archiv = archive_read_new();
archive_read_support_filter_all(archiv); // Support for gzip, bzip2, xz, etc.
archive_read_support_format_all(archiv); // Support for tar, zip, 7zip, etc.

// Load from memory
result = archive_read_open_memory(archiv, filedata.data(), filedata.size());

if (result != ARCHIVE_OK)
{
cerr << "Error opening archive from memory: " << archive_error_string(archiv) << endl;
archive_read_free(archiv);
return 1;
}

// Number of chars to remove at the start of the path
size_t ffmpeg_entry_path_lenght = 0;

size_t total_entries = 0;
size_t processed_entries = 0;

// Read all entry to know how many files are
while (archive_read_next_header(archiv, &entry) == ARCHIVE_OK) {
total_entries++;
archive_read_data_skip(archiv); // Saltar los datos para solo contar
}

// Restart the read
archive_read_free(archiv);
archiv = archive_read_new();
archive_read_support_filter_all(archiv);
archive_read_support_format_all(archiv);
result = archive_read_open_memory(archiv, filedata.data(), filedata.size());

if (result != ARCHIVE_OK)
{
cerr << "Error reopening archive from memory: " << archive_error_string(archiv) << endl;
archive_read_free(archiv);
return 1;
}

// Read every entry (file/dir) inside the compressed file
while (archive_read_next_header(archiv, &entry) == ARCHIVE_OK)
{
if (total_entries > 0 && callback != NULL)
{
processed_entries++;

callback(callback_data, processed_entries, total_entries);
}

fs::path entry_path = fs::path(archive_entry_pathname(entry));

if (ffmpeg_entry_path_lenght != 0)
{
string temp = entry_path.string();

temp.erase(0, ffmpeg_entry_path_lenght);

entry_path = fs::path(temp);
}

const fs::path full_dest_path = destination_dir / entry_path;

/*
The first entry is the root folder
because we want all in ffmpeg-vm/... and not in ffmpeg-vm/something/..., we check this path to remove
*/
if (ffmpeg_entry_path_lenght == 0 && entry_path.string().rfind("ffmpeg") == 0) // Check if starts with ffmpeg
{
ffmpeg_entry_path_lenght = entry_path.string().length();
continue;
}

// Make sure that the folder exist
if (full_dest_path.has_parent_path())
{
fs::create_directories(full_dest_path.parent_path());
}

// If is only a directory, create the dir
if (archive_entry_filetype(entry) == AE_IFDIR)
{
fs::create_directories(full_dest_path);

continue;
}

#ifndef _WIN32
// If is only a symlink, create the symlink
if (archive_entry_filetype(entry) == AE_IFLNK)
{
const char* link_target_cstr = archive_entry_symlink(entry);

if (link_target_cstr) {
// fs::create_symlink can fail if the link exist
std::error_code ec;
fs::remove(full_dest_path, ec); // Remove to prevent errors
fs::create_symlink(fs::path(link_target_cstr), full_dest_path);
} else {
cerr << "Warning: could not read symlink target for " << full_dest_path << endl;
}

continue;
}
#endif

// if it's a file, write it to disk
ofstream outfile(full_dest_path, ios::binary);

if (!outfile)
{
cerr << "Error: Could not open file for writing: " << full_dest_path << endl;
archive_read_close(archiv);
archive_read_free(archiv);
return 2;
}

const void *buff; // Points to the data, no need to free, is only a pointer in memory
size_t size;
la_int64_t offset;

// Read the blocks and write it
while ((result = archive_read_data_block(archiv, &buff, &size, &offset)) == ARCHIVE_OK)
{
outfile.write(static_cast<const char *>(buff), size);
}

if (result != ARCHIVE_EOF)
{
cerr << "Error reading data block: " << archive_error_string(archiv) << endl;
archive_read_close(archiv);
archive_read_free(archiv);
return 3;
}

#ifndef _WIN32
// Restore permissions
if (archive_entry_filetype(entry) != AE_IFLNK) {
try {
fs::permissions(full_dest_path, static_cast<fs::perms>(archive_entry_perm(entry)), fs::perm_options::replace);
} catch (const exception& e) {
cerr << "Warning: could not set permissions for " << full_dest_path << ". " << e.what() << endl;
}
}
#endif
}

// Check if error on readling last header
result = archive_read_close(archiv);

if (result != ARCHIVE_OK)
{
cerr << "Error closing archive: " << archive_error_string(archiv) << endl;
archive_read_free(archiv);
return 4;
}

archive_read_free(archiv);

return 0;
}
Loading
Loading