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_opus_metadata(DB_FILE *in, const char *fname, const off_t offset, const off_t stream_size, const int output_gain, const uint32_t num_tags, char **tags) { off_t res; char tempname[PATH_MAX] = ""; 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; } /* Should we write the tags packet directly into the existing file ... */ const long tags_packet_size = check_opus_header(in, &oy, offset, &vendor); if (tags_packet_size <= OGGEDIT_EOF) { res = tags_packet_size; goto cleanup; } const int64_t metadata_size = strlen(TAGMAGIC) + vc_size(vendor, num_tags, tags); int64_t padding = tags_packet_size - metadata_size; const off_t file_size_k = in->vfs->getlength(in) / 1000; const off_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) { res = open_temp_file(fname, tempname, &out); if (res) { 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 we reach the correct OpusHead, then write OpusTags */ ogg_page og; int64_t opus_serial = copy_up_to_codec(in, out, &oy, &og, *tempname ? 0 : offset, offset, OPUSNAME); if (opus_serial <= OGGEDIT_EOF) { res = opus_serial; goto cleanup; } if (output_gain > INT_MIN) { og.body[16] = output_gain & 0xFF; og.body[17] = output_gain >> 8 & 0xFF; ogg_page_checksum_set(&og); }
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) { res = open_temp_file(fname, tempname, &out); if (res) { 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; }
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) { res = open_temp_file(fname, tempname, &out); if (res) { 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; int64_t 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; } vorbis_serial = copy_up_to_header(in, out, &oy, &og, vorbis_serial); if (vorbis_serial <= OGGEDIT_EOF) { res = vorbis_serial; 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) { vorbis_serial = copy_remaining_pages(in, out, &oy, vorbis_serial, pageno); if (vorbis_serial <= OGGEDIT_EOF) { res = vorbis_serial; 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; }