Skip to content

Instantly share code, notes, and snippets.

@7etsuo
Created September 27, 2024 21:05
Show Gist options
  • Save 7etsuo/bfe1c62cb12447fa3b7c84be288337c5 to your computer and use it in GitHub Desktop.
Save 7etsuo/bfe1c62cb12447fa3b7c84be288337c5 to your computer and use it in GitHub Desktop.
// all ytdl files and code.
/* argument_parsing.c */
#include "argument_parsing.h"
#include "help_display.h"
#include "directory_management.h"
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static char *
my_strdup (const char *s)
{
assert (s != NULL);
size_t len = strlen (s) + 1;
char *new_str = malloc (len);
if (new_str == NULL)
return NULL;
return memcpy (new_str, s, len);
}
int
parse_arguments (int argc, char *argv[], Config *config)
{
struct option long_options[] = {{"help", no_argument, 0, 'h'},
{"output", required_argument, 0, 'o'},
{0, 0, 0, 0}};
int opt;
while ((opt = getopt_long (argc, argv, "ho:", long_options, NULL)) != -1)
{
switch (opt)
{
case 'h':
display_help ();
exit (EXIT_SUCCESS);
case 'o':
config->output_path = my_strdup (optarg);
if (config->output_path == NULL)
{
fprintf (stderr, "Error: Memory allocation failed for output path\n");
return EXIT_FAILURE;
}
break;
}
}
if (optind < argc)
{
config->url = argv[optind];
}
else
{
fprintf (stderr, "Error: URL is required\n");
display_help ();
return EXIT_FAILURE;
}
return 0;
}
int
initialize_output_path (Config *config)
{
if (config->output_path == NULL)
{
char *allocated_path = get_current_working_directory ();
if (allocated_path == NULL)
{
return EXIT_FAILURE;
}
config->output_path = allocated_path;
}
else
{
if (create_directory_if_not_exists (config->output_path) == -1)
{
return EXIT_FAILURE;
}
}
return 0;
}
/* command_execution.c */
#include "command_execution.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
pid_t
fork_process (void)
{
pid_t pid = fork ();
if (pid == -1)
{
perror ("fork");
}
return pid;
}
int
setup_pipes (int pipefd[2])
{
if (pipe (pipefd) == -1)
{
perror ("pipe");
return -1;
}
return 0;
}
int
redirect_stdout (int pipefd)
{
if (dup2 (pipefd, STDOUT_FILENO) == -1)
{
perror ("dup2");
return -1;
}
return 0;
}
char *
read_from_pipe (int pipefd)
{
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
size_t output_size = 0;
char *output = malloc (1);
if (output == NULL)
{
perror ("malloc");
return NULL;
}
output[0] = '\0';
while ((bytes_read = read (pipefd, buffer, BUFFER_SIZE - 1)) > 0)
{
buffer[bytes_read] = '\0';
size_t new_size = output_size + bytes_read + 1;
char *new_output = realloc (output, new_size);
if (new_output == NULL)
{
perror ("realloc");
free (output);
return NULL;
}
output = new_output;
memcpy (output + output_size, buffer, bytes_read + 1);
output_size += bytes_read;
}
if (bytes_read == -1)
{
perror ("read");
free (output);
return NULL;
}
return output;
}
char *
execute_command_with_output (const char *command, char *const argv[])
{
int pipefd[2];
if (setup_pipes (pipefd) == -1)
{
return NULL;
}
pid_t pid = fork_process ();
if (pid == -1)
{
close (pipefd[READ_END]);
close (pipefd[WRITE_END]);
return NULL;
}
else if (pid == 0)
{
close (pipefd[READ_END]);
if (redirect_stdout (pipefd[WRITE_END]) == -1)
{
close (pipefd[1]);
exit (EXIT_FAILURE);
}
close (pipefd[WRITE_END]);
execvp (command, argv);
perror ("execvp");
exit (EXIT_FAILURE);
}
else
{
close (pipefd[WRITE_END]);
char *output = read_from_pipe (pipefd[0]);
close (pipefd[READ_END]);
int status;
if (waitpid (pid, &status, 0) == -1)
{
perror ("waitpid");
free (output);
return NULL;
}
if (WIFEXITED (status))
{
int exit_status = WEXITSTATUS (status);
if (exit_status != 0)
{
fprintf (stderr, "Error: Command exited with status %d\n", exit_status);
free (output);
return NULL;
}
}
else
{
fprintf (stderr, "Error: Child process did not exit normally\n");
free (output);
return NULL;
}
return output;
}
}
int
execute_command_without_output (const char *command, char *const argv[])
{
pid_t pid = fork_process ();
if (pid == -1)
{
return -1;
}
else if (pid == 0)
{
execvp (command, argv);
perror ("execvp");
exit (EXIT_FAILURE);
}
else
{
int status;
if (waitpid (pid, &status, 0) == -1)
{
perror ("waitpid");
return -1;
}
if (WIFEXITED (status))
{
return WEXITSTATUS (status);
}
else
{
fprintf (stderr, "Error: Child process did not exit normally\n");
return -1;
}
}
}
/* directory_management.c */
#include "directory_management.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
int
create_directory_if_not_exists (const char *path)
{
struct stat st = {0};
if (stat (path, &st) == -1)
{
if (mkdir (path, DIRECTORY_PERMISSIONS) == -1)
{
fprintf (stderr, "Error: Failed to create directory %s: %s\n", path,
strerror (errno));
return -1;
}
}
return 0;
}
char *
get_current_working_directory (void)
{
char *current_dir = getcwd (NULL, 0);
if (current_dir == NULL)
{
perror ("getcwd");
return NULL;
}
return current_dir;
}
/* download_helpers.c */
#include "download_helpers.h"
#include "command_execution.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char **
build_download_command_args (const char *format_code, const char *output_path,
const char *url)
{
// Calculate the number of arguments
// "yt-dlp", "-f", format_code, "-o", output_template, url, NULL
int arg_count = 5;
if (format_code && strlen (format_code) > 0)
{
arg_count = 6;
}
char **args = malloc (sizeof (char *) * (arg_count + 1));
if (args == NULL)
{
fprintf (stderr, "Error: Memory allocation failed for command arguments\n");
return NULL;
}
args[0] = "yt-dlp";
int idx = 1;
if (format_code && strlen (format_code) > 0)
{
args[idx++] = "-f";
args[idx++] = (char *) format_code;
}
else
{
args[idx++] = "-f";
args[idx++] = "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best";
}
args[idx++] = "-o";
char *output_template = malloc (MAX_PATH_LENGTH);
if (output_template == NULL)
{
fprintf (stderr, "Error: Memory allocation failed for output template\n");
free (args);
return NULL;
}
snprintf (output_template, MAX_PATH_LENGTH, "%s/%%(title)s.%%(ext)s",
output_path);
args[idx++] = output_template;
args[idx++] = (char *) url;
args[idx] = NULL;
return args;
}
void
free_command_args (char **args)
{
if (args == NULL)
return;
// The output_template is at index 4 if format_code is provided, else index 3
if (args[4] != NULL)
{
free (args[4]);
}
free (args);
}
int
download_video (const Config *config, const char *format_code)
{
printf ("Downloading...\n");
char **args = build_download_command_args (format_code, config->output_path,
config->url);
if (args == NULL)
{
fprintf (stderr, "Error: Failed to build download command arguments\n");
return -1;
}
int result = execute_command_without_output ("yt-dlp", args);
if (result == 0)
{
printf ("Download complete! Saved to: %s\n", config->output_path);
}
else
{
fprintf (stderr, "Error: Download failed\n");
}
free_command_args (args);
return result;
}
/* format_parsing.c */
#include "format_parsing.h"
#include <stdio.h>
#include <jansson.h>
json_t *
parse_formats (const char *json_str)
{
json_error_t error;
json_t *root = json_loads (json_str, 0, &error);
if (!root)
{
fprintf (stderr, "Error: JSON parsing failed on line %d: %s\n", error.line,
error.text);
return NULL;
}
json_t *formats = json_object_get (root, "formats");
if (!json_is_array (formats))
{
fprintf (stderr, "Error: 'formats' is not an array in JSON data\n");
json_decref (root);
return NULL;
}
// Increment reference count to keep formats alive after root is decref'd
json_incref (formats);
json_decref (root);
return formats;
}
void
display_formats (const json_t *formats)
{
printf ("Available formats:\n");
size_t index;
json_t *format;
json_array_foreach (formats, index, format)
{
const char *format_id
= json_string_value (json_object_get (format, "format_id"));
const char *resolution
= json_string_value (json_object_get (format, "resolution"));
const char *ext = json_string_value (json_object_get (format, "ext"));
json_int_t filesize
= json_integer_value (json_object_get (format, "filesize"));
printf ("Format code: %-5s Resolution: %-10s Extension: %-4s Filesize: "
"%8lld bytes\n",
format_id ? format_id : "N/A", resolution ? resolution : "N/A",
ext ? ext : "N/A", (long long) filesize);
}
}
/* help_display.c */
#include "help_display.h"
#include "directory_management.h"
#include <stdio.h>
#include <stdlib.h>
void
display_help (void)
{
printf ("Usage: ytdl [OPTION]... URL\n");
printf ("Download videos from YouTube using yt-dlp\n\n");
printf ("Options:\n");
printf (" -h, --help\t\t\tDisplay this help message\n");
printf (" -o, --output PATH\t\tSpecify the output directory (default: "
"current directory)\n");
}
void
cleanup (Config *config)
{
if (config->output_path != NULL
&& config->output_path != get_current_working_directory ())
{
free ((void *) config->output_path);
}
}
/* main.c */
/**
* ytdl.c
*
* - Tetsuo - c/asm
* - https://www.github.com/7etsuo
* - https://www.x.com/7etsuo
* - https://www.discord.gg/c-asm
*
* ---------------------------------------------------------------------------
*
* The author disclaims copyright to this source code.
* In place of a legal notice, here is a blessing:
*
* - May you do good and not evil.
* - May you find forgiveness for yourself and forgive others.
* - May you share freely, never taking more than you give.
*
* ---------------------------------------------------------------------------
*
* Description:
* ytdl is a command-line utility for downloading YouTube videos using
* yt-dlp. It allows users to specify the output directory& select video
* formats. The program fetches video information in JSON format, parses
* available formats, and enables users to choose their preferred format
* for downloading.
*
* Features:
* - Fetch video metadata using yt-dlp in JSON format.
* - Parse and display available video formats with details such as format
* code, resolution, extension, and filesize.
* - Prompt users to select a desired video format or default to the best
* quality available.
* - Download the selected video format to a specified or current directory.
*
* Usage:
* ./ytdl [OPTIONS] URL
*
* Options:
* -h, --help Display this help message and exit.
* -o, --output PATH Specify the output directory for downloaded videos.
* Defaults to the current working directory if not
* provided.
*
* Examples:
* - Display help message:
* ./ytdl --help
*
* - Download a video to the current directory:
* ./ytdl https://www.youtube.com/watch?v=example
*
* - Download a video to a specified directory:
* ./ytdl -o /path/to/download https://www.youtube.com/watch?v=example
*
* Dependencies:
* - yt-dlp: Ensure that yt-dlp is installed https://github.com/yt-dlp/yt-dlp.
* - jansson: A C library for encoding, decoding, and manipulating JSON data.
* Installation:
* On Debian/Ubuntu:
* sudo apt-get install libjansson-dev
* On macOS (using Homebrew):
* brew install jansson
*
* Compilation:
* To compile the program: make all
*
* https://www.x.com/7etsuo
* ---------------------------------------------------------------------------
*/
#include "argument_parsing.h"
#include "command_execution.h"
#include "directory_management.h"
#include "download_helpers.h"
#include "format_parsing.h"
#include "help_display.h"
#include "user_interaction.h"
#include "video_info.h"
#include "ytdl.h"
#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char *argv[])
{
Config config = {0};
int init_result = 0;
init_result = parse_arguments (argc, argv, &config);
if (init_result != 0)
{
cleanup (&config);
return init_result;
}
init_result = initialize_output_path (&config);
if (init_result != 0)
{
cleanup (&config);
return init_result;
}
printf ("URL: %s\n", config.url);
printf ("Output path: %s\n", config.output_path);
char *json_str = get_video_info (config.url);
if (json_str == NULL)
{
fprintf (stderr, "Error: Failed to get video information\n");
cleanup (&config);
return EXIT_FAILURE;
}
json_t *formats = parse_formats (json_str);
free (json_str);
if (formats == NULL)
{
cleanup (&config);
return EXIT_FAILURE;
}
display_formats (formats);
json_decref (formats);
char *format_code = prompt_for_format ();
if (format_code == NULL)
{
cleanup (&config);
return EXIT_FAILURE;
}
if (download_video (&config, format_code) != 0)
{
fprintf (stderr, "Error: Download failed\n");
free (format_code);
cleanup (&config);
return EXIT_FAILURE;
}
free (format_code);
cleanup (&config);
return EXIT_SUCCESS;
}
/* m.c */
.text:00575F6E byte_575F6E db 0, 1, 19h, 19h
.text:00575F6E ; DATA XREF: _FXCLI_OraBR_Exec_Command+
.text:00575F6F db 19h, 19h, 19h, 19h ; indirect table for switch statement
.text:00575F6F db 2, 3, 4, 5
.text:00575F6F db 6, 7, 8, 9
.text:00575F6F db 0Ah, 0Bh, 19h, 19h
.text:00575F6F db 19h, 19h, 19h, 19h
.text:00575F6F db 0Ch, 0Dh, 0Eh, 0Fh
.text:00575F6F db [10h], 11h, 12h, 13h
.text:00575F6F db 14h, 19h, 19h, 19h
.text:00575F6F db 19h, 19h, 19h, 19h
.text:00575F6F db 19h, 19h, 19h, 19h
.text:00575F6F db 19h, 19h, 19h, 19h
.text:00575F6F db 19h, 19h, 19h, 18h
.text:00575F6F db 15h, 16h, 17h, 18h
/* user_interaction.c */
#include "user_interaction.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *
prompt_for_format (void)
{
char *format_code = malloc (FORMAT_CODE_LENGTH * sizeof (char));
if (format_code == NULL)
{
fprintf (stderr, "Error: Memory allocation failed for format code\n");
return NULL;
}
printf ("Enter the format code (leave blank for best quality): ");
if (fgets (format_code, FORMAT_CODE_LENGTH, stdin) == NULL)
{
fprintf (stderr, "Error: Failed to read format code\n");
free (format_code);
return NULL;
}
format_code[strcspn (format_code, "\n")] = 0;
return format_code;
}
/* video_info.c */
#include "video_info.h"
#include "command_execution.h"
#include <stdio.h>
char *
get_video_info (const char *url)
{
printf ("Fetching video info...\n");
char *const argv[] = {"yt-dlp", "-j", (char *) url, NULL};
return execute_command_with_output ("yt-dlp", argv);
}
/* argument_parsing.h */
#ifndef ARGUMENT_PARSING_H
#define ARGUMENT_PARSING_H
#include "ytdl.h"
// clang-format off
int parse_arguments(int argc, char *argv[], Config *config);
int initialize_output_path(Config *config);
// clang-format on
#endif
/* command_execution.h */
#ifndef COMMAND_EXECUTION_H
#define COMMAND_EXECUTION_H
#include "ytdl.h"
// clang-format off
pid_t fork_process(void);
int setup_pipes(int pipefd[2]);
int redirect_stdout(int pipefd);
char *read_from_pipe(int pipefd);
char *execute_command_with_output(const char *command, char *const argv[]);
int execute_command_without_output(const char *command, char *const argv[]);
// clang-format on
#endif
/* directory_management.h */
#ifndef DIRECTORY_MANAGEMENT_H
#define DIRECTORY_MANAGEMENT_H
#include "ytdl.h"
// clang-format off
int create_directory_if_not_exists(const char *path);
char *get_current_working_directory(void);
// clang-format on
#endif
/* download_helpers.h */
#ifndef DOWNLOAD_HELPERS_H
#define DOWNLOAD_HELPERS_H
#include "ytdl.h"
// clang-format off
char **build_download_command_args(const char *format_code, const char *output_path, const char *url);
void free_command_args(char **args);
int download_video(const Config *config, const char *format_code);
// clang-format on
#endif
/* format_parsing.h */
#ifndef FORMAT_PARSING_H
#define FORMAT_PARSING_H
#include "ytdl.h"
// clang-format off
json_t *parse_formats(const char *json_str);
void display_formats(const json_t *formats);
// clang-format on
#endif
/* help_display.h */
#ifndef HELP_DISPLAY_H
#define HELP_DISPLAY_H
#include "ytdl.h"
// clang-format off
void display_help(void);
void cleanup(Config *config);
// clang-format on
#endif
/* user_interaction.h */
#ifndef USER_INTERACTION_H
#define USER_INTERACTION_H
#include "ytdl.h"
// clang-format off
char *prompt_for_format(void);
// clang-format on
#endif
/* video_info.h */
#ifndef VIDEO_INFO_H
#define VIDEO_INFO_H
#include "ytdl.h"
// clang-format off
char *get_video_info(const char *url);
// clang-format on
#endif
/* ytdl.h */
#ifndef YTDL_H
#define YTDL_H
#include <errno.h>
#include <getopt.h>
#include <jansson.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_URL_LENGTH 2048
#define MAX_PATH_LENGTH 4096
#define BUFFER_SIZE 1024
#define FORMAT_CODE_LENGTH 20
#define DIRECTORY_PERMISSIONS (S_IRWXU)
enum
{
READ_END,
WRITE_END
};
typedef struct
{
const char *url;
char *output_path;
} Config;
#endif
/* build.sh */
#!/bin/bash
# build.sh
# This script installs all necessary dependencies for the ytdl program on Debian-based systems.
set -e
function echo_info {
echo -e "\e[34m[INFO]\e[0m $1"
}
function echo_error {
echo -e "\e[31m[ERROR]\e[0m $1"
}
function command_exists {
command -v "$1" >/dev/null 2>&1
}
if [[ "$EUID" -ne 0 ]]; then
echo_error "Please run this script with sudo or as root."
exit 1
fi
echo_info "Updating package list..."
apt-get update -y
echo_info "Installing build-essential..."
apt-get install -y build-essential
echo_info "Installing libjansson-dev..."
apt-get install -y libjansson-dev
echo_info "Installing pkg-config..."
apt-get install -y pkg-config
echo_info "Installing libssl-dev..."
apt-get install -y libssl-dev
echo_info "Installing libcurl4-openssl-dev..."
apt-get install -y libcurl4-openssl-dev
echo_info "Installing python3-pip..."
apt-get install -y python3-pip
echo_info "All system dependencies installed successfully."
echo
echo "--------------------------------------------------"
echo "Next Steps:"
echo "1. Install yt-dlp using pip3:"
echo " pip3 install yt-dlp"
echo
echo "2. Compile the ytdl program:"
echo " make all"
echo
echo "3. Run the program:"
echo " ./ytdl [OPTIONS] URL"
echo "--------------------------------------------------"
echo
/* log.sh */
#!/bin/bash
rm -f log.c
for file in *.c *.h *.sh; do
if [ -f "$file" ]; then
echo "/* $file */" >> log.c
cat "$file" >> log.c
echo "" >> log.c
fi
done
echo "All .c, .h, and .sh files have been concatenated into log.c"
/* Makefile */
CC = gcc
CFLAGS = -Wall -Wextra -pedantic -std=c11
LDFLAGS = -ljansson
SRCS = main.c command_execution.c video_info.c format_parsing.c user_interaction.c directory_management.c download_helpers.c argument_parsing.c help_display.c
OBJS = $(SRCS:.c=.o)
TARGET = ytdl
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f $(OBJS) $(TARGET)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment