diff options
Diffstat (limited to 'plugins/liboggedit/oggedit_vorbis.c')
-rw-r--r-- | plugins/liboggedit/oggedit_vorbis.c | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/plugins/liboggedit/oggedit_vorbis.c b/plugins/liboggedit/oggedit_vorbis.c new file mode 100644 index 00000000..2ff3fc09 --- /dev/null +++ b/plugins/liboggedit/oggedit_vorbis.c @@ -0,0 +1,168 @@ +/* + This file is part of Deadbeef Player source code + http://deadbeef.sourceforge.net + + DeaDBeeF Ogg Edit library Vorbis 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 VCMAGIC "\3vorbis" +#define CODEMAGIC "\5vorbis" + +off_t oggedit_vorbis_stream_info(DB_FILE *in, const off_t start_offset, const off_t end_offset, char **codecs) +{ + int res; + ogg_sync_state oy; + ogg_sync_init(&oy); + *codecs = codec_names(in, &oy, start_offset, &res); + const off_t stream_size = codec_stream_size(in, &oy, start_offset, end_offset, VORBISNAME); + cleanup(in, NULL, &oy, NULL); + return stream_size; +} + +static ptrdiff_t check_vorbis_headers(DB_FILE *in, ogg_sync_state *oy, const off_t offset, char **vendor, ogg_packet *codebooks) +{ + ogg_stream_state os; + ogg_page og; + const int serial = init_read_stream(in, oy, &os, &og, offset, VORBISNAME); + if (serial <= OGGEDIT_EOF) + return serial; + + ogg_packet vc; + int pages = read_packet(in, oy, &os, &og, &vc, 1); + if (pages > OGGEDIT_EOF) + pages = read_packet(in, oy, &os, &og, codebooks, pages); + ogg_stream_clear(&os); + if (pages <= OGGEDIT_EOF) + return pages; + + if (vc.bytes > strlen(VCMAGIC) && !memcmp(vc.packet, VCMAGIC, strlen(VCMAGIC)) && + codebooks->bytes > strlen(CODEMAGIC) && !memcmp(codebooks->packet, CODEMAGIC, strlen(CODEMAGIC))) + *vendor = parse_vendor(&vc, strlen(VCMAGIC)); + free(vc.packet); + if (!*vendor) + return OGGEDIT_CANNOT_PARSE_HEADERS; + + if ((vc.bytes + codebooks->bytes) < MAXPAYLOAD * (pages-1)) + return 4; // prevent in-place write if the packets are split over too many pages + + return vc.bytes; +} + +static long write_vorbis_tags(FILE *out, const int serial, const char *vendor, const size_t num_tags, char **tags, const size_t padding, ogg_packet *codebooks) +{ + ogg_packet op; + if (!fill_vc_packet(VCMAGIC, strlen(VCMAGIC), vendor, num_tags, tags, true, padding, &op)) + 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; + ogg_stream_packetin(&os, &op); + ogg_stream_packetin(&os, codebooks); + + ogg_packet_clear(&op); + return flush_stream(out, &os); +} + +off_t oggedit_write_vorbis_metadata(DB_FILE *in, const char *fname, const off_t offset, const size_t stream_size, const int num_tags, char **tags) +{ + off_t res; + char tempname[PATH_MAX] = ""; + char *vendor = NULL; + ogg_packet codebooks; + memset(&codebooks, '\0', sizeof(codebooks)); + 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 ... */ + const ptrdiff_t tags_packet_size = check_vorbis_headers(in, &oy, offset, &vendor, &codebooks); + if (tags_packet_size <= OGGEDIT_EOF) { + res = tags_packet_size; + goto cleanup; + } + const size_t metadata_size = strlen(VCMAGIC) + vc_size(vendor, num_tags, tags) + 1; + ptrdiff_t padding = tags_packet_size - metadata_size; + const off_t file_size_k = in->vfs->getlength(in) / 1000; + const size_t stream_size_k = stream_size ? stream_size / 1000 : file_size_k; + if (file_size_k < 100 || padding < 0 || padding > file_size_k/10+stream_size_k+metadata_size) + if (res = open_temp_file(fname, tempname, &out)) + goto cleanup; + + /* Re-pad if writing the whole file */ + if (*tempname) + padding = stream_size_k < 90 ? 0 : stream_size_k < 1000 ? 128 : stream_size_k < 10000 ? 1024 : 8192; + + /* Write pages until the correct comment header */ + ogg_page og; + const int vorbis_serial = copy_up_to_codec(in, out, &oy, &og, *tempname ? 0 : offset, offset, VORBISNAME); + if (vorbis_serial <= OGGEDIT_EOF) { + res = vorbis_serial; + goto cleanup; + } + if ((res = copy_up_to_header(in, out, &oy, &og, vorbis_serial)) <= OGGEDIT_EOF) + goto cleanup; + const long pageno = write_vorbis_tags(out, vorbis_serial, vendor, num_tags, tags, padding, &codebooks); + 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, vorbis_serial, pageno)) < OGGEDIT_EOF) + goto cleanup; + if (rename(tempname, fname)) { + res = OGGEDIT_RENAME_FAILED; + goto cleanup; + } + } + + res = file_size(fname); + +cleanup: + ogg_packet_clear(&codebooks); + cleanup(in, out, &oy, vendor); + if (res < OGGEDIT_OK) + unlink(tempname); + return res; +} |