summaryrefslogtreecommitdiff
path: root/plugins/liboggedit/oggedit_flac.c
diff options
context:
space:
mode:
authorGravatar Ian Nartowicz <iann@crunchbang>2014-06-04 22:45:30 +0100
committerGravatar Ian Nartowicz <iann@crunchbang>2014-06-04 22:45:30 +0100
commit823a4064ebcd2662f8e480f99326ac75dd6404a7 (patch)
tree420f6df26d390479f5573acb4f922d62d458ae8c /plugins/liboggedit/oggedit_flac.c
parent923bdcd04d441869dc0fc9350c5bfb856f6e5677 (diff)
Full release of oggedit
Diffstat (limited to 'plugins/liboggedit/oggedit_flac.c')
-rw-r--r--plugins/liboggedit/oggedit_flac.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/plugins/liboggedit/oggedit_flac.c b/plugins/liboggedit/oggedit_flac.c
new file mode 100644
index 00000000..6eec6ac9
--- /dev/null
+++ b/plugins/liboggedit/oggedit_flac.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of Deadbeef Player source code
+ http://deadbeef.sourceforge.net
+
+ DeaDBeeF Ogg Edit library Flac 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <ogg/ogg.h>
+#include <deadbeef/deadbeef.h>
+#include "oggedit_internal.h"
+#include "oggedit.h"
+
+#define PADTYPE 0x01
+#define VCTYPE 0x04
+#define LASTBLOCK 0x80
+
+static void clear_header_list(ogg_packet **headers)
+{
+ if (headers) {
+ for (ogg_packet **header = headers; *header; header++) {
+ ogg_packet_clear(*header);
+ free(*header);
+ }
+ free(headers);
+ }
+}
+
+static ogg_packet **headers_alloc(ogg_packet **headers, const size_t packets)
+{
+ ogg_packet **new_headers = realloc(headers, (packets+2) * sizeof(headers));
+ if (!new_headers) {
+ clear_header_list(headers);
+ return NULL;
+ }
+
+ if (!(new_headers[packets] = malloc(sizeof(ogg_packet)))) {
+ clear_header_list(new_headers);
+ return NULL;
+ }
+
+ new_headers[packets + 1] = NULL;
+ return new_headers;
+}
+
+static ogg_packet **metadata_block_packets(DB_FILE *in, ogg_sync_state *oy, const off_t offset, char **vendor, int *res)
+{
+ ogg_stream_state os;
+ ogg_page og;
+ if ((*res = init_read_stream(in, oy, &os, &og, offset, FLACNAME)) <= OGGEDIT_EOF)
+ return NULL;
+
+ int pages = 1;
+ size_t packets = 0;
+ ogg_packet **headers = NULL;
+ while ((headers = headers_alloc(headers, packets)) &&
+ (pages = read_packet(in, oy, &os, &og, headers[packets], pages)) > OGGEDIT_EOF &&
+ (headers[packets++]->packet[0] & LASTBLOCK) != LASTBLOCK);
+
+ ogg_stream_clear(&os);
+
+ if (!headers)
+ *res = OGGEDIT_ALLOCATION_FAILURE;
+ else if (!packets || headers[0]->packet[0] & 0x3F != VCTYPE)
+ *res = OGGEDIT_CANNOT_PARSE_HEADERS;
+ else
+ *res = pages;
+ if (*res <= OGGEDIT_EOF) {
+ clear_header_list(headers);
+ return NULL;
+ }
+
+ *vendor = parse_vendor(headers[0], 4);
+ size_t bytes = 0;
+ for (size_t i = 0; i < packets; i++)
+ bytes += headers[i]->bytes;
+ if (bytes < MAXPAYLOAD * (pages-1))
+ headers[0]->bytes = 4;
+
+ *res = OGGEDIT_OK;
+ return headers;
+}
+
+static long write_metadata_block_packets(FILE *out, const int serial, const char *vendor, const size_t num_tags, char **tags, size_t padding, ogg_packet **metadata)
+{
+ const size_t header_length = vc_size(vendor, num_tags, tags);
+ if (header_length > (2<<24))
+ return OGGEDIT_ALLOCATION_FAILURE;
+
+ char magic[4];
+ magic[0] = VCTYPE;
+ magic[1] = header_length >> 16 & 0xFF;
+ magic[2] = header_length >> 8 & 0xFF;
+ magic[3] = header_length & 0xFF;
+ ogg_packet_clear(metadata[0]);
+ if (!fill_vc_packet(magic, 4, vendor, num_tags, tags, false, padding, metadata[0]))
+ return OGGEDIT_ALLOCATION_FAILURE;
+
+ ogg_stream_state os;
+ if (ogg_stream_init(&os, serial))
+ return OGGEDIT_FAILED_TO_INIT_STREAM;
+ os.b_o_s = 1;
+ os.pageno = 1;
+
+ for (int i = 0; metadata[i]; i++) {
+ if (!metadata[i+1])
+ metadata[i]->packet[0] |= LASTBLOCK;
+ ogg_stream_packetin(&os, metadata[i]);
+ }
+
+ return flush_stream(out, &os);
+}
+
+off_t oggedit_write_flac_metadata(DB_FILE *in, const char *fname, const off_t offset, const int num_tags, char **tags)
+{
+ off_t res;
+ char tempname[PATH_MAX] = "";
+ ogg_packet **headers = NULL;
+ char *vendor = NULL;
+ ogg_sync_state oy;
+ ogg_sync_init(&oy);
+
+ /* Original file must be writable whichever way we update it */
+ FILE *out = fopen(fname, "r+b");
+ if (!out) {
+ res = OGGEDIT_CANNOT_UPDATE_FILE;
+ goto cleanup;
+ }
+
+ /* See if we can write the tags packet directly into the existing file ... */
+ if (!(headers = metadata_block_packets(in, &oy, offset, &vendor, (int *)&res)))
+ goto cleanup;
+ const off_t stream_size_k = in->vfs->getlength(in) / 1000; // use file size for now
+ const size_t metadata_size = 4 + vc_size(vendor, num_tags, tags);
+ ptrdiff_t padding = headers[0]->bytes - metadata_size;
+ if (stream_size_k < 1000 || padding < 0 || headers[1] && padding > 0 || padding > stream_size_k+metadata_size)
+ if (res = open_temp_file(fname, tempname, &out))
+ goto cleanup;
+
+ /* Re-pad if writing the whole file */
+ if (*tempname) {
+ size_t i = 0, j = 0;
+ while (headers[i]) {
+ headers[j++] = headers[i];
+ while (headers[++i] && (headers[i]->packet[0] & 0x3F) == PADTYPE) {
+ ogg_packet_clear(headers[i]);
+ free(headers[i]);
+ }
+ }
+ headers[j] = NULL;
+ padding = headers[1] || stream_size_k < 900 ? 0 : stream_size_k < 10000 ? 1024 : stream_size_k < 100000 ? 8192 : 65536;
+ }
+
+ /* Write pages until we reach the correct comment header */
+ ogg_page og;
+ const int flac_serial = copy_up_to_codec(in, out, &oy, &og, *tempname ? 0 : offset, offset, FLACNAME);
+ if (flac_serial <= OGGEDIT_EOF) {
+ res = flac_serial;
+ goto cleanup;
+ }
+ if ((res = copy_up_to_header(in, out, &oy, &og, flac_serial)) <= OGGEDIT_EOF)
+ goto cleanup;
+ const long pageno = write_metadata_block_packets(out, flac_serial, vendor, num_tags, tags, padding, headers);
+ if (pageno < OGGEDIT_EOF) {
+ res = pageno;
+ goto cleanup;
+ }
+
+ /* If we have tempfile, copy the remaining pages */
+ if (*tempname) {
+ if ((res = copy_remaining_pages(in, out, &oy, flac_serial, pageno)) < OGGEDIT_EOF)
+ goto cleanup;
+ if (rename(tempname, fname)) {
+ res = OGGEDIT_RENAME_FAILED;
+ goto cleanup;
+ }
+ }
+
+ res = file_size(fname);
+
+cleanup:
+ clear_header_list(headers);
+ cleanup(in, out, &oy, vendor);
+ if (res < OGGEDIT_OK)
+ unlink(tempname);
+ return res;
+}
+