1 /* $Id: Header.xs 360 2005-11-26 08:02:13Z dsully $ */
2 
3 /* This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License
5  * as published by the Free Software Foundation; either version 2
6  * of the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16  *
17  * Chunks of this code have been borrowed and influenced from the FLAC source.
18  *
19  */
20 
21 #ifdef __cplusplus
22 "C" {
23 #endif
24 #include "EXTERN.h"
25 #include "perl.h"
26 #include "XSUB.h"
27 #ifdef __cplusplus
28 }
29 #endif
30 
31 #include <FLAC/all.h>
32 
33 /* for PRIu64 */
34 #include <inttypes.h>
35 
36 #define FLACHEADERFLAG "fLaC"
37 #define ID3HEADERFLAG  "ID3"
38 
39 #ifdef _MSC_VER
40 # define stat _stat
41 #endif
42 
43 /* strlen the length automatically */
44 #define my_hv_store(a,b,c)   (void)hv_store(a,b,strlen(b),c,0)
45 #define my_hv_store_ent(a,b,c) (void)hv_store_ent(a,b,c,0)
46 #define my_hv_fetch(a,b)     hv_fetch(a,b,strlen(b),0)
47 
_cuesheet_frame_to_msf(unsigned frame,unsigned * minutes,unsigned * seconds,unsigned * frames)48 void _cuesheet_frame_to_msf(unsigned frame, unsigned *minutes, unsigned *seconds, unsigned *frames) {
49 
50   *frames = frame % 75;
51   frame /= 75;
52   *seconds = frame % 60;
53   frame /= 60;
54   *minutes = frame;
55 }
56 
_read_metadata(HV * self,char * path,FLAC__StreamMetadata * block,unsigned block_number)57 void _read_metadata(HV *self, char *path, FLAC__StreamMetadata *block, unsigned block_number) {
58 
59   unsigned i;
60   int storePicture = 0;
61 
62   HV *pictureContainer = newHV();
63   AV *allpicturesContainer = NULL;
64 
65   switch (block->type) {
66 
67     case FLAC__METADATA_TYPE_STREAMINFO:
68     {
69       HV *info = newHV();
70       float totalSeconds;
71 
72       my_hv_store(info, "MINIMUMBLOCKSIZE", newSVuv(block->data.stream_info.min_blocksize));
73       my_hv_store(info, "MAXIMUMBLOCKSIZE", newSVuv(block->data.stream_info.max_blocksize));
74 
75       my_hv_store(info, "MINIMUMFRAMESIZE", newSVuv(block->data.stream_info.min_framesize));
76       my_hv_store(info, "MAXIMUMFRAMESIZE", newSVuv(block->data.stream_info.max_framesize));
77 
78       my_hv_store(info, "SAMPLERATE", newSVuv(block->data.stream_info.sample_rate));
79       my_hv_store(info, "NUMCHANNELS", newSVuv(block->data.stream_info.channels));
80       my_hv_store(info, "BITSPERSAMPLE", newSVuv(block->data.stream_info.bits_per_sample));
81       my_hv_store(info, "TOTALSAMPLES", newSVnv(block->data.stream_info.total_samples));
82 
83       if (block->data.stream_info.md5sum != NULL) {
84 
85         /* Initialize an SV with the first element,
86            and then append to it. If we don't do it this way, we get a "use of
87            uninitialized element" in subroutine warning. */
88         SV *md5 = newSVpvf("%02x", (unsigned)block->data.stream_info.md5sum[0]);
89 
90         for (i = 1; i < 16; i++) {
91           sv_catpvf(md5, "%02x", (unsigned)block->data.stream_info.md5sum[i]);
92         }
93 
94         my_hv_store(info, "MD5CHECKSUM", md5);
95       }
96 
97       my_hv_store(self, "info", newRV_noinc((SV*) info));
98 
99       /* Store some other metadata for backwards compatability with the original Audio::FLAC */
100       /* needs to be higher resolution */
101       totalSeconds = block->data.stream_info.total_samples / (float)block->data.stream_info.sample_rate;
102 
103       if (totalSeconds <= 0) {
104         warn("File: %s - %s\n%s\n",
105           path,
106           "totalSeconds is 0 - we couldn't find either TOTALSAMPLES or SAMPLERATE!",
107           "setting totalSeconds to 1 to avoid divide by zero error!"
108         );
109 
110         totalSeconds = 1;
111       }
112 
113       my_hv_store(self, "trackTotalLengthSeconds", newSVnv(totalSeconds));
114 
115       my_hv_store(self, "trackLengthMinutes", newSVnv((int)totalSeconds / 60));
116       my_hv_store(self, "trackLengthSeconds", newSVnv((int)totalSeconds % 60));
117       my_hv_store(self, "trackLengthFrames", newSVnv((totalSeconds - (int)totalSeconds) * 75));
118 
119       break;
120     }
121 
122     case FLAC__METADATA_TYPE_PADDING:
123     case FLAC__METADATA_TYPE_SEEKTABLE:
124       /* Don't handle these yet. */
125       break;
126 
127     case FLAC__METADATA_TYPE_APPLICATION:
128     {
129       if (block->data.application.id[0]) {
130 
131         HV *app   = newHV();
132         SV *tmpId = newSVpvf("%02x", (unsigned)block->data.application.id[0]);
133         SV *appId;
134 
135         for (i = 1; i < 4; i++) {
136           sv_catpvf(tmpId, "%02x", (unsigned)block->data.application.id[i]);
137         }
138 
139         /* Be compatible with the pure perl version */
140         appId = newSVpvf("%ld", strtol(SvPV_nolen(tmpId), NULL, 16));
141 
142         if (block->data.application.data != 0) {
143           my_hv_store_ent(app, appId, newSVpvn((char*)block->data.application.data, block->length));
144         }
145 
146         my_hv_store(self, "application",  newRV_noinc((SV*) app));
147 
148         SvREFCNT_dec(tmpId);
149         SvREFCNT_dec(appId);
150       }
151 
152       break;
153     }
154 
155     case FLAC__METADATA_TYPE_VORBIS_COMMENT:
156     {
157       AV *rawTagArray = newAV();
158       HV *tags = newHV();
159       SV **tag = NULL;
160       SV **separator = NULL;
161 
162       if (block->data.vorbis_comment.vendor_string.entry) {
163         my_hv_store(tags, "VENDOR", newSVpv((char*)block->data.vorbis_comment.vendor_string.entry, 0));
164       }
165 
166       for (i = 0; i < block->data.vorbis_comment.num_comments; i++) {
167 
168         if (!block->data.vorbis_comment.comments[i].entry || !block->data.vorbis_comment.comments[i].length) {
169           warn("Empty comment, skipping...\n");
170           continue;
171         }
172 
173         /* store the pointer location of the '=', poor man's split() */
174         char *entry = (char*)block->data.vorbis_comment.comments[i].entry;
175         char *half  = strchr(entry, '=');
176 
177         /* store the raw tags */
178         av_push(rawTagArray, newSVpv(entry, 0));
179 
180         if (half == NULL) {
181           warn("Comment \"%s\" missing \'=\', skipping...\n", entry);
182           continue;
183         }
184 
185         if (hv_exists(tags, entry, half - entry)) {
186           /* fetch the existing entry */
187           tag = hv_fetch(tags, entry, half - entry, 0);
188 
189           /* fetch the multi-value separator or default and append to the entry */
190           if (hv_exists(self, "separator", 9)) {
191             separator = hv_fetch(self, "separator", 9, 0);
192             sv_catsv(*tag, *separator);
193           } else {
194             sv_catpv(*tag, "/");
195           }
196 
197           /* concatenate with the new entry */
198           sv_catpv(*tag, half + 1);
199         } else {
200           (void)hv_store(tags, entry, half - entry, newSVpv(half + 1, 0), 0);
201         }
202       }
203 
204       my_hv_store(self, "tags", newRV_noinc((SV*) tags));
205       my_hv_store(self, "rawTags", newRV_noinc((SV*) rawTagArray));
206 
207       break;
208     }
209 
210     case FLAC__METADATA_TYPE_CUESHEET:
211     {
212       AV *cueArray = newAV();
213 
214       /*
215        * buffer for decimal representations of uint64_t values
216        *
217        * newSVpvf() and sv_catpvf() can't handle 64-bit values
218        * in some cases, so we need to do the conversion "manually"
219        * with sprintf() and the PRIu64 format macro for portability
220        *
221        * see http://bugs.debian.org/462249
222        *
223        * maximum string length: ceil(log10(2**64)) == 20 (+trailing \0)
224        */
225       char decimal[21];
226 
227       /* A lot of this comes from flac/src/share/grabbag/cuesheet.c */
228       const FLAC__StreamMetadata_CueSheet *cs;
229       unsigned track_num, index_num;
230 
231       cs = &block->data.cue_sheet;
232 
233       if (*(cs->media_catalog_number)) {
234         av_push(cueArray, newSVpvf("CATALOG %s\n", cs->media_catalog_number));
235       }
236 
237       av_push(cueArray, newSVpvf("FILE \"%s\" FLAC\n", path));
238 
239       for (track_num = 0; track_num < cs->num_tracks-1; track_num++) {
240 
241         const FLAC__StreamMetadata_CueSheet_Track *track = cs->tracks + track_num;
242 
243         av_push(cueArray, newSVpvf("  TRACK %02u %s\n",
244           (unsigned)track->number, track->type == 0? "AUDIO" : "DATA"
245         ));
246 
247         if (track->pre_emphasis) {
248           av_push(cueArray, newSVpv("    FLAGS PRE\n", 0));
249         }
250 
251         if (*(track->isrc)) {
252           av_push(cueArray, newSVpvf("    ISRC %s\n", track->isrc));
253         }
254 
255         for (index_num = 0; index_num < track->num_indices; index_num++) {
256 
257           const FLAC__StreamMetadata_CueSheet_Index *index = track->indices + index_num;
258 
259           SV *indexSV = newSVpvf("    INDEX %02u ", (unsigned)index->number);
260 
261           if (cs->is_cd) {
262 
263             unsigned logical_frame = (unsigned)((track->offset + index->offset) / (44100 / 75));
264             unsigned m, s, f;
265 
266             _cuesheet_frame_to_msf(logical_frame, &m, &s, &f);
267 
268             sv_catpvf(indexSV, "%02u:%02u:%02u\n", m, s, f);
269 
270           } else {
271             sprintf(decimal, "%"PRIu64, track->offset + index->offset);
272             sv_catpvf(indexSV, "%s\n", decimal);
273           }
274 
275 
276           av_push(cueArray, indexSV);
277         }
278       }
279 
280       sprintf(decimal, "%"PRIu64, cs->lead_in);
281       av_push(cueArray, newSVpvf("REM FLAC__lead-in %s\n", decimal));
282       sprintf(decimal, "%"PRIu64, cs->tracks[track_num].offset);
283       av_push(cueArray, newSVpvf("REM FLAC__lead-out %u %s\n",
284         (unsigned)cs->tracks[track_num].number, decimal)
285       );
286 
287       my_hv_store(self, "cuesheet",  newRV_noinc((SV*) cueArray));
288 
289       break;
290     }
291 
292 /* The PICTURE metadata block came about in FLAC 1.1.3 */
293 #ifdef FLAC_API_VERSION_CURRENT
294     case FLAC__METADATA_TYPE_PICTURE:
295     {
296       HV *picture = newHV();
297       SV *type;
298 
299       my_hv_store(picture, "mimeType", newSVpv(block->data.picture.mime_type, 0));
300       my_hv_store(picture, "description", newSVpv((const char*)block->data.picture.description, 0));
301       my_hv_store(picture, "width", newSViv(block->data.picture.width));
302       my_hv_store(picture, "height", newSViv(block->data.picture.height));
303       my_hv_store(picture, "depth", newSViv(block->data.picture.depth));
304       my_hv_store(picture, "colorIndex", newSViv(block->data.picture.colors));
305       my_hv_store(picture, "imageData", newSVpv((const char*)block->data.picture.data, block->data.picture.data_length));
306       my_hv_store(picture, "pictureType", newSViv(block->data.picture.type));
307 
308       type = newSViv(block->data.picture.type);
309 
310       my_hv_store_ent(pictureContainer, type, newRV_noinc((SV*) picture));
311 
312       SvREFCNT_dec(type);
313 
314       storePicture = 1;
315 
316       /* update allpictures */
317       if (hv_exists(self, "allpictures", 11)) {
318         allpicturesContainer = (AV *) SvRV(*my_hv_fetch(self, "allpictures"));
319       } else {
320         allpicturesContainer = newAV();
321 
322         /* store the 'allpictures' array */
323         my_hv_store(self, "allpictures", newRV_noinc((SV*) allpicturesContainer));
324       }
325 
326       av_push(allpicturesContainer, (SV*) newRV((SV*) picture));
327 
328       break;
329     }
330 #endif
331 
332     /* XXX- Just ignore for now */
333     default:
334       break;
335   }
336 
337   /* store the 'picture' hash */
338   if (storePicture && hv_scalar(pictureContainer)) {
339     my_hv_store(self, "picture", newRV_noinc((SV*) pictureContainer));
340   } else {
341     SvREFCNT_dec((SV*) pictureContainer);
342   }
343 }
344 
345 /* From src/metaflac/operations.c */
print_error_with_chain_status(FLAC__Metadata_Chain * chain,const char * format,...)346 void print_error_with_chain_status(FLAC__Metadata_Chain *chain, const char *format, ...) {
347 
348   const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain);
349   va_list args;
350 
351   FLAC__ASSERT(0 != format);
352 
353   va_start(args, format);
354   (void) vfprintf(stderr, format, args);
355   va_end(args);
356 
357   warn("status = \"%s\"\n", FLAC__Metadata_ChainStatusString[status]);
358 
359   if (status == FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE) {
360 
361     warn("The FLAC file could not be opened. Most likely the file does not exist or is not readable.");
362 
363   } else if (status == FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE) {
364 
365     warn("The file does not appear to be a FLAC file.");
366 
367   } else if (status == FLAC__METADATA_CHAIN_STATUS_NOT_WRITABLE) {
368 
369     warn("The FLAC file does not have write permissions.");
370 
371   } else if (status == FLAC__METADATA_CHAIN_STATUS_BAD_METADATA) {
372 
373     warn("The metadata to be writted does not conform to the FLAC metadata specifications.");
374 
375   } else if (status == FLAC__METADATA_CHAIN_STATUS_READ_ERROR) {
376 
377     warn("There was an error while reading the FLAC file.");
378 
379   } else if (status == FLAC__METADATA_CHAIN_STATUS_WRITE_ERROR) {
380 
381     warn("There was an error while writing FLAC file; most probably the disk is full.");
382 
383   } else if (status == FLAC__METADATA_CHAIN_STATUS_UNLINK_ERROR) {
384 
385     warn("There was an error removing the temporary FLAC file.");
386   }
387 }
388 
389 MODULE = Audio::FLAC::Header PACKAGE = Audio::FLAC::Header
390 
391 PROTOTYPES: DISABLE
392 
393 SV*
394 _new_XS(class, path)
395   char *class;
396   char *path;
397 
398   CODE:
399 
400   HV *self = newHV();
401   SV *obj_ref = newRV_noinc((SV*) self);
402 
403   /* Start to walk the metadata list */
404   FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
405 
406   if (chain == 0) {
407     die("Out of memory allocating chain");
408     XSRETURN_UNDEF;
409   }
410 
411   if (!FLAC__metadata_chain_read(chain, path)) {
412     print_error_with_chain_status(chain, "%s: ERROR: reading metadata", path);
413     XSRETURN_UNDEF;
414   }
415 
416   {
417     FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
418     FLAC__StreamMetadata *block = 0;
419     FLAC__bool ok = true;
420     unsigned block_number = 0;
421 
422     if (iterator == 0) {
423       die("out of memory allocating iterator");
424     }
425 
426     FLAC__metadata_iterator_init(iterator, chain);
427 
428     do {
429              block = FLAC__metadata_iterator_get_block(iterator);
430       ok &= (0 != block);
431 
432       if (!ok) {
433 
434         warn("%s: ERROR: couldn't get block from chain", path);
435 
436       } else {
437 
438         _read_metadata(self, path, block, block_number);
439       }
440 
441       block_number++;
442 
443     } while (ok && FLAC__metadata_iterator_next(iterator));
444 
445     FLAC__metadata_iterator_delete(iterator);
446   }
447 
448   FLAC__metadata_chain_delete(chain);
449 
450   /* Make sure tags is an empty HV if there were no VCs in the file */
451   if (!hv_exists(self, "tags", 4)) {
452     my_hv_store(self, "tags", newRV_noinc((SV*) newHV()));
453   }
454 
455   /* Find the offset of the start pos for audio blocks (ie: after metadata) */
456   {
457     unsigned int  is_last = 0;
458     unsigned char buf[4];
459     long len;
460     struct stat st;
461     float totalSeconds;
462     PerlIO *fh;
463 
464     if ((fh = PerlIO_open(path, "r")) == NULL) {
465       warn("Couldn't open file [%s] for reading!\n", path);
466       XSRETURN_UNDEF;
467     }
468 
469     if (PerlIO_read(fh, &buf, 4) == -1) {
470       warn("Couldn't read magic fLaC header!\n");
471       PerlIO_close(fh);
472       XSRETURN_UNDEF;
473     }
474 
475     if (memcmp(buf, ID3HEADERFLAG, 3) == 0) {
476 
477       unsigned id3size = 0;
478       int c = 0;
479 
480       /* How big is the ID3 header? Skip the next two bytes */
481       if (PerlIO_read(fh, &buf, 2) == -1) {
482         warn("Couldn't read ID3 header length!\n");
483         PerlIO_close(fh);
484         XSRETURN_UNDEF;
485       }
486 
487       /* The size of the ID3 tag is a 'synchsafe' 4-byte uint */
488       for (c = 0; c < 4; c++) {
489 
490         if (PerlIO_read(fh, &buf, 1) == -1 || buf[0] & 0x80) {
491           warn("Couldn't read ID3 header length (syncsafe)!\n");
492           PerlIO_close(fh);
493           XSRETURN_UNDEF;
494         }
495 
496         id3size <<= 7;
497         id3size |= (buf[0] & 0x7f);
498       }
499 
500       if (PerlIO_seek(fh, id3size, SEEK_CUR) < 0) {
501         warn("Couldn't seek past ID3 header!\n");
502         PerlIO_close(fh);
503         XSRETURN_UNDEF;
504       }
505 
506       if (PerlIO_read(fh, &buf, 4) == -1) {
507         warn("Couldn't read magic fLaC header!\n");
508         PerlIO_close(fh);
509         XSRETURN_UNDEF;
510       }
511     }
512 
513     if (memcmp(buf, FLACHEADERFLAG, 4)) {
514       warn("Couldn't read magic fLaC header - got gibberish instead!\n");
515       PerlIO_close(fh);
516       XSRETURN_UNDEF;
517     }
518 
519     while (!is_last) {
520 
521       if (PerlIO_read(fh, &buf, 4) != 4) {
522         warn("Couldn't read 4 bytes of the metadata block!\n");
523         PerlIO_close(fh);
524         XSRETURN_UNDEF;
525       }
526 
527       is_last = (unsigned int)(buf[0] & 0x80);
528 
529       len = (long)((buf[1] << 16) | (buf[2] << 8) | (buf[3]));
530 
531       PerlIO_seek(fh, len, SEEK_CUR);
532     }
533 
534     len = PerlIO_tell(fh);
535     PerlIO_close(fh);
536 
537     my_hv_store(self, "startAudioData", newSVnv(len));
538 
539     /* Now calculate the bit rate and file size */
540     totalSeconds = (float)SvIV(*(my_hv_fetch(self, "trackTotalLengthSeconds")));
541 
542     /* Find the file size */
543     if (stat(path, &st) == 0) {
544       my_hv_store(self, "fileSize", newSViv(st.st_size));
545     } else {
546       warn("Couldn't stat file: [%s], might be more problems ahead!", path);
547     }
548 
549     my_hv_store(self, "bitRate", newSVnv(8.0 * (st.st_size - len) / totalSeconds));
550   }
551 
552   my_hv_store(self, "filename", newSVpv(path, 0));
553 
554   /* Bless the hashref to create a class object */
555   sv_bless(obj_ref, gv_stashpv(class, FALSE));
556 
557   RETVAL = obj_ref;
558 
559   OUTPUT:
560   RETVAL
561 
562 SV*
563 _write_XS(obj)
564   SV* obj
565 
566   CODE:
567 
568   FLAC__bool ok = true;
569 
570   HE *he;
571   HV *self = (HV *) SvRV(obj);
572   HV *tags = (HV *) SvRV(*(my_hv_fetch(self, "tags")));
573 
574   char *path = (char *) SvPV_nolen(*(my_hv_fetch(self, "filename")));
575 
576   FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
577 
578   if (chain == 0) {
579     die("Out of memory allocating chain");
580     XSRETURN_UNDEF;
581   }
582 
583   if (!FLAC__metadata_chain_read(chain, path)) {
584     print_error_with_chain_status(chain, "%s: ERROR: reading metadata", path);
585     XSRETURN_UNDEF;
586   }
587 
588   FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
589   FLAC__StreamMetadata *block = 0;
590   FLAC__bool found_vc_block = false;
591 
592   if (iterator == 0) {
593     die("out of memory allocating iterator");
594   }
595 
596   FLAC__metadata_iterator_init(iterator, chain);
597 
598   do {
599     block = FLAC__metadata_iterator_get_block(iterator);
600 
601     if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
602       found_vc_block = true;
603     }
604 
605   } while (!found_vc_block && FLAC__metadata_iterator_next(iterator));
606 
607   if (found_vc_block) {
608 
609     /* Empty out the existing block */
610     if (0 != block->data.vorbis_comment.comments) {
611 
612       FLAC__ASSERT(block->data.vorbis_comment.num_comments > 0);
613 
614       if (!FLAC__metadata_object_vorbiscomment_resize_comments(block, 0)) {
615 
616         die("%s: ERROR: memory allocation failure\n", path);
617       }
618 
619     } else {
620 
621       FLAC__ASSERT(block->data.vorbis_comment.num_comments == 0);
622     }
623 
624   } else {
625 
626     /* create a new block if necessary */
627     block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
628 
629     if (0 == block) {
630       die("out of memory allocating VORBIS_COMMENT block");
631     }
632 
633     while (FLAC__metadata_iterator_next(iterator));
634 
635     if (!FLAC__metadata_iterator_insert_block_after(iterator, block)) {
636 
637       print_error_with_chain_status(chain, "%s: ERROR: adding new VORBIS_COMMENT block to metadata", path);
638       XSRETURN_UNDEF;
639     }
640 
641     /* iterator is left pointing to new block */
642     FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block);
643   }
644 
645   FLAC__StreamMetadata_VorbisComment_Entry entry = { 0 };
646   FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true);
647 
648   if (hv_iterinit(tags)) {
649 
650     while ((he = hv_iternext(tags))) {
651 
652       FLAC__StreamMetadata_VorbisComment_Entry entry;
653 
654       char *key = HePV(he, PL_na);
655       char *val = SvPV_nolen(HeVAL(he));
656       char *ent = form("%s=%s", key, val);
657 
658       if (ent == NULL) {
659         warn("Couldn't create key/value pair!\n");
660         XSRETURN_UNDEF;
661       }
662 
663       if (strEQ(key, "VENDOR")) {
664         entry.entry = (FLAC__byte *)val;
665       } else {
666         entry.entry = (FLAC__byte *)ent;
667       }
668 
669       entry.length = strlen((const char *)entry.entry);
670 
671       if (strEQ(key, "VENDOR")) {
672 
673         if (!FLAC__metadata_object_vorbiscomment_set_vendor_string(block, entry, /*copy=*/true)) {
674           warn("%s: ERROR: memory allocation failure\n", path);
675           XSRETURN_UNDEF;
676         }
677 
678       } else {
679 
680         if (!FLAC__format_vorbiscomment_entry_is_legal(entry.entry, entry.length)) {
681 
682           warn("%s: ERROR: tag value for '%s' is not valid UTF-8\n", path, ent);
683           XSRETURN_UNDEF;
684         }
685 
686         if (!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) {
687 
688           warn("%s: ERROR: memory allocation failure\n", path);
689           XSRETURN_UNDEF;
690         }
691       }
692     }
693   }
694 
695   FLAC__metadata_iterator_delete(iterator);
696   FLAC__metadata_chain_sort_padding(chain);
697 
698   ok = FLAC__metadata_chain_write(chain, /* padding */true, /*modtime*/ false);
699 
700   if (!ok) {
701     print_error_with_chain_status(chain, "%s: ERROR: writing FLAC file", path);
702     RETVAL = &PL_sv_no;
703   } else {
704     RETVAL = &PL_sv_yes;
705   }
706 
707   FLAC__metadata_chain_delete(chain);
708 
709   OUTPUT:
710   RETVAL
711