summaryrefslogtreecommitdiff
path: root/plugins/liboggedit/oggedit_internal.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/liboggedit/oggedit_internal.c')
-rw-r--r--plugins/liboggedit/oggedit_internal.c524
1 files changed, 524 insertions, 0 deletions
diff --git a/plugins/liboggedit/oggedit_internal.c b/plugins/liboggedit/oggedit_internal.c
new file mode 100644
index 00000000..a95fa1f8
--- /dev/null
+++ b/plugins/liboggedit/oggedit_internal.c
@@ -0,0 +1,524 @@
+/*
+ This file is part of Deadbeef Player source code
+ http://deadbeef.sourceforge.net
+
+ DeaDBeeF Ogg Edit library internal functions
+
+ Copyright (C) 2014 Ian Nartowicz <deadbeef@nartowicz.co.uk>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <libgen.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <ogg/ogg.h>
+#include <deadbeef/deadbeef.h>
+#include "oggedit.h"
+#include "oggedit_internal.h"
+
+static char *cat_string(char *dest, const char *src, const char *sep)
+{
+ char *more = realloc(dest, strlen(dest) + strlen(src) + strlen(sep) + 1);
+ if (!more) {
+ free(dest);
+ return NULL;
+ }
+ return strcat(strcat(more, sep), src);
+}
+
+static const char *codec_name(ogg_page *og)
+{
+ typedef struct {
+ const size_t length;
+ const char *codec;
+ const char *magic;
+ } codec_t;
+ const codec_t codecs[] = {
+ {.codec = OPUSNAME, .length = 19, .magic = "OpusHead"},
+ {.codec = VORBISNAME, .length = 30, .magic = "\1vorbis"},
+ {.codec = FLACNAME, .length = 47, .magic = "\177FLAC"},
+ {.codec = "Speex", .length = 80, .magic = "Speex "},
+ {.codec = "Celt", .length = 80, .magic = "CELT"}, // obsolete
+ {.codec = "MIDI", .length = 13, .magic = "OggMIDI\0"},
+ {.codec = "PCM", .length = 28, .magic = "PCM "},
+ {.codec = "Theora", .length = 42, .magic = "\200theora"},
+ {.codec = "Daala", .length = 38, .magic = "\200daala"},
+ {.codec = "Dirac", .length = 5, .magic = "BBCD\0"},
+ {.codec = "Skeleton", .length = 80, .magic = "fishead\0"},
+ {.codec = "Kate", .length = 64, .magic = "\200kate\0\0\0"},
+ {.codec = "CMML", .length = 29, .magic = "CMML\0\0\0\0"},
+ {.codec = "YUV4MPEG", .length = 8, .magic = "YUV4Mpeg"},
+ {.codec = "UVS", .length = 48, .magic = "UVS "},
+ {.codec = "YUV", .length = 32, .magic = "\0YUV"},
+ {.codec = "RGB", .length = 24, .magic = "\0RGB"},
+ {.codec = "JNG", .length = 48, .magic = "\213JNG\r\n\032\n"},
+ {.codec = "MNG", .length = 48, .magic = "\212MNG\r\n\032\n"},
+ {.codec = "PNG", .length = 48, .magic = "\211PNG\r\n\032\n"},
+ {.codec = "Spots", .length = 52, .magic = "SPOTS\0\0\0"},
+ {.codec = NULL}
+ };
+
+ for (const codec_t *match = codecs; match->codec; match++)
+ if ((size_t)og->body_len >= match->length && !memcmp(og->body, match->magic, strlen(match->codec)))
+ return match->codec;
+
+ return "unknown";
+}
+
+void _oggpack_chars(oggpack_buffer *opb, const char *s, size_t length)
+{
+// oggpack_writecopy(opb, s, length * 8); currently broken
+ while (length--)
+ oggpack_write(opb, *s++, 8);
+}
+
+void _oggpack_string(oggpack_buffer *opb, const char *s)
+{
+ oggpack_write(opb, strlen(s), 32);
+ _oggpack_chars(opb, s, strlen(s));
+}
+
+void _oggpackB_string(oggpack_buffer *opb, const char *s)
+{
+ oggpackB_write(opb, strlen(s), 32);
+ _oggpack_chars(opb, s, strlen(s));
+}
+
+static bool ensure_directory(const char *path)
+{
+ struct stat stat_struct;
+ if (!stat(path, &stat_struct))
+ return !S_ISDIR(stat_struct.st_mode);
+
+ if (errno != ENOENT)
+ return false;
+
+ char* dir = strdup(path);
+ if (!dir)
+ return false;
+
+ const int bad_dir = ensure_directory(dirname(dir));
+ free(dir);
+
+ return !bad_dir && !mkdir(path, 0777);
+}
+
+FILE *open_new_file(const char *outname)
+{
+ char outpath[PATH_MAX];
+ strcpy(outpath, outname);
+ if (!ensure_directory(dirname(outpath)))
+ return NULL;
+
+ unlink(outname);
+ return fopen(outname, "wbx");
+}
+
+int open_temp_file(const char *fname, char *tempname, FILE **out)
+{
+ snprintf(tempname, PATH_MAX, "%s.temp", fname);
+ unlink(tempname);
+ if (!(*out = freopen(tempname, "abx", *out)))
+ return OGGEDIT_CANNOT_OPEN_TEMPORARY_FILE;
+
+ struct stat stat_struct;
+ if (!stat(fname, &stat_struct))
+ chmod(tempname, stat_struct.st_mode);
+
+ return 0;
+}
+
+off_t file_size(const char *fname)
+{
+ struct stat sb;
+ if (stat(fname, &sb))
+ return OGGEDIT_STAT_FAILED;
+
+ return sb.st_size;
+}
+
+void cleanup(DB_FILE *in, FILE *out, ogg_sync_state *oy, void *buffer)
+{
+ if (in)
+ in->vfs->close(in);
+
+ if (out)
+ fclose(out);
+
+ ogg_sync_clear(oy);
+
+ if (buffer)
+ free(buffer);
+}
+
+static int get_page(DB_FILE *in, ogg_sync_state *oy, ogg_page *og)
+{
+ uint16_t chunks_left = MAXPAGE / CHUNKSIZE;
+ while (ogg_sync_pageout(oy, og) != 1) {
+ char *buffer = ogg_sync_buffer(oy, CHUNKSIZE);
+ if (!in || !buffer || !chunks_left--)
+ return OGGEDIT_CANT_FIND_STREAM;
+
+ const size_t bytes = in->vfs->read(buffer, 1, CHUNKSIZE, in);
+ if (!bytes)
+ return OGGEDIT_EOF;
+
+ ogg_sync_wrote(oy, bytes);
+ }
+
+ return ogg_page_serialno(og);
+}
+
+static int skip_to_bos(DB_FILE *in, ogg_sync_state *oy, ogg_page *og, const off_t offset)
+{
+ if (!in)
+ return OGGEDIT_FILE_NOT_OPEN;
+
+ if (in->vfs->seek(in, offset, SEEK_SET))
+ return OGGEDIT_SEEK_FAILED;
+
+ ogg_sync_reset(oy);
+ int serial;
+ do
+ serial = get_page(in, oy, og);
+ while (serial > OGGEDIT_EOF && !ogg_page_bos(og));
+
+ return serial;
+}
+
+static int skip_to_codec(DB_FILE *in, ogg_sync_state *oy, ogg_page *og, const off_t offset, const char *codec)
+{
+ int serial = skip_to_bos(in, oy, og, offset);
+ while (serial > OGGEDIT_EOF && strcmp(codec_name(og), codec))
+ serial = get_page(in, oy, og);
+
+ return serial;
+}
+
+static int skip_to_header(DB_FILE *in, ogg_sync_state *oy, ogg_page *og, int serial, const int codec_serial)
+{
+ while (serial > OGGEDIT_EOF && (ogg_page_bos(og) || serial != codec_serial))
+ serial = get_page(in, oy, og);
+
+ return serial;
+}
+
+static off_t sync_tell(DB_FILE *in, ogg_sync_state *oy, ogg_page *og)
+{
+ return in->vfs->tell(in) - oy->fill + oy->returned - og->header_len - og->body_len;
+}
+
+static bool write_page(FILE *out, ogg_page *og)
+{
+ if (fwrite(og->header, 1, og->header_len, out) != (size_t)og->header_len)
+ return false;
+
+ if (fwrite(og->body, 1, og->body_len, out) != (size_t)og->body_len)
+ return false;
+
+ return true;
+}
+
+static int write_page_and_get_next(DB_FILE *in, FILE *out, ogg_sync_state *oy, ogg_page *og)
+{
+ if (!write_page(out, og))
+ return OGGEDIT_WRITE_ERROR;
+
+ return get_page(in, oy, og);
+}
+
+int copy_up_to_codec(DB_FILE *in, FILE *out, ogg_sync_state *oy, ogg_page *og, const off_t start_offset, const off_t link_offset, const char *codec)
+{
+ int serial = skip_to_bos(in, oy, og, start_offset);
+
+ if (fseek(out, sync_tell(in, oy, og), SEEK_SET))
+ return OGGEDIT_SEEK_FAILED;
+
+ while (serial > OGGEDIT_EOF && (sync_tell(in, oy, og) < link_offset || !ogg_page_bos(og) || strcmp(codec_name(og), codec)))
+ serial = write_page_and_get_next(in, out, oy, og);
+
+ return serial;
+}
+
+int copy_up_to_header(DB_FILE *in, FILE *out, ogg_sync_state *oy, ogg_page *og, const int codec_serial)
+{
+ int serial;
+ do
+ serial = write_page_and_get_next(in, out, oy, og);
+ while (serial > OGGEDIT_EOF && serial != codec_serial);
+ return serial;
+}
+
+long flush_stream(FILE *out, ogg_stream_state *os)
+{
+ ogg_page og;
+ while (ogg_stream_flush_fill(os, &og, MAXPAYLOAD))
+ if (!write_page(out, &og))
+ return OGGEDIT_WRITE_ERROR;
+ const long pageno = ogg_stream_check(os) ? OGGEDIT_FLUSH_FAILED : ogg_page_pageno(&og);
+ ogg_stream_clear(os);
+ return pageno;
+}
+
+char *codec_names(DB_FILE *in, ogg_sync_state *oy, const off_t link_offset, int *res)
+{
+ ogg_page og;
+ *res = skip_to_bos(in, oy, &og, link_offset);
+ char *codecs = strdup("Ogg");
+ while (codecs && *res > OGGEDIT_EOF && ogg_page_bos(&og)) {
+ codecs = cat_string(codecs, codec_name(&og), strcmp(codecs, "Ogg") ? "/" : " ");
+ *res = get_page(in, oy, &og);
+ }
+ if (!*codecs) {
+ *res = OGGEDIT_ALLOCATION_FAILURE;
+ return NULL;
+ }
+ if (*res <= OGGEDIT_EOF) {
+ free(codecs);
+ return NULL;
+ }
+
+ *res = OGGEDIT_OK;
+ return codecs;
+}
+
+off_t codec_stream_size(DB_FILE *in, ogg_sync_state *oy, const off_t start_offset, const off_t end_offset, const char *codec)
+{
+ /* Find codec serial and any other codecs */
+ bool multiplex = false;
+ ogg_page og;
+ int codec_serial = -1;
+ int serial = skip_to_bos(in, oy, &og, start_offset);
+ while (serial > OGGEDIT_EOF && ogg_page_bos(&og)) {
+ if (strcmp(codec_name(&og), codec))
+ multiplex = true;
+ else
+ codec_serial = serial;
+ serial = get_page(in, oy, &og);
+ }
+
+ /* Skip to the first codec data page */
+ while (serial > OGGEDIT_EOF && !(ogg_page_granulepos(&og) > 0 && serial == codec_serial))
+ if ((serial = get_page(in, oy, &og)) <= OGGEDIT_EOF)
+ return serial;
+
+ off_t stream_size = 0;
+ if (multiplex) {
+ /* Add up all the codec stream pages until EOF or a new link */
+ while (serial > OGGEDIT_EOF && !ogg_page_bos(&og)) {
+ if (serial == codec_serial)
+ stream_size += og.header_len + og.body_len;
+ serial = get_page(in, oy, &og);
+ }
+ }
+ else {
+ /* Find the exact offsets of the start and end of the audio */
+ stream_size -= sync_tell(in, oy, &og);
+ if (in->vfs->seek(in, end_offset, end_offset == 0 ? SEEK_END : SEEK_SET))
+ return OGGEDIT_SEEK_FAILED;
+ stream_size += in->vfs->tell(in);
+ ogg_sync_reset(oy);
+ while ((serial = get_page(in, oy, &og)) > OGGEDIT_EOF && !ogg_page_bos(&og))
+ stream_size += og.header_len + og.body_len;
+ }
+ if (serial < OGGEDIT_EOF)
+ return serial;
+
+ return stream_size;
+}
+
+char *parse_vendor(const ogg_packet *op, const size_t magic_length)
+{
+ if (op->bytes < magic_length + 4)
+ return NULL;
+
+ const uint8_t *p = op->packet + magic_length;
+ const uint32_t vendor_length = *p | *(p+1)<<8 | *(p+2)<<16 | *(p+3)<<24;
+ if (op->bytes < magic_length + 4 + vendor_length)
+ return NULL;
+
+ char *vendor = calloc(vendor_length+1, 1);
+ if (vendor)
+ memcpy(vendor, p+4, vendor_length);
+ return vendor;
+}
+
+int init_read_stream(DB_FILE *in, ogg_sync_state *oy, ogg_stream_state *os, ogg_page *og, const off_t offset, const char *codec)
+{
+ int serial = skip_to_codec(in, oy, og, offset, codec);
+ serial = skip_to_header(in, oy, og, serial, serial);
+ if (serial <= OGGEDIT_EOF)
+ return serial;
+
+ if (ogg_stream_init(os, serial))
+ return OGGEDIT_FAILED_TO_INIT_STREAM;
+
+ os->b_o_s = 1;
+ ogg_stream_pagein(os, og);
+
+ return OGGEDIT_OK;
+}
+
+int read_packet(DB_FILE *in, ogg_sync_state *oy, ogg_stream_state *os, ogg_page *og, ogg_packet *header, int pages)
+{
+ ogg_packet op;
+ do {
+ while (ogg_stream_packetpeek(os, NULL) == 0) {
+ const int serial = get_page(in, oy, og);
+ if (serial <= OGGEDIT_EOF) {
+ return serial;
+ }
+ if (os->serialno == serial) {
+ pages++;
+ ogg_stream_pagein(os, og);
+ }
+ }
+ if (ogg_stream_check(os))
+ return OGGEDIT_FAILED_TO_STREAM_PAGE_FOR_PACKET;
+ } while (ogg_stream_packetout(os, &op) != 1);
+
+ memset(header, '\0', sizeof(*header));
+ if (!header || !(header->packet = malloc(op.bytes))) {
+ free(header);
+ return OGGEDIT_ALLOCATION_FAILURE;
+ }
+
+ header->bytes = op.bytes;
+ memcpy(header->packet, op.packet, op.bytes);
+ return pages;
+}
+
+ogg_packet *fill_vc_packet(const char *magic, const size_t magic_length, const char *vendor, const size_t num_tags, char **tags, const bool framing, const size_t padding, ogg_packet *op)
+{
+ oggpack_buffer opb;
+ oggpack_writeinit(&opb);
+ _oggpack_chars(&opb, magic, magic_length);
+ _oggpack_string(&opb, vendor);
+ oggpack_write(&opb, num_tags, 32);
+ for (size_t i = 0; i < num_tags; i++)
+ _oggpack_string(&opb, tags[i]);
+ if (framing) {
+ oggpack_write(&opb, 1, 1);
+ oggpack_writealign(&opb);
+ }
+ for (size_t i = 0; i < padding; i++)
+ oggpack_write(&opb, 0, 8); // use oggpack_writecopy when it is fixed
+ if (oggpack_writecheck(&opb))
+ return NULL;
+
+ if (op) {
+ memset(op, '\0', sizeof(*op));
+ op->bytes = oggpack_bytes(&opb);
+ if (op->packet = malloc(op->bytes))
+ memcpy(op->packet, oggpack_get_buffer(&opb), op->bytes);
+ }
+ oggpack_writeclear(&opb);
+
+ if (!op->packet)
+ return NULL;
+
+ return op;
+}
+
+size_t vc_size(const char *vendor, size_t num_tags, char **tags)
+{
+ size_t metadata_size = 4 + strlen(vendor) + 4;
+ for (size_t i = 0; i < num_tags; i++)
+ metadata_size += strlen(tags[i]) + 4;
+ return metadata_size;
+}
+
+int copy_remaining_pages(DB_FILE *in, FILE *out, ogg_sync_state *oy, const int codec_serial, uint32_t pageno)
+{
+ /* Skip past the codec header packets */
+ ogg_page og;
+ int serial;
+ do
+ serial = get_page(in, oy, &og);
+ while (serial > OGGEDIT_EOF && serial == codec_serial && ogg_page_granulepos(&og) <= 0);
+
+ /* Renumber the rest of this link */
+ while (serial > OGGEDIT_EOF && !ogg_page_bos(&og)) {
+ if (serial == codec_serial && ogg_page_pageno(&og) != ++pageno) {
+ og.header[18] = pageno & 0xFF;
+ og.header[19] = pageno >> 8 & 0xFF;
+ og.header[20] = pageno >> 16 & 0xFF;
+ og.header[21] = pageno >> 24 & 0xFF;
+ ogg_page_checksum_set(&og);
+ }
+ serial = write_page_and_get_next(in, out, oy, &og);
+ }
+
+ /* Blindly copy remaining links */
+ while (serial > OGGEDIT_EOF)
+ serial = write_page_and_get_next(in, out, oy, &og);
+
+ return serial;
+}
+
+int write_all_streams(DB_FILE *in, FILE *out, ogg_sync_state *oy, const off_t offset)
+{
+ /* Copy BOS page(s) */
+ ogg_page og;
+ int serial = skip_to_bos(in, oy, &og, offset);
+ while (serial > OGGEDIT_EOF && ogg_page_bos(&og))
+ serial = write_page_and_get_next(in, out, oy, &og);
+ if (serial <= OGGEDIT_EOF)
+ return serial;
+
+ /* Copy all pages until EOF or next link */
+ while (serial > OGGEDIT_EOF && !ogg_page_bos(&og))
+ if ((serial = write_page_and_get_next(in, out, oy, &og)) < OGGEDIT_EOF)
+ return serial;
+
+ return OGGEDIT_OK;
+}
+
+int write_one_stream(DB_FILE *in, FILE *out, ogg_sync_state *oy, const off_t offset, const char *codec)
+{
+ /* Find codec BOS page */
+ ogg_page og;
+ const int codec_serial = skip_to_codec(in, oy, &og, offset, codec);
+ if (codec_serial <= OGGEDIT_EOF)
+ return codec_serial;
+
+ /* Write it and skip the other BOS pages */
+ int serial = write_page_and_get_next(in, out, oy, &og);
+ if ((serial = skip_to_header(in, oy, &og, serial, codec_serial)) <= OGGEDIT_EOF)
+ return serial;
+
+ /* Copy all codec pages until EOF or next link */
+ while (serial > OGGEDIT_EOF && !ogg_page_bos(&og)) {
+ if (serial == codec_serial && !write_page(out, &og))
+ return OGGEDIT_WRITE_ERROR;
+ serial = get_page(in, oy, &og);
+ }
+ if (serial < OGGEDIT_EOF)
+ return serial;
+
+ return OGGEDIT_OK;
+}