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