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; }