Skip to content

Commit

Permalink
Enable libFLAC to write to new file on changing metadata
Browse files Browse the repository at this point in the history
Previously, altering metadata could only be done rewriting the
file (or with a tempfile with the assumption the tempfile would
replace the existing file). However, there are good reasons to
write a new file instead. One API call is added for that, and
one is changed to enable this.
  • Loading branch information
ktmf01 committed Oct 28, 2024
1 parent 3725add commit 73b5502
Show file tree
Hide file tree
Showing 6 changed files with 417 additions and 276 deletions.
1 change: 1 addition & 0 deletions include/FLAC++/metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,7 @@ namespace FLAC {
bool check_if_tempfile_needed(bool use_padding); ///< See FLAC__metadata_chain_check_if_tempfile_needed().

bool write(bool use_padding = true, bool preserve_file_stats = false); ///< See FLAC__metadata_chain_write().
bool write(const char *filename, bool use_padding = false); ///< See FLAC__metadata_chain_write_new_file().
bool write(bool use_padding, ::FLAC__IOHandle handle, ::FLAC__IOCallbacks callbacks); ///< See FLAC__metadata_chain_write_with_callbacks().
bool write(bool use_padding, ::FLAC__IOHandle handle, ::FLAC__IOCallbacks callbacks, ::FLAC__IOHandle temp_handle, ::FLAC__IOCallbacks temp_callbacks); ///< See FLAC__metadata_chain_write_with_callbacks_and_tempfile().

Expand Down
40 changes: 29 additions & 11 deletions include/FLAC/metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -788,9 +788,6 @@ typedef enum {
/**< FLAC__metadata_chain_write_with_callbacks() was called when the
* chain write requires a tempfile; use
* FLAC__metadata_chain_write_with_callbacks_and_tempfile() instead.
* Or, FLAC__metadata_chain_write_with_callbacks_and_tempfile() was
* called when the chain write does not require a tempfile; use
* FLAC__metadata_chain_write_with_callbacks() instead.
* Always check FLAC__metadata_chain_check_if_tempfile_needed()
* before writing via callbacks. */

Expand Down Expand Up @@ -927,11 +924,11 @@ FLAC_API FLAC__bool FLAC__metadata_chain_read_ogg_with_callbacks(FLAC__Metadata_
* edited metadata back to the FLAC file does not require rewriting the
* entire file. If rewriting is required, then a temporary workfile is
* required. When writing metadata using callbacks, you must check
* this function to know whether to call
* FLAC__metadata_chain_write_with_callbacks() or
* FLAC__metadata_chain_write_with_callbacks_and_tempfile(). When
* writing with FLAC__metadata_chain_write(), the temporary file is
* handled internally.
* this function to know whether
* FLAC__metadata_chain_write_with_callbacks() can be used or
* FLAC__metadata_chain_write_with_callbacks_and_tempfile() is
* necessary. When writing with FLAC__metadata_chain_write(), the
* temporary file is handled internally.
*
* \param chain A pointer to an existing chain.
* \param use_padding
Expand Down Expand Up @@ -994,6 +991,28 @@ FLAC_API FLAC__bool FLAC__metadata_chain_check_if_tempfile_needed(FLAC__Metadata
*/
FLAC_API FLAC__bool FLAC__metadata_chain_write(FLAC__Metadata_Chain *chain, FLAC__bool use_padding, FLAC__bool preserve_file_stats);

/** Write all metadata out to a new FLAC file.
*
* This function works similar to FLAC__metadata_chain_write(), but is
* useful if writing to a new file is desired. This is more efficient
* than copying the file before changing it.
*
* For this write function to be used, the chain must have been read with
* FLAC__metadata_chain_read()/FLAC__metadata_chain_read_ogg(), not
* FLAC__metadata_chain_read_with_callbacks()/FLAC__metadata_chain_read_ogg_with_callbacks().
* See also FLAC__metadata_chain_write_with_callbacks_and_tempfile()
*
* \param chain A pointer to an existing chain.
* \param use_padding See FLAC__metadata_chain_write()
* \param filename The filename of the new file.
* \assert
* \code chain != NULL \endcode
* \retval FLAC__bool
* \c true if the write succeeded, else \c false. On failure,
* check the status with FLAC__metadata_chain_status().
*/
FLAC_API FLAC__bool FLAC__metadata_chain_write_new_file(FLAC__Metadata_Chain *chain, const char *filename, FLAC__bool use_padding);

/** Write all metadata out to a FLAC stream via callbacks.
*
* (See FLAC__metadata_chain_write() for the details on how padding is
Expand Down Expand Up @@ -1035,7 +1054,8 @@ FLAC_API FLAC__bool FLAC__metadata_chain_write_with_callbacks(FLAC__Metadata_Cha
* FLAC file to edit, and a temporary handle to which the new FLAC
* file will be written. It is the caller's job to move this temporary
* FLAC file on top of the original FLAC file to complete the metadata
* edit.
* edit. This version of the write-with-callbacks function can also be
* used if writing to a new file is desired anyway.
*
* The \a handle must be open for reading and be seekable. The
* equivalent minimum stdio fopen() file mode is \c "r" (or \c "rb"
Expand All @@ -1050,8 +1070,6 @@ FLAC_API FLAC__bool FLAC__metadata_chain_write_with_callbacks(FLAC__Metadata_Cha
* For this write function to be used, the chain must have been read with
* FLAC__metadata_chain_read_with_callbacks()/FLAC__metadata_chain_read_ogg_with_callbacks(),
* not FLAC__metadata_chain_read()/FLAC__metadata_chain_read_ogg().
* Also, FLAC__metadata_chain_check_if_tempfile_needed() must have returned
* \c true.
*
* \param chain A pointer to an existing chain.
* \param use_padding See FLAC__metadata_chain_write()
Expand Down
6 changes: 6 additions & 0 deletions src/libFLAC++/metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,12 @@ namespace FLAC {
return static_cast<bool>(::FLAC__metadata_chain_write(chain_, use_padding, preserve_file_stats));
}

bool Chain::write(const char* filename, bool use_padding)
{
FLAC__ASSERT(is_valid());
return static_cast<bool>(::FLAC__metadata_chain_write_new_file(chain_, filename, use_padding));
}

bool Chain::write(bool use_padding, ::FLAC__IOHandle handle, ::FLAC__IOCallbacks callbacks)
{
FLAC__ASSERT(is_valid());
Expand Down
84 changes: 68 additions & 16 deletions src/libFLAC/metadata_iterators.c
Original file line number Diff line number Diff line change
Expand Up @@ -1475,7 +1475,7 @@ static FLAC__bool chain_rewrite_metadata_in_place_(FLAC__Metadata_Chain *chain)
return ret;
}

static FLAC__bool chain_rewrite_file_(FLAC__Metadata_Chain *chain, const char *tempfile_path_prefix)
static FLAC__bool chain_rewrite_file_(FLAC__Metadata_Chain *chain, const char *tempfile_path_prefix, const char *filename)
{
FILE *f, *tempfile = NULL;
char *tempfilename;
Expand All @@ -1491,9 +1491,19 @@ static FLAC__bool chain_rewrite_file_(FLAC__Metadata_Chain *chain, const char *t
chain->status = FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE;
return false;
}
if(!open_tempfile_(chain->filename, tempfile_path_prefix, &tempfile, &tempfilename, &status)) {
chain->status = get_equivalent_status_(status);
goto err;
if(filename == NULL) {
if(!open_tempfile_(chain->filename, tempfile_path_prefix, &tempfile, &tempfilename, &status)) {
chain->status = get_equivalent_status_(status);
goto err;
}
}
else {
if(0 == (tempfile = flac_fopen(filename, "wb"))) {
(void)fclose(f);
chain->status = FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE;
return false;
}

}
if(!copy_n_bytes_from_file_(f, tempfile, chain->first_offset, &status)) {
chain->status = get_equivalent_status_(status);
Expand Down Expand Up @@ -1525,16 +1535,23 @@ static FLAC__bool chain_rewrite_file_(FLAC__Metadata_Chain *chain, const char *t

/* move the tempfile on top of the original */
(void)fclose(f);
if(!transport_tempfile_(chain->filename, &tempfile, &tempfilename, &status)) {
chain->status = get_equivalent_status_(status);
return false;
if(filename == NULL) {
if(!transport_tempfile_(chain->filename, &tempfile, &tempfilename, &status)) {
chain->status = get_equivalent_status_(status);
return false;
}
}
else
(void)fclose(tempfile);

return true;

err:
(void)fclose(f);
cleanup_tempfile_(&tempfile, &tempfilename);
if(filename == NULL)
cleanup_tempfile_(&tempfile, &tempfilename);
else
(void)fclose(tempfile);
return false;
}

Expand Down Expand Up @@ -1820,7 +1837,7 @@ FLAC_API FLAC__bool FLAC__metadata_chain_write(FLAC__Metadata_Chain *chain, FLAC
return false;
}
else {
if(!chain_rewrite_file_(chain, tempfile_path_prefix))
if(!chain_rewrite_file_(chain, tempfile_path_prefix, NULL))
return false;

/* recompute lengths and offsets */
Expand All @@ -1839,6 +1856,48 @@ FLAC_API FLAC__bool FLAC__metadata_chain_write(FLAC__Metadata_Chain *chain, FLAC
return true;
}

FLAC_API FLAC__bool FLAC__metadata_chain_write_new_file(FLAC__Metadata_Chain *chain, const char *filename, FLAC__bool use_padding)
{
FLAC__off_t current_length;

FLAC__ASSERT(0 != chain);

if (chain->is_ogg) { /* cannot write back to Ogg FLAC yet */
chain->status = FLAC__METADATA_CHAIN_STATUS_INTERNAL_ERROR;
return false;
}

if (0 == chain->filename) {
chain->status = FLAC__METADATA_CHAIN_STATUS_READ_WRITE_MISMATCH;
return false;
}

if (0 == filename) {
chain->status = FLAC__METADATA_CHAIN_STATUS_ILLEGAL_INPUT;
return false;
}

current_length = chain_prepare_for_write_(chain, use_padding);

/* a return value of 0 means there was an error; chain->status is already set */
if (0 == current_length)
return false;

if(!chain_rewrite_file_(chain, NULL, filename))
return false;

/* recompute lengths and offsets */
{
const FLAC__Metadata_Node *node;
chain->initial_length = current_length;
chain->last_offset = chain->first_offset;
for(node = chain->head; node; node = node->next)
chain->last_offset += (FLAC__STREAM_METADATA_HEADER_LENGTH + node->data->length);
}

return true;
}

FLAC_API FLAC__bool FLAC__metadata_chain_write_with_callbacks(FLAC__Metadata_Chain *chain, FLAC__bool use_padding, FLAC__IOHandle handle, FLAC__IOCallbacks callbacks)
{
FLAC__off_t current_length;
Expand Down Expand Up @@ -1901,19 +1960,12 @@ FLAC_API FLAC__bool FLAC__metadata_chain_write_with_callbacks_and_tempfile(FLAC_
return false;
}

if (!FLAC__metadata_chain_check_if_tempfile_needed(chain, use_padding)) {
chain->status = FLAC__METADATA_CHAIN_STATUS_WRONG_WRITE_CALL;
return false;
}

current_length = chain_prepare_for_write_(chain, use_padding);

/* a return value of 0 means there was an error; chain->status is already set */
if (0 == current_length)
return false;

FLAC__ASSERT(current_length != chain->initial_length);

/* rewind */
if(0 != callbacks.seek(handle, 0, SEEK_SET)) {
chain->status = FLAC__METADATA_CHAIN_STATUS_SEEK_ERROR;
Expand Down
Loading

0 comments on commit 73b5502

Please sign in to comment.