Random splice algorithm test!

This commit is contained in:
Devon Crawford
2019-02-23 16:53:43 -05:00
parent 7aa3396985
commit 06dc9bf2c0
9 changed files with 649 additions and 8 deletions

View File

@@ -116,6 +116,11 @@ OBJS_BASE= VideoContext Clip ClipDecode OutputContext Timebase \
$(DBE)test-sequence-encode: $$(call EXE_OBJS,$$@,$(OBJS_BASE))
$(LINK_EXE)
OBJS_BASE=Sequence LinkedListAPI Clip Util VideoContext Timebase \
OutputContext SequenceEncode SequenceDecode ClipDecode
$(DBE)random-splice: $$(call EXE_OBJS,$$@,$(OBJS_BASE))
$(LINK_EXE)
# $(1) = name of exe
# $(2) = the list of basename object files that the executable needs to run, without .o
define EXE_OBJS

349
examples/random-splice.c Normal file
View File

@@ -0,0 +1,349 @@
#include "RandomSplice.h"
/**
* valgrind --leak-check=yes bin/examples/random-splice test-resources/sequence/out7.mov 30 48000 test-resources/sequence/ 300 80 40
*/
int main(int argc, char **argv) {
if(argc < 8) {
printf("usage: %s output_file fps sample_rate source_dir duration cut_len_avg cut_len_var\n", argv[0]);
printf("\nExplanation\n------------\n");
printf("output_file(string): output filename of encoded edit (ex. out.mov)\n");
printf("fps(int): frames per second to use in sequence. All frame parameters are based on this (ex. 30 for 30fps)\n");
printf("sample_rate(int): audio sample rate (ex. 48000 for 48kHz)\n");
printf("source_dir(string): directory of raw video files that will be used in the edit\n");
printf("duration(int): duration of output file (in frames - fps defined above)\n");
printf("cut_len_avg(int): average length of cuts (in frames)\n");
printf("cut_len_var(int): variability of average cuts used by the random number generator for max and min range\n");
return -1;
}
RandSpliceParams par;
strcpy(par.output_file, argv[1]);
par.fps = atoi(argv[2]);
par.sample_rate = atoi(argv[3]);
strcpy(par.source_dir, argv[4]);
par.duration = atoi(argv[5]);
par.cut_len_avg = atoi(argv[6]);
par.cut_len_var = atoi(argv[7]);
par.pick_frames_recur = 0;
int num_files, ret = 0;
char **files = get_filenames_in_dir(par.source_dir, &num_files);
srand(time(NULL));
Sequence orig_seq, new_seq;
init_sequence_cmp(&orig_seq, par.fps, par.sample_rate, &list_compare_clips_sequential);
init_sequence_cmp(&new_seq, par.fps, par.sample_rate, &list_compare_clips_sequential);
ret = add_files(&orig_seq, files, num_files);
if(ret < 0) {
fprintf(stderr, "failed to add files\n");
return ret;
}
char *str = print_sequence(&orig_seq);
printf("====== ORIG_SEQ =====\n%s\n", str);
free(str);
str = NULL;
ret = random_edit(&orig_seq, &new_seq, &par);
if(ret < 0) {
fprintf(stderr, "random_edit() error: Failed to finish edit\n");
goto end;
}
str = print_sequence(&new_seq);
printf("====== NEW_SEQ =====\n%s\n", str);
free(str);
str = NULL;
if(new_seq.clips.head != NULL) {
// output parameters
OutputParameters op;
VideoOutParams vp;
AudioOutParams ap;
Clip *clip1 = (Clip *) (new_seq.clips.head->data);
set_video_out_params(&vp, clip1->vid_ctx->video_codec_ctx);
vp.codec_id = AV_CODEC_ID_NONE;
vp.bit_rate = -1;
set_audio_out_params(&ap, clip1->vid_ctx->audio_codec_ctx);
if(set_output_params(&op, par.output_file, vp, ap) < 0) {
fprintf(stderr, "Failed to set output params\n");
goto end;
}
ret = write_sequence(&new_seq, &op);
if(ret < 0) {
fprintf(stderr, "Failed to write new sequence to output file[%s]\n", op.filename);
goto end;
}
}
end:
// print_str_arr(files, num_files);
free_str_arr(&files, num_files);
free_sequence(&orig_seq);
free_sequence(&new_seq);
}
/**
* Make a random edit. This function calls itself recursively to continue making
* cuts until the output sequence is our desired length
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return 0 on success (fully edited sequence), < 0 on fail
*/
int random_edit(Sequence *os, Sequence *ns, RandSpliceParams *par) {
if(par->cut_len_var > par->cut_len_avg) {
fprintf(stderr, "random_edit() error: cut_len_var[%d] must be less than cut_len_avg[%d]\n", par->cut_len_var, par->cut_len_avg);
return -1;
}
if(get_sequence_duration(ns) > par->duration) {
return 0;
}
int ret = random_cut(os, ns, par);
if(ret < 0) {
return ret;
}
return random_edit(os, ns, par);
}
/**
* Make a random cut from original sequence and place that clip into new sequence
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return >= 0 on success
*/
int random_cut(Sequence *os, Sequence *ns, RandSpliceParams *par) {
int s, e;
int ret = pick_frames(os, par, &s, &e);
if(ret < 0) {
fprintf(stderr, "random_cut() error: Failed to pick frames\n");
return ret;
}
return cut_remove_insert(os, ns, s, e);
}
/**
* Cut a clip out of original sequence and insert it into new sequence in sorted order
* @param os Original sequence
* @param ns New Sequence
* @param start_index start frame of cut in original sequence
* @param end_index end frame of cut in original sequence
* @return >= 0 on success
*/
int cut_remove_insert(Sequence *os, Sequence *ns, int start_index, int end_index) {
int cut_center_index = start_index + ((end_index - start_index) / 2);
int ret = cut_clip(os, start_index);
if(ret < 0) {
fprintf(stderr, "cut_remove_insert() error: Failed to cut clip at start index[%d]\n", start_index);
return ret;
}
ret = cut_clip(os, end_index);
if (ret < 0) {
fprintf(stderr, "cut_remove_insert() error: Failed to cut clip at end index[%d]\n", end_index);
return ret;
}
Clip *cut = NULL;
find_clip_at_index(os, cut_center_index, &cut);
if(!cut) {
fprintf(stderr, "cut_remove_insert() error: Failed to find clip at cut center index[%d]\n", cut_center_index);
return -1;
}
Clip *cut_copy = alloc_clip(cut->url);
if(cut_copy == NULL) {
fprintf(stderr, "cut_remove_insert() error: Failed to allocate cut_copy\n");
return -1;
}
ret = set_clip_bounds_pts(cut_copy, cut->orig_start_pts, cut->orig_end_pts);
if(ret < 0) {
free_clip(cut_copy);
free(cut_copy);
cut_copy = NULL;
fprintf(stderr, "cut_remove_insert() error: Failed to set clip bounds on cut copy\n");
return ret;
}
ret = sequence_insert_clip_sorted(ns, cut_copy);
if(ret < 0) {
free_clip(cut_copy);
free(cut_copy);
cut_copy = NULL;
fprintf(stderr, "cut_remove_insert() error: failed to add clip[%s] to new sequence\n", cut_copy->url);
return ret;
}
ret = sequence_ripple_delete_clip(os, cut);
if(ret < 0) {
free_clip(cut_copy);
free(cut_copy);
cut_copy = NULL;
fprintf(stderr, "cut_remove_insert() error: Failed to delete clip from original sequence\n");
return ret;
}
return 0;
}
/**
* Randomly pick start and end frames given user parameters
* @param seq Original sequence to pick frames
* @param par user parameters
* @param start_index output index of start frame for cut
* @param end_index output index of end frame for cut
* @return >= 0 on success
*/
int pick_frames(Sequence *seq, RandSpliceParams *par, int *start_index, int *end_index) {
if(par->pick_frames_recur > PICK_FRAMES_RECUR_LIMIT) {
fprintf(stderr, "pick_frames() error: par->pick_frames_recur[%d] > limit[%d]\nThis should never happen\n", par->pick_frames_recur, PICK_FRAMES_RECUR_LIMIT);
return -1;
}
int64_t seq_dur = get_sequence_duration(seq);
if(seq_dur <= 0) {
fprintf(stderr, "pick_frames() error: sequence duration[%ld] is invalid\n", seq_dur);
return -1;
}
int s = rand_range(0, seq_dur - par->cut_len_avg - 1);
int e_var = rand_range((-1)*(par->cut_len_var), par->cut_len_var);
int e = s + par->cut_len_avg + e_var;
if(e > seq_dur) {
e = seq_dur;
}
Clip *sc = NULL, *se = NULL;
find_clip_at_index(seq, s, &sc);
find_clip_at_index(seq, e, &se);
if(!sc || !se) {
fprintf(stderr, "pick_frames() error: clip does not exist at start[%d] or end[%d] index\n", s, e);
return -1;
}
// If start and end index do not lie on the same clip.. try again
if(compare_clips_sequential(sc, se) != 0) {
++(par->pick_frames_recur);
return pick_frames(seq, par, start_index, end_index);
}
*start_index = s;
*end_index = e;
par->pick_frames_recur = 0;
return 0;
}
/**
* Generate random number between range in the uniform distribution
* @param min low bound (inclusive)
* @param max high bound (inclusive)
* @return random number between min and max on a uniform distribution
*/
int rand_range(int min, int max) {
int diff = max-min;
return (int) (((double)(diff+1)/RAND_MAX) * rand() + min);
}
/**
* Add all files from string array into sequence!
* @param seq Sequence
* @param files string array of filenames (videos to be added)
* @param num_files number strings in files array
* @return >= 0 on success
*/
int add_files(Sequence *seq, char **files, int num_files) {
int ret;
for(int i = 0; i < num_files; i++) {
Clip *curr = alloc_clip(files[i]);
if(curr == NULL) {
printf("add_files() warning: failed to allocate clip[%s]\n", files[i]);
}
ret = sequence_insert_clip_sorted(seq, curr);
if(ret < 0) {
fprintf(stderr, "add_files() error: failed to add clip[%s] to sequence\n", files[i]);
return ret;
}
printf("added clip[%s] to original sequence\n", files[i]);
}
return 0;
}
/**
* print string array
* @param str string array
* @param rows number of strings
*/
void print_str_arr(char **str, int rows) {
for (int i = 0; i < rows; i++) {
printf("%s\n", str[i]);
}
}
/**
* Free array of strings
* @param str pointer to array of strings
* @param rows number of strings in array
*/
void free_str_arr(char ***str, int rows) {
char **s = *str;
if(s != NULL) {
for(int i = 0; i < rows; i++) {
free(s[i]);
s[i] = NULL;
}
free(*str);
*str = NULL;
}
}
/**
* Get all filenames in directory
* @param name of directory to search
* @param rows number of files
* @return array of strings (filenames)
*/
char **get_filenames_in_dir(char *dirname, int *rows) {
char buf[4096];
buf[0] = 0;
*rows = 0;
DIR *dir;
struct dirent *ent;
if ((dir = opendir (dirname)) != NULL) {
/* print all the files and directories within directory */
while ((ent = readdir (dir)) != NULL) {
strcat(buf, dirname);
strcat(buf, ent->d_name);
if(!is_regular_file(buf)) {
buf[0] = 0;
continue;
}
buf[0] = 0;
++(*rows);
}
} else {
perror ("");
return NULL;
}
rewinddir(dir);
char **str = malloc(sizeof(char *) * (*rows));
/* print all the files and directories within directory */
int i = 0;
while ((ent = readdir (dir)) != NULL) {
strcat(buf, dirname);
strcat(buf, ent->d_name);
if(!is_regular_file(buf)) {
buf[0] = 0;
continue;
}
str[i] = malloc(sizeof(char) * (strlen(buf) + 1));
strcpy(str[i++], buf);
buf[0] = 0;
}
closedir (dir);
return str;
}
/**
* Tests if path is file or directory
* @param path filename
* @return >= 0 on success
*/
int is_regular_file(const char *path) {
struct stat path_stat;
stat(path, &path_stat);
return S_ISREG(path_stat.st_mode);
}

View File

@@ -88,7 +88,9 @@ typedef struct Clip {
} Clip;
/**
* Allocate clip on heap and initialize to default values
* Allocate clip on heap, initialize default values and open the clip.
* It is important to open the clip when first created so we can set default
* values such as clip->orig_end_pts by reading file contents (length of video)
* @param url filename
* @return NULL on error, not NULL on success
*/

View File

@@ -111,7 +111,16 @@ as a pointer to the first and last element of the list.
**/
void insertSorted(List* list, void* toBeAdded);
/** Uses the comparison function pointer to place the element in the
* appropriate position in the list. Also return the node of inserted element
* should be used as the only insert function if a sorted list is required.
*@pre List exists and has memory allocated to it. Node to be added is valid.
*@post The node to be added will be placed immediately before or after the first occurrence of a related node
*@param list a pointer to the dummy head of the list containing function pointers for delete and compare, as well
as a pointer to the first and last element of the list.
*@param toBeAdded a pointer to data that is to be added to the linked list
**/
Node *insertSortedGetNode(List *list, void *toBeAdded);
/** Removes data from from the list, deletes the node and frees the memory,
* changes pointer values of surrounding nodes to maintain list structure.

137
include/RandomSplice.h Normal file
View File

@@ -0,0 +1,137 @@
#ifndef _RANDOM_SPLICE_
#define _RANDOM_SPLICE_
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "OutputContext.h"
#define PICK_FRAMES_RECUR_LIMIT 50
typedef struct RandSpliceParams {
/*************** SEQUENCE PARAMS ***************/
/*
output filename
*/
char output_file[1024];
/*
frames per second to use in sequence
*/
int fps;
/*
audio sample rate
*/
int sample_rate;
/*************** ALGORITHM PARAMS ***************/
/*
directory to fetch video files
*/
char source_dir[1024];
/*
duration of output video (in frames)
*/
int duration;
/*
average length of cuts (in frames)
*/
int cut_len_avg;
/*
variability of average cuts
*/
int cut_len_var;
/*************** INTERNAL ONLY ***************/
int pick_frames_recur;
} RandSpliceParams;
/**
* Make a random edit. This function calls itself recursively to continue making
* cuts until the output sequence is our desired length
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return 0 on success (fully edited sequence), < 0 on fail
*/
int random_edit(Sequence *os, Sequence *ns, RandSpliceParams *par);
/**
* Make a random cut from original sequence and place that clip into new sequence
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return >= 0 on success
*/
int random_cut(Sequence *os, Sequence *ns, RandSpliceParams *par);
/**
* Cut a clip out of original sequence and insert it into new sequence in sorted order
* @param os Original sequence
* @param ns New Sequence
* @param start_index start frame of cut in original sequence
* @param end_index end frame of cut in original sequence
* @return >= 0 on success
*/
int cut_remove_insert(Sequence *os, Sequence *ns, int start_index, int end_index);
/**
* Randomly pick start and end frames given user parameters
* @param seq Original sequence to pick frames
* @param par user parameters
* @param start_index output index of start frame for cut
* @param end_index output index of end frame for cut
* @return >= 0 on success
*/
int pick_frames(Sequence *seq, RandSpliceParams *par, int *start_index, int *end_index);
/**
* Generate random number between range in the uniform distribution
* @param min low bound (inclusive)
* @param max high bound (inclusive)
* @return random number between min and max on a uniform distribution
*/
int rand_range(int min, int max);
/**
* Add all files from string array into sequence!
* @param seq Sequence
* @param files string array of filenames (videos to be added)
* @param num_files number strings in files array
* @return >= 0 on success
*/
int add_files(Sequence *seq, char **files, int num_files);
/**
* print string array
* @param str string array
* @param rows number of strings
*/
void print_str_arr(char **str, int rows);
/**
* Free array of strings
* @param str pointer to array of strings
* @param rows number of strings in array
*/
void free_str_arr(char ***str, int rows);
/**
* Get all filenames in directory
* @param name of directory to search
* @param rows number of files
* @return array of strings (filenames)
*/
char **get_filenames_in_dir(char *dirname, int *rows);
/**
* Tests if path is file or directory
* @param path filename
* @return >= 0 on success
*/
int is_regular_file(const char *path);
#endif

View File

@@ -64,6 +64,16 @@ typedef struct Sequence {
*/
int init_sequence(Sequence *seq, double fps, int sample_rate);
/**
* Initialize a new sequence, list of clips along with a custom compare function (for insertSorted)
* @param seq Sequence to initialize
* @param fps frames per second
* @param sample_rate sample_rate of the audio stream
* @param compareFunc custom compare function used in sorting and searching
* @return >= 0 on success
*/
int init_sequence_cmp(Sequence *seq, double fps, int sample_rate, int (*compareFunc)(const void* first,const void* second));
/**
* Get duration of sequence in frames (defined by fps)
* @param seq Sequence
@@ -103,6 +113,25 @@ void sequence_add_clip_pts(Sequence *seq, Clip *clip, int64_t start_pts);
*/
void sequence_append_clip(Sequence *seq, Clip *clip);
/**
* Insert clip sorted by:
* 1. Date & time of file
* 2. clip->orig_start_pts
* This function will generate the sequence pts for the clip (clip->start_pts and clip->end_pts)
* @param seq Sequence
* @param clip Clip to insert
* @return >= 0 on success
*/
int sequence_insert_clip_sorted(Sequence *seq, Clip *clip);
/**
* Shift clips sequence pts to after the current node (insert clip function)
* @param seq Sequence
* @param curr_node current clip which should shift all following nodes
* @return >= 0 on success
*/
int shift_clips_after(Sequence *seq, Node *curr_node);
/**
* Delete a clip from a sequence and move all following clips forward
* @param seq Sequence

View File

@@ -9,13 +9,15 @@
#include "Clip.h"
/**
* Allocate clip on heap and initialize to default values
* Allocate clip on heap, initialize default values and open the clip.
* It is important to open the clip when first created so we can set default
* values such as clip->orig_end_pts by reading file contents (length of video)
* @param url filename
* @return NULL on error, not NULL on success
*/
Clip *alloc_clip(char *url) {
Clip *clip = malloc(sizeof(struct Clip));
if(clip == NULL || init_clip(clip, url) < 0) {
if(clip == NULL || init_clip(clip, url) < 0 || open_clip(clip) < 0) {
return NULL;
}
return clip;
@@ -410,7 +412,6 @@ int64_t compare_clips_sequential(Clip *f, Clip *s) {
return -2;
}
double diff = difftime(f->file_stats.st_mtime, s->file_stats.st_mtime);
printf("difftime: %f\n", diff);
if(diff < 0.01 && diff > -0.01) {
// if equal date & time
// compare start pts

View File

@@ -176,6 +176,52 @@ void insertSorted(List *list, void *toBeAdded){
return;
}
/** Uses the comparison function pointer to place the element in the
* appropriate position in the list. Also return the node of inserted element
* should be used as the only insert function if a sorted list is required.
*@pre List exists and has memory allocated to it. Node to be added is valid.
*@post The node to be added will be placed immediately before or after the first occurrence of a related node
*@param list a pointer to the dummy head of the list containing function pointers for delete and compare, as well
as a pointer to the first and last element of the list.
*@param toBeAdded a pointer to data that is to be added to the linked list
**/
Node *insertSortedGetNode(List *list, void *toBeAdded){
if (list == NULL || toBeAdded == NULL){
return NULL;
}
if (list->head == NULL){
insertBack(list, toBeAdded);
return list->head;
}
if (list->compare(toBeAdded, list->head->data) <= 0){
insertFront(list, toBeAdded);
return list->head;
}
if (list->compare(toBeAdded, list->tail->data) > 0){
insertBack(list, toBeAdded);
return list->tail;
}
Node* currNode = list->head;
while (currNode != NULL){
if (list->compare(toBeAdded, currNode->data) <= 0){
Node* newNode = initializeNode(toBeAdded);
newNode->next = currNode;
newNode->previous = currNode->previous;
currNode->previous->next = newNode;
currNode->previous = newNode;
(list->length)++;
return newNode;
}
currNode = currNode->next;
}
return NULL;
}
void* deleteDataFromList(List* list, void* toBeDeleted){
if (list == NULL || toBeDeleted == NULL){
return NULL;

View File

@@ -18,11 +18,23 @@
* @return >= 0 on success
*/
int init_sequence(Sequence *seq, double fps, int sample_rate) {
if(seq == NULL) {
fprintf(stderr, "sequence is NULL, cannot initialize");
return init_sequence_cmp(seq, fps, sample_rate, &list_compare_clips);
}
/**
* Initialize a new sequence, list of clips along with a custom compare function (for insertSorted)
* @param seq Sequence to initialize
* @param fps frames per second
* @param sample_rate sample_rate of the audio stream
* @param compareFunc custom compare function used in sorting and searching
* @return >= 0 on success
*/
int init_sequence_cmp(Sequence *seq, double fps, int sample_rate, int (*compareFunc)(const void* first,const void* second)) {
if(seq == NULL || compareFunc == NULL) {
fprintf(stderr, "init_sequence_cmp() error: params cannot be NULL\n");
return -1;
}
seq->clips = initializeList(&list_print_clip, &list_delete_clip, &list_compare_clips);
seq->clips = initializeList(&list_print_clip, &list_delete_clip, compareFunc);
seq->clips_iter = createIterator(seq->clips);
seq->video_time_base = (AVRational){1, fps * SEQ_VIDEO_FRAME_DURATION};
seq->audio_time_base = (AVRational){1, sample_rate};
@@ -115,6 +127,57 @@ void sequence_append_clip(Sequence *seq, Clip *clip) {
}
}
/**
* Insert clip sorted by:
* 1. Date & time of file
* 2. clip->orig_start_pts
* This function will generate the sequence pts for the clip (clip->start_pts and clip->end_pts)
* @param seq Sequence
* @param clip Clip to insert
* @return >= 0 on success
*/
int sequence_insert_clip_sorted(Sequence *seq, Clip *clip) {
if(seq == NULL || clip == NULL) {
fprintf(stderr, "sequence_insert_clip_sorted() error: parameters cannot be NULL\n");
return -1;
}
Node *node = insertSortedGetNode(&(seq->clips), clip);
if(node == NULL) {
fprintf(stderr, "sequence_insert_clip_sorted() error: could not insert clip in sorted order\n");
return -1;
}
seq->clips_iter.current = seq->clips.head;
if(node->previous == NULL) {
move_clip_pts(seq, clip, 0);
} else {
move_clip_pts(seq, clip, ((Clip *) (node->previous->data))->end_pts);
}
return shift_clips_after(seq, node);
}
/**
* Shift clips sequence pts to after the current node (insert clip function)
* @param seq Sequence
* @param curr_node current clip which should shift all following nodes
* @return >= 0 on success
*/
int shift_clips_after(Sequence *seq, Node *curr_node) {
if(seq == NULL || curr_node == NULL) {
fprintf(stderr, "shift_clips_after() error: parameters cannot be NULL\n");
return -1;
}
Clip *curr = (Clip *) (curr_node->data);
int64_t shift = curr->end_pts - curr->start_pts;
Node *node = curr_node->next;
while(node != NULL) {
Clip *next = (Clip *) (node->data);
next->start_pts += shift;
next->end_pts += shift;
node = node->next;
}
return 0;
}
/**
* Delete a clip from a sequence and move all following clips forward
* @param seq Sequence