view src/edl.c @ 8:0c98b46eaf73 v2.0

*: update API to 2.0, big changes all APIs now use pointers to an EDL object. it is up to the user to make sure that the pointer is valid. additionally, many things have been separated into new files to make it easier to digest
author Paper <paper@paper.us.eu.org>
date Sun, 03 Mar 2024 17:56:58 -0500
parents 7137fbac0b85
children 0cc2555db371
line wrap: on
line source

#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "edl.h"
#include "str.h"
#include "datatypes.h"
#include "util.h"

/* CRC32 hashes of EDL headers */
#define EDL_HEADER_ID				0x1541c503
#define EDL_HEADER_TRACK			0x4b211812
#define EDL_HEADER_STARTTIME		0xbb46516f
#define EDL_HEADER_LENGTH			0xaeac5df7
#define EDL_HEADER_PLAYRATE			0x03834606
#define EDL_HEADER_LOCKED			0x0c2083d3
#define EDL_HEADER_NORMALIZED		0xe60c8b1d
#define EDL_HEADER_STRETCHMETHOD	0xbe5802c9
#define EDL_HEADER_LOOPED			0x4ec8be4c
#define EDL_HEADER_ONRULER			0xe6cb84d1
#define EDL_HEADER_MEDIATYPE		0x035f84c2
#define EDL_HEADER_FILENAME			0x379d32c5
#define EDL_HEADER_STREAM			0x9f334738
#define EDL_HEADER_STREAMSTART		0x07b4e0e7
#define EDL_HEADER_STREAMLENGTH		0x8f16c7b8
#define EDL_HEADER_FADETIMEIN		0xd2edd7e4
#define EDL_HEADER_FADETIMEOUT		0x792e8c40
#define EDL_HEADER_SUSTAINGAIN		0x98374657
#define EDL_HEADER_CURVEIN			0x3e998b1f
#define EDL_HEADER_GAININ			0x82fb09c4
#define EDL_HEADER_CURVEOUT			0x53add388
#define EDL_HEADER_GAINOUT			0x4210ba56
#define EDL_HEADER_LAYER			0x89c4df6c
#define EDL_HEADER_COLOR			0xadf2f1a3
#define EDL_HEADER_CURVEINR			0xa56b43e1
#define EDL_HEADER_CURVEOUTR		0xcb6d715e
#define EDL_HEADER_PLAYPITCH		0x9da1b9ed
#define EDL_HEADER_LOCKPITCH		0x2bda6ed4
#define EDL_HEADER_FIRSTCHANNEL		0x3071a4c6
#define EDL_HEADER_CHANNELS			0xe94981a4

/* use a CRC32 hash function to process the headers */
static uint32_t EDL_internal_crcn32b(const unsigned char* message, size_t length) {
	uint32_t crc = 0xffffffff;
	size_t i;

	for (i = 0; i < length && message[i]; i++) {
		size_t j;

		crc = crc ^ message[i];
		for (j = 0; j < 8; j++)
			crc = (crc >> 1) ^ (0xedb88320 & -(crc & 1));
	}

	return ~crc;
}

static size_t EDL_internal_size_t_min(size_t a, size_t b) {
	return (a < b) ? a : b;
}

/* This parses the header of the EDL and creates an array with CRC32 hashes
 * in the order of the strings.
*/
static uint32_t* EDL_internal_parse_header(const char* data, size_t* length) {
	/* */
	const size_t newline = EDL_internal_size_t_min(strchr(data, '\n') - data, strchr(data, '\r') - data);

	size_t current_hash = 0, hashes_size = 32;
	uint32_t* hashes = malloc(hashes_size * sizeof(uint32_t));

	/* overall iterates over the entire line,
	 * column stores the length of the current column */
	size_t overall, column;

	for (overall = 0, column = 0; overall <= newline; overall++) {
		if (data[overall] == ';' || data[overall] == ':') {
			/* process the current column */
			if (hashes_size < current_hash)
				hashes = realloc(hashes, (hashes_size *= 2) * sizeof(uint32_t));

			hashes[current_hash++] = EDL_internal_crcn32b((const unsigned char*)&data[overall - column], column);
			column = 0; /* reset */
		} else column++;
	}

	*length = current_hash;

	return hashes;
}

int EDL_reallocate(EDL* edl, size_t new_capacity) {
	edl->arr = realloc(edl->arr, new_capacity * sizeof(EDL_line));
	if (!edl->arr)
		return 0;

	if (new_capacity > edl->capacity)
		memset(&edl->arr[edl->capacity], 0, (new_capacity - edl->capacity) * sizeof(EDL_line));

	edl->capacity = new_capacity;

	return 1;
}

int EDL_append(EDL* edl, EDL_line line) {
	if (edl->size + 1 < edl->capacity && !EDL_reallocate(edl, edl->capacity))
		return 0;

	edl->arr[++edl->size] = line;
	return 1;
}

/* the big important function */
EDL_parse_error_t EDL_parse(EDL* edl, const char* data, size_t length) {
	size_t order_size = 0;
	uint32_t* order = NULL;
	size_t offset = 0;

	if (!EDL_reallocate(edl, 16))
		return EDL_PARSE_ERROR_OUT_OF_MEMORY;

	order = EDL_internal_parse_header(data, &order_size);

	while ((offset = EDL_internal_strnchr(&data[offset], '\n', length - offset) - data + 1) < length) {
		size_t local_offset = offset; /* original offset stays intact */
		bool ok = true;
		int i;

		for (i = 0; i < order_size; i++) {
#define ADD_TO_OFFSET(x, a) \
{ \
	size_t o = a(data, local_offset, length, &edl->arr[edl->size].x); \
	if (!o) \
		ok = false; \
	local_offset += o; \
}
			switch (order[i]) {
				case EDL_HEADER_ID:
					ADD_TO_OFFSET(id, EDL_internal_parse_int);
					break;
				case EDL_HEADER_TRACK:
					ADD_TO_OFFSET(track, EDL_internal_parse_int);
					break;
				case EDL_HEADER_STARTTIME:
					ADD_TO_OFFSET(start_time, EDL_internal_parse_double);
					break;
				case EDL_HEADER_LENGTH:
					ADD_TO_OFFSET(length, EDL_internal_parse_double);
					break;
				case EDL_HEADER_PLAYRATE:
					ADD_TO_OFFSET(play_rate, EDL_internal_parse_double);
					break;
				case EDL_HEADER_LOCKED:
					ADD_TO_OFFSET(locked, EDL_internal_parse_bool);
					break;
				case EDL_HEADER_NORMALIZED:
					ADD_TO_OFFSET(normalized, EDL_internal_parse_bool);
					break;
				case EDL_HEADER_STRETCHMETHOD:
					ADD_TO_OFFSET(stretch_method, EDL_internal_parse_int);
					break;
				case EDL_HEADER_LOOPED:
					ADD_TO_OFFSET(looped, EDL_internal_parse_bool);
					break;
				case EDL_HEADER_ONRULER:
					ADD_TO_OFFSET(on_ruler, EDL_internal_parse_bool);
					break;
				case EDL_HEADER_MEDIATYPE:
					ADD_TO_OFFSET(media_type, EDL_internal_parse_media_type);
					break;
				case EDL_HEADER_FILENAME:
					ADD_TO_OFFSET(filename, EDL_internal_parse_string);
					break;
				case EDL_HEADER_STREAM:
					ADD_TO_OFFSET(stream, EDL_internal_parse_int);
					break;
				case EDL_HEADER_STREAMSTART:
					ADD_TO_OFFSET(stream_start, EDL_internal_parse_double);
					break;
				case EDL_HEADER_STREAMLENGTH:
					ADD_TO_OFFSET(stream_length, EDL_internal_parse_double);
					break;
				case EDL_HEADER_FADETIMEIN:
					ADD_TO_OFFSET(fade_time_in, EDL_internal_parse_double);
					break;
				case EDL_HEADER_FADETIMEOUT:
					ADD_TO_OFFSET(fade_time_out, EDL_internal_parse_double);
					break;
				case EDL_HEADER_SUSTAINGAIN:
					ADD_TO_OFFSET(sustain_gain, EDL_internal_parse_double);
					break;
				case EDL_HEADER_CURVEIN:
					ADD_TO_OFFSET(curve_in, EDL_internal_parse_int);
					break;
				case EDL_HEADER_GAININ:
					ADD_TO_OFFSET(gain_in, EDL_internal_parse_double);
					break;
				case EDL_HEADER_CURVEOUT:
					ADD_TO_OFFSET(curve_out, EDL_internal_parse_int);
					break;
				case EDL_HEADER_GAINOUT:
					ADD_TO_OFFSET(gain_out, EDL_internal_parse_double);
					break;
				case EDL_HEADER_LAYER:
					ADD_TO_OFFSET(layer, EDL_internal_parse_int);
					break;
				case EDL_HEADER_COLOR:
					ADD_TO_OFFSET(color, EDL_internal_parse_int);
					break;
				case EDL_HEADER_CURVEINR:
					ADD_TO_OFFSET(curve_in_r, EDL_internal_parse_int);
					break;
				case EDL_HEADER_CURVEOUTR:
					ADD_TO_OFFSET(curve_out_r, EDL_internal_parse_int);
					break;
				case EDL_HEADER_PLAYPITCH:
					ADD_TO_OFFSET(play_pitch, EDL_internal_parse_double);
					break;
				case EDL_HEADER_LOCKPITCH:
					ADD_TO_OFFSET(lock_pitch, EDL_internal_parse_bool);
					break;
				case EDL_HEADER_FIRSTCHANNEL:
					ADD_TO_OFFSET(first_channel, EDL_internal_parse_int);
					break;
				case EDL_HEADER_CHANNELS:
					ADD_TO_OFFSET(channels, EDL_internal_parse_int);
					break;
				default:
					/* ... what */
					break;
#undef ADD_TO_OFFSET
			}
		}

		if (!ok)
			break;

		if (++edl->size >= edl->capacity)
			EDL_reallocate(edl, edl->capacity * 2);
	}

	/* put on the shrinkwrap */
	EDL_reallocate(edl, edl->size);

	free(order);

	return EDL_PARSE_ERROR_SUCCESS;
}

static void EDL_dump_line(EDL_internal_string* str, const EDL_line* line, const uint32_t* order, const size_t order_len) {
	size_t i;

	for (i = 0; i < order_len; i++) {
		switch (order[i]) {
#define APPEND_ITEM(a, x) \
{ \
	char* tstr = x(line->a); \
	EDL_internal_string_append(str, tstr, strlen(tstr)); \
	free(tstr); \
}
			case EDL_HEADER_ID:
				APPEND_ITEM(id, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_TRACK:
				APPEND_ITEM(track, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_STARTTIME:
				APPEND_ITEM(start_time, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_LENGTH:
				APPEND_ITEM(length, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_PLAYRATE:
				APPEND_ITEM(play_rate, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_LOCKED:
				APPEND_ITEM(locked, EDL_internal_bool_to_string);
				break;
			case EDL_HEADER_NORMALIZED:
				APPEND_ITEM(normalized, EDL_internal_bool_to_string);
				break;
			case EDL_HEADER_STRETCHMETHOD:
				APPEND_ITEM(stretch_method, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_LOOPED:
				APPEND_ITEM(looped, EDL_internal_bool_to_string);
				break;
			case EDL_HEADER_ONRULER:
				APPEND_ITEM(on_ruler, EDL_internal_bool_to_string);
				break;
			case EDL_HEADER_MEDIATYPE:
				APPEND_ITEM(media_type, EDL_internal_media_type_to_string);
				break;
			case EDL_HEADER_FILENAME:
				EDL_internal_string_append(str, "\"", 1);
				EDL_internal_string_append(str, line->filename, strlen(line->filename));
				EDL_internal_string_append(str, "\"", 1);
				break;
			case EDL_HEADER_STREAM:
				APPEND_ITEM(stream, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_STREAMSTART:
				APPEND_ITEM(stream_start, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_STREAMLENGTH:
				APPEND_ITEM(stream_length, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_FADETIMEIN:
				APPEND_ITEM(fade_time_in, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_FADETIMEOUT:
				APPEND_ITEM(fade_time_out, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_SUSTAINGAIN:
				APPEND_ITEM(sustain_gain, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_CURVEIN:
				APPEND_ITEM(curve_in, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_GAININ:
				APPEND_ITEM(gain_in, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_CURVEOUT:
				APPEND_ITEM(curve_out, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_GAINOUT:
				APPEND_ITEM(gain_out, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_LAYER:
				APPEND_ITEM(layer, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_COLOR:
				APPEND_ITEM(color, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_CURVEINR:
				APPEND_ITEM(curve_in_r, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_CURVEOUTR:
				APPEND_ITEM(curve_out_r, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_PLAYPITCH:
				APPEND_ITEM(play_pitch, EDL_internal_double_to_string);
				break;
			case EDL_HEADER_LOCKPITCH:
				APPEND_ITEM(lock_pitch, EDL_internal_bool_to_string);
				break;
			case EDL_HEADER_FIRSTCHANNEL:
				APPEND_ITEM(first_channel, EDL_internal_integer_to_string);
				break;
			case EDL_HEADER_CHANNELS:
				APPEND_ITEM(channels, EDL_internal_integer_to_string);
				break;
			default:
				/* ... what */
				break;
#undef APPEND_ITEM
		}

		if (i < order_len - 1)
			EDL_internal_string_append(str, ";\t", 2);
		else
			EDL_internal_string_append(str, ";", 1);
	}

	EDL_internal_string_append(str, "\n", 1);
}

/* this gets progressively slower and slower... oops */
char* EDL_dump(const EDL* edl) {
	static const char order_str[] =
		"\"ID\";\"Track\";\"StartTime\";\"Length\";\"PlayRate\";\"Locked\";\"Normalized\";\"StretchMethod\";"
		"\"Looped\";\"OnRuler\";\"MediaType\";\"FileName\";\"Stream\";\"StreamStart\";\"StreamLength\";"
		"\"FadeTimeIn\";\"FadeTimeOut\";\"SustainGain\";\"CurveIn\";\"GainIn\";\"CurveOut\";\"GainOut\";"
		"\"Layer\";\"Color\";\"CurveInR\";\"CurveOutR\":\"PlayPitch\";\"LockPitch\";\"FirstChannel\";\"Channels\"\n";

	EDL_internal_string ret;
	EDL_internal_string_init(&ret);

	EDL_internal_string_append(&ret, order_str, strlen(order_str));

	{
		size_t order_len;
		uint32_t* order = EDL_internal_parse_header(order_str, &order_len);

		size_t i;
		for (i = 0; i < edl->size; i++)
			EDL_dump_line(&ret, &edl->arr[i], order, order_len);

		free(order);
	}

	return ret.data;
}

void EDL_free(EDL* edl) {
	size_t i;
	for (i = 0; i < edl->size; i++) {
		if (edl->arr[i].filename)
			free(edl->arr[i].filename);
	}
	free(edl->arr);
}