1 /* EasyTAG - tag editor for audio files
2  * Copyright (C) 2014 David King <amigadave@amigadave.com>
3  * Copyright (C) 2000-2001 Michael Smith <msmith@xiph.org>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 51
17  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h" /* For definition of ENABLE_OGG. */
21 
22 #ifdef ENABLE_OGG
23 #include <gio/gio.h>
24 #include <glib/gi18n.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ogg/ogg.h>
28 #include <vorbis/codec.h>
29 
30 #include "vcedit.h"
31 #include "ogg_header.h"
32 
33 #define CHUNKSIZE 4096
34 
35 struct _EtOggState
36 {
37     /*< private >*/
38     GFileInputStream *in;
39 #ifdef ENABLE_SPEEX
40     SpeexHeader *si;
41 #endif
42 #ifdef ENABLE_OPUS
43     OpusHead *oi;
44 #endif
45     vorbis_info *vi;
46     ogg_stream_state *os;
47     ogg_sync_state *oy;
48     vorbis_comment *vc;
49     EtOggKind oggtype;
50     glong serial;
51     guchar *mainbuf;
52     guchar *bookbuf;
53     gchar *vendor;
54     gint mainlen;
55     gint booklen;
56     gint prevW;
57     gint extrapage;
58     gint eosin;
59 };
60 
61 EtOggState *
vcedit_new_state(void)62 vcedit_new_state (void)
63 {
64     EtOggState *state = g_slice_new0 (EtOggState);
65     state->oggtype = ET_OGG_KIND_UNKNOWN;
66 
67     return state;
68 }
69 
70 vorbis_comment *
vcedit_comments(EtOggState * state)71 vcedit_comments (EtOggState *state)
72 {
73     return state->vc;
74 }
75 
76 #ifdef ENABLE_SPEEX
77 const SpeexHeader *
vcedit_speex_header(EtOggState * state)78 vcedit_speex_header (EtOggState *state)
79 {
80     return state->si;
81 }
82 #endif /* ENABLE_SPEEX */
83 
84 static void
vcedit_clear_internals(EtOggState * state)85 vcedit_clear_internals (EtOggState *state)
86 {
87     if (state->vc)
88     {
89         vorbis_comment_clear (state->vc);
90         g_slice_free (vorbis_comment, state->vc);
91     }
92 
93     if (state->os)
94     {
95         ogg_stream_clear (state->os);
96         g_slice_free (ogg_stream_state, state->os);
97     }
98 
99     if (state->oy)
100     {
101         ogg_sync_clear (state->oy);
102         g_slice_free (ogg_sync_state, state->oy);
103     }
104 
105     g_free (state->vendor);
106     g_free (state->mainbuf);
107     g_free (state->bookbuf);
108 
109     if (state->vi)
110     {
111         vorbis_info_clear (state->vi);
112         g_slice_free (vorbis_info, state->vi);
113     }
114 
115 #ifdef ENABLE_SPEEX
116     if (state->si)
117     {
118         speex_header_free (state->si);
119     }
120 #endif
121 
122 #ifdef ENABLE_OPUS
123     if (state->oi)
124     {
125         g_slice_free (OpusHead, state->oi);
126     }
127 #endif /* ENABLE_OPUS */
128 
129     if (state->in)
130     {
131         g_object_unref (state->in);
132     }
133 
134     memset (state, 0, sizeof (*state));
135 }
136 
137 void
vcedit_clear(EtOggState * state)138 vcedit_clear (EtOggState *state)
139 {
140     if (state)
141     {
142         vcedit_clear_internals (state);
143         g_slice_free (EtOggState, state);
144     }
145 }
146 
147 /* Next two functions pulled straight from libvorbis, apart from one change
148  * - we don't want to overwrite the vendor string.
149  */
150 static void
_v_writestring(oggpack_buffer * o,const char * s,int len)151 _v_writestring (oggpack_buffer *o,
152                 const char *s,
153                 int len)
154 {
155     while (len--)
156     {
157         oggpack_write (o, *s++, 8);
158     }
159 }
160 
161 static int
_commentheader_out(EtOggState * state,ogg_packet * op)162 _commentheader_out (EtOggState *state,
163                     ogg_packet *op)
164 {
165     vorbis_comment *vc = state->vc;
166     const gchar *vendor = state->vendor;
167     oggpack_buffer opb;
168 
169     oggpack_writeinit (&opb);
170 
171     if (state->oggtype == ET_OGG_KIND_VORBIS)
172     {
173         /* preamble */
174         oggpack_write (&opb, 0x03, 8);
175         _v_writestring (&opb, "vorbis", 6);
176     }
177 #ifdef ENABLE_OPUS
178     else if (state->oggtype == ET_OGG_KIND_OPUS)
179     {
180         _v_writestring (&opb, "OpusTags", 8);
181     }
182 #endif
183 
184     /* vendor */
185     oggpack_write (&opb, strlen (vendor), 32);
186     _v_writestring (&opb,vendor, strlen (vendor));
187 
188     /* comments */
189     oggpack_write (&opb, vc->comments, 32);
190 
191     if (vc->comments)
192     {
193         int i;
194 
195         for (i=0; i < vc->comments; i++)
196         {
197             if (vc->user_comments[i])
198             {
199                 oggpack_write (&opb, vc->comment_lengths[i], 32);
200                 _v_writestring (&opb, vc->user_comments[i],
201                                 vc->comment_lengths[i]);
202             }
203             else
204             {
205                 oggpack_write (&opb, 0, 32);
206             }
207         }
208     }
209 
210     oggpack_write (&opb, 1, 1);
211 
212     op->packet = _ogg_malloc (oggpack_bytes (&opb));
213     memcpy (op->packet, opb.buffer, oggpack_bytes (&opb));
214 
215     op->bytes = oggpack_bytes (&opb);
216     op->b_o_s = 0;
217     op->e_o_s = 0;
218     op->granulepos = 0;
219 
220     if (state->oggtype == ET_OGG_KIND_VORBIS)
221     {
222         op->packetno = 1;
223     }
224 
225     oggpack_writeclear (&opb);
226     return 0;
227 }
228 
229 static int
_blocksize(EtOggState * s,ogg_packet * p)230 _blocksize (EtOggState *s,
231             ogg_packet *p)
232 {
233     int this = vorbis_packet_blocksize (s->vi, p);
234     int ret = (this + s->prevW) / 4;
235 
236     if(!s->prevW)
237     {
238         s->prevW = this;
239         return 0;
240     }
241 
242     s->prevW = this;
243     return ret;
244 }
245 
246 static gboolean
_fetch_next_packet(EtOggState * s,ogg_packet * p,ogg_page * page,GError ** error)247 _fetch_next_packet (EtOggState *s,
248                     ogg_packet *p,
249                     ogg_page *page,
250                     GError **error)
251 {
252     int result;
253     char *buffer;
254     gssize bytes;
255 
256     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
257 
258     result = ogg_stream_packetout (s->os, p);
259 
260     if (result > 0)
261     {
262         return TRUE;
263     }
264     else
265     {
266         if (s->eosin)
267         {
268             g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_EOS,
269                          "Page reached end of logical bitstream");
270             g_assert (error == NULL || *error != NULL);
271             return FALSE;
272         }
273 
274         while (ogg_sync_pageout (s->oy, page) <= 0)
275         {
276             buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
277             bytes = g_input_stream_read (G_INPUT_STREAM (s->in), buffer,
278                                          CHUNKSIZE, NULL, error);
279             ogg_sync_wrote (s->oy, bytes);
280 
281             if(bytes == 0)
282             {
283                 g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_EOF,
284                              "Reached end of file");
285                 g_assert (error == NULL || *error != NULL);
286                 return FALSE;
287             }
288             else if (bytes == -1)
289             {
290                 g_assert (error == NULL || *error != NULL);
291                 return FALSE;
292             }
293         }
294 
295         if (ogg_page_eos (page))
296         {
297             s->eosin = 1;
298         }
299         else if (ogg_page_serialno (page) != s->serial)
300         {
301             g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_SN,
302                          "Page serial number and state serial number doesn't match");
303             s->eosin = 1;
304             s->extrapage = 1;
305             g_assert (error == NULL || *error != NULL);
306             return FALSE;
307         }
308 
309         g_assert (error == NULL || *error == NULL);
310         ogg_stream_pagein (s->os, page);
311         return _fetch_next_packet (s, p, page, error);
312     }
313 }
314 
315 /*
316  * Next functions pulled straight from libvorbis,
317  */
318 static void
_v_readstring(oggpack_buffer * o,char * buf,int bytes)319 _v_readstring (oggpack_buffer *o,
320                char *buf,
321                int bytes)
322 {
323     while (bytes--)
324     {
325         *buf++ = oggpack_read (o, 8);
326     }
327 }
328 
329 /*
330  * Next functions pulled straight from libvorbis, apart from one change
331  * - disabled the EOP check
332  */
333 static int
_speex_unpack_comment(vorbis_comment * vc,oggpack_buffer * opb)334 _speex_unpack_comment (vorbis_comment *vc,
335                        oggpack_buffer *opb)
336 {
337     int i;
338     int vendorlen = oggpack_read (opb, 32);
339 
340     if (vendorlen < 0)
341     {
342         goto err_out;
343     }
344 
345     vc->vendor = _ogg_calloc (vendorlen + 1, 1);
346     _v_readstring (opb, vc->vendor, vendorlen);
347 
348     vc->comments = oggpack_read (opb, 32);
349 
350     if (vc->comments < 0)
351     {
352         goto err_out;
353     }
354 
355     vc->user_comments = _ogg_calloc (vc->comments + 1,
356                                      sizeof (*vc->user_comments));
357     vc->comment_lengths = _ogg_calloc (vc->comments + 1,
358                                        sizeof (*vc->comment_lengths));
359 
360     for (i = 0; i < vc->comments; i++)
361     {
362         int len = oggpack_read (opb, 32);
363 
364         if (len < 0)
365         {
366             goto err_out;
367         }
368 
369         vc->comment_lengths[i] = len;
370         vc->user_comments[i] = _ogg_calloc (len + 1, 1);
371         _v_readstring (opb, vc->user_comments[i], len);
372     }
373 
374     /*if(oggpack_read(opb,1)!=1)goto err_out; EOP check */
375     return(0);
376 
377 err_out:
378     vorbis_comment_clear(vc);
379     return(1);
380 }
381 
382 gboolean
vcedit_open(EtOggState * state,GFile * file,GError ** error)383 vcedit_open (EtOggState *state,
384              GFile *file,
385              GError **error)
386 {
387     char *buffer;
388     gssize bytes;
389     int i;
390     int chunks = 0;
391     int headerpackets = 0;
392     oggpack_buffer opb;
393     ogg_packet *header;
394     ogg_packet  header_main;
395     ogg_packet  header_comments;
396     ogg_packet  header_codebooks;
397     ogg_page    og;
398     GFileInputStream *istream;
399 
400     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
401 
402     istream = g_file_read (file, NULL, error);
403 
404     if (!istream)
405     {
406         g_assert (error == NULL || *error != NULL);
407         return FALSE;
408     }
409 
410     state->in = istream;
411     state->oy = g_slice_new (ogg_sync_state);
412     ogg_sync_init (state->oy);
413 
414     while(1)
415     {
416         buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
417         bytes = g_input_stream_read (G_INPUT_STREAM (state->in), buffer,
418                                      CHUNKSIZE, NULL, error);
419         if (bytes == -1)
420         {
421             goto err;
422         }
423 
424         ogg_sync_wrote(state->oy, bytes);
425 
426         if(ogg_sync_pageout(state->oy, &og) == 1)
427             break;
428 
429         if(chunks++ >= 10) /* Bail if we don't find data in the first 40 kB */
430         {
431             if(bytes<CHUNKSIZE)
432                 g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_TRUNC,
433                              "Input truncated or empty");
434             else
435                 g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_NOTOGG,
436                              "Input is not an Ogg bitstream");
437             goto err;
438         }
439     }
440 
441     state->serial = ogg_page_serialno(&og);
442 
443     state->os = g_slice_new (ogg_stream_state);
444     ogg_stream_init (state->os, state->serial);
445 
446     state->vi = g_slice_new (vorbis_info);
447     vorbis_info_init (state->vi);
448 
449     state->vc = g_slice_new (vorbis_comment);
450     vorbis_comment_init (state->vc);
451 
452     if (ogg_stream_pagein (state->os, &og) < 0)
453     {
454         g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_PAGE,
455                      "Error reading first page of Ogg bitstream");
456         goto err;
457     }
458 
459     if (ogg_stream_packetout (state->os, &header_main) != 1)
460     {
461         g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_HEADER,
462                      "Error reading initial header packet");
463         goto err;
464     }
465 
466     /* Save the main header first, it seems speex_packet_to_header() munges
467      * it. */
468     state->mainlen = header_main.bytes;
469     state->mainbuf = g_memdup (header_main.packet, header_main.bytes);
470 
471     state->oggtype = ET_OGG_KIND_UNKNOWN;
472 
473     if(vorbis_synthesis_headerin (state->vi, state->vc, &header_main) == 0)
474     {
475         state->oggtype = ET_OGG_KIND_VORBIS;
476     }
477     else
478     {
479 #ifdef ENABLE_SPEEX
480         /* Done after "Ogg test" to avoid to display an error message in
481          * function speex_packet_to_header() when the file is not Speex. */
482         if((state->si = speex_packet_to_header ((char*)(&header_main)->packet,
483                                                 (&header_main)->bytes)))
484         {
485             state->oggtype = ET_OGG_KIND_SPEEX;
486         }
487 #endif
488 
489 #ifdef ENABLE_OPUS
490         if (state->oggtype == ET_OGG_KIND_UNKNOWN)
491         {
492             state->oi = g_slice_new (OpusHead);
493 
494             if (opus_head_parse (state->oi,
495                                  (unsigned char*)(&header_main)->packet,
496                                  (&header_main)->bytes) == 0)
497             {
498                 state->oggtype = ET_OGG_KIND_OPUS;
499             }
500         }
501 #endif
502     }
503 
504     if (state->oggtype == ET_OGG_KIND_UNKNOWN)
505     {
506         g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_INVALID,
507                      "Ogg bitstream contains unknown data");
508         goto err;
509     }
510 
511     switch (state->oggtype)
512     {
513         case ET_OGG_KIND_VORBIS:
514             header = &header_comments;
515             headerpackets = 3;
516             break;
517 #ifdef ENABLE_SPEEX
518         case ET_OGG_KIND_SPEEX:
519             header = &header_comments;
520             headerpackets = 2 + state->si->extra_headers;
521             break;
522 #endif
523 #ifdef ENABLE_OPUS
524         case ET_OGG_KIND_OPUS:
525             header = &header_comments;
526             headerpackets = 2;
527 #endif
528             break;
529 #ifndef ENABLE_SPEEX
530         case ET_OGG_KIND_SPEEX:
531 #endif
532 #ifndef ENABLE_OPUS
533         case ET_OGG_KIND_OPUS:
534 #endif
535         case ET_OGG_KIND_UNKNOWN:
536         default:
537             g_assert_not_reached ();
538             break;
539     }
540 
541     i = 1;
542 
543     while (i < headerpackets)
544     {
545         while (i < headerpackets)
546         {
547             int result = ogg_sync_pageout (state->oy, &og);
548 
549             if (result == 0)
550             {
551                 break; /* Too little data so far */
552             }
553             else if (result == 1)
554             {
555                 ogg_stream_pagein (state->os, &og);
556 
557                 while (i < headerpackets)
558                 {
559                     result = ogg_stream_packetout(state->os, header);
560                     if (result == 0)
561                     {
562                         break;
563                     }
564 
565                     if (result == -1)
566                     {
567                         g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_CORRUPT,
568                                      "Corrupt secondary header");
569                         goto err;
570                     }
571                     switch (state->oggtype)
572                     {
573                         case ET_OGG_KIND_VORBIS:
574                             vorbis_synthesis_headerin (state->vi, state->vc,
575                                                        header);
576                             switch (i)
577                             {
578                                 /* 0 packet was the Vorbis header. */
579                                 case 1:
580                                     header = &header_codebooks;
581                                     break;
582                                 case 2:
583                                     state->booklen = header->bytes;
584                                     state->bookbuf = g_memdup (header->packet,
585                                                                header->bytes);
586                                     break;
587                                 default:
588                                     g_assert_not_reached ();
589                                     break;
590                             }
591                             break;
592                         case ET_OGG_KIND_SPEEX:
593                             switch (i)
594                             {
595                                 /* 0 packet was the Speex header. */
596                                 case 1:
597                                     oggpack_readinit(&opb,header->packet,header->bytes);
598                                     _speex_unpack_comment(state->vc,&opb);
599                                     break;
600                                 default: /* FIXME. */
601                                     g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_EXTRA,
602                                                  "Need to save extra headers");
603                                     goto err;
604                                     break;
605                             }
606                             break;
607 #ifdef ENABLE_OPUS
608                         case ET_OGG_KIND_OPUS:
609                             switch (opus_tags_parse ((OpusTags *)state->vc,
610                                                      header->packet,
611                                                      header->bytes))
612                             {
613                                 case 0:
614                                     break;
615 
616                                 case OP_ENOTFORMAT:
617                                     g_set_error (error, ET_OGG_ERROR,
618                                                  ET_OGG_ERROR_HEADER,
619                                                  "Ogg Opus tags do not start with \"OpusTags\"");
620                                     goto err;
621                                     break;
622 
623                                 case OP_EFAULT:
624                                     g_set_error (error, ET_OGG_ERROR,
625                                                  ET_OGG_ERROR_HEADER,
626                                                  "Not enough memory to store Ogg Opus tags");
627                                     goto err;
628                                     break;
629 
630                                 case OP_EBADHEADER:
631                                     g_set_error (error, ET_OGG_ERROR,
632                                                  ET_OGG_ERROR_HEADER,
633                                                  "Ogg Opus tags do not follow the specification");
634                                     goto err;
635                                     break;
636                                 default:
637                                     g_assert_not_reached ();
638                                     break;
639                             }
640                             break;
641 #endif
642 #ifndef ENABLE_OPUS
643                         case ET_OGG_KIND_OPUS:
644 #endif
645                         case ET_OGG_KIND_UNKNOWN:
646                         default:
647                             g_assert_not_reached ();
648                             break;
649                     }
650 
651                     i++;
652                 }
653             }
654         }
655 
656         buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
657         bytes = g_input_stream_read (G_INPUT_STREAM (state->in), buffer,
658                                      CHUNKSIZE, NULL, error);
659 
660         if (bytes == -1)
661         {
662             goto err;
663         }
664 
665         if (bytes == 0 && i < 2)
666         {
667             g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_VORBIS,
668                          "EOF before end of Vorbis headers");
669             goto err;
670         }
671         ogg_sync_wrote (state->oy, bytes);
672     }
673 
674     /* Copy the vendor tag */
675     state->vendor = g_strdup (state->vc->vendor);
676 
677     /* Headers are done! */
678     g_assert (error == NULL || *error == NULL);
679 
680     return TRUE;
681 
682 err:
683     g_assert (error == NULL || *error != NULL);
684     vcedit_clear_internals (state);
685     return FALSE;
686 }
687 
688 gboolean
vcedit_write(EtOggState * state,GFile * file,GError ** error)689 vcedit_write (EtOggState *state,
690               GFile *file,
691               GError **error)
692 {
693     ogg_stream_state streamout;
694     ogg_packet header_main;
695     ogg_packet header_comments;
696     ogg_packet header_codebooks;
697 
698     ogg_page ogout, ogin;
699     ogg_packet op;
700     ogg_int64_t granpos = 0;
701     int result;
702     char *buffer;
703     int bytes;
704     int needflush = 0, needout = 0;
705     GOutputStream *ostream;
706     gchar *buf;
707     gsize size;
708     GFileInfo *fileinfo;
709 
710     g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
711 
712     fileinfo = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
713                                   G_FILE_QUERY_INFO_NONE, NULL, error);
714     if (!fileinfo)
715     {
716         g_assert (error == NULL || *error != NULL);
717         return FALSE;
718     }
719 
720     buf = g_malloc (g_file_info_get_size (fileinfo));
721     ostream = g_memory_output_stream_new (buf,
722                                           g_file_info_get_size (fileinfo),
723                                           g_realloc, g_free);
724     g_object_unref (fileinfo);
725 
726     state->eosin = 0;
727     state->extrapage = 0;
728 
729     header_main.bytes = state->mainlen;
730     header_main.packet = state->mainbuf;
731     header_main.b_o_s = 1;
732     header_main.e_o_s = 0;
733     header_main.granulepos = 0;
734 
735     header_codebooks.bytes = state->booklen;
736     header_codebooks.packet = state->bookbuf;
737     header_codebooks.b_o_s = 0;
738     header_codebooks.e_o_s = 0;
739     header_codebooks.granulepos = 0;
740 
741     ogg_stream_init (&streamout, state->serial);
742 
743     _commentheader_out (state, &header_comments);
744 
745     ogg_stream_packetin (&streamout, &header_main);
746     ogg_stream_packetin (&streamout, &header_comments);
747 
748     if (state->oggtype == ET_OGG_KIND_VORBIS)
749     {
750         ogg_stream_packetin (&streamout, &header_codebooks);
751     }
752 
753     while ((result = ogg_stream_flush (&streamout, &ogout)))
754     {
755         gsize bytes_written;
756 
757         if (!g_output_stream_write_all (ostream, ogout.header,
758                                         ogout.header_len, &bytes_written, NULL,
759                                         error))
760         {
761             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of data "
762                      "were written", bytes_written, ogout.header_len);
763             g_assert (error == NULL || *error != NULL);
764             goto cleanup;
765         }
766 
767         if (!g_output_stream_write_all (ostream, ogout.body, ogout.body_len,
768                                         &bytes_written, NULL, error))
769         {
770             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of data "
771                      "were written", bytes_written, ogout.body_len);
772             g_assert (error == NULL || *error != NULL);
773             goto cleanup;
774         }
775     }
776 
777     while (_fetch_next_packet (state, &op, &ogin, error))
778     {
779         if (needflush)
780         {
781             if (ogg_stream_flush (&streamout, &ogout))
782             {
783                 gsize bytes_written;
784 
785                 if (!g_output_stream_write_all (ostream, ogout.header,
786                                                 ogout.header_len, &bytes_written,
787                                                 NULL, error))
788                 {
789                     g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of "
790                              "data were written", bytes_written, ogout.header_len);
791                     g_assert (error == NULL || *error != NULL);
792                     goto cleanup;
793                 }
794 
795                 if (!g_output_stream_write_all (ostream, ogout.body,
796                                                 ogout.body_len, &bytes_written,
797                                                 NULL, error))
798                 {
799                     g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of "
800                              "data were written", bytes_written, ogout.body_len);
801                     g_assert (error == NULL || *error != NULL);
802                     goto cleanup;
803                 }
804             }
805         }
806         else if (needout)
807         {
808             if(ogg_stream_pageout (&streamout, &ogout))
809             {
810                 gsize bytes_written;
811 
812                 if (!g_output_stream_write_all (ostream, ogout.header,
813                                                 ogout.header_len,
814                                                 &bytes_written, NULL, error))
815                 {
816                     g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes "
817                              "of data were written", bytes_written,
818                              ogout.header_len);
819                     g_assert (error == NULL || *error != NULL);
820                     goto cleanup;
821                 }
822 
823                 if (!g_output_stream_write_all (ostream, ogout.body,
824                                                 ogout.body_len, &bytes_written,
825                                                 NULL, error))
826                 {
827                     g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes "
828                              "of data were written", bytes_written,
829                              ogout.body_len);
830                     g_assert (error == NULL || *error != NULL);
831                     goto cleanup;
832                 }
833             }
834         }
835 
836         needflush = needout = 0;
837 
838         if (state->oggtype == ET_OGG_KIND_VORBIS ||
839             state->oggtype == ET_OGG_KIND_OPUS)
840         {
841             if (state->oggtype == ET_OGG_KIND_VORBIS)
842             {
843                 granpos += _blocksize (state, &op);
844             }
845 #ifdef ENABLE_OPUS
846             else
847             {
848                 granpos += opus_packet_get_samples_per_frame (op.packet,
849                                                               48000);
850             }
851 #endif
852             if(op.granulepos == -1)
853             {
854                 op.granulepos = granpos;
855                 ogg_stream_packetin (&streamout, &op);
856             }
857             else /* granulepos is set, validly. Use it, and force a flush to
858                 account for shortened blocks (vcut) when appropriate */
859             {
860                 if (granpos > op.granulepos)
861                 {
862                     granpos = op.granulepos;
863                     ogg_stream_packetin (&streamout, &op);
864                     needflush = 1;
865                 }
866                 else
867                 {
868                     ogg_stream_packetin (&streamout, &op);
869                     needout = 1;
870                 }
871             }
872         }
873         /* Don't know about granulepos for speex, will just assume the original
874            was appropriate. Not sure about the flushing?? */
875         else if (state->oggtype == ET_OGG_KIND_SPEEX)
876         {
877             ogg_stream_packetin (&streamout, &op);
878             needout = 1;
879         }
880     }
881 
882     if (error != NULL)
883     {
884         if (g_error_matches (*error, ET_OGG_ERROR, ET_OGG_ERROR_EOF)
885             || g_error_matches (*error, ET_OGG_ERROR, ET_OGG_ERROR_EOS))
886         {
887             /* While nominally errors, these are expected and can be safely
888              * ignored. */
889             g_clear_error (error);
890         }
891         else
892         {
893             goto cleanup;
894         }
895     }
896 
897     streamout.e_o_s = 1;
898 
899     while (ogg_stream_flush (&streamout, &ogout))
900     {
901         gsize bytes_written;
902 
903         if (!g_output_stream_write_all (ostream, ogout.header,
904                                         ogout.header_len, &bytes_written, NULL,
905                                         error))
906         {
907             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of data "
908                      "were written", bytes_written, ogout.header_len);
909             g_assert (error == NULL || *error != NULL);
910             goto cleanup;
911         }
912 
913         if (!g_output_stream_write_all (ostream, ogout.body, ogout.body_len,
914                                         &bytes_written, NULL, error))
915         {
916             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of data "
917                      "were written", bytes_written, ogout.body_len);
918             g_assert (error == NULL || *error != NULL);
919             goto cleanup;
920         }
921     }
922 
923     if (state->extrapage)
924     {
925         gsize bytes_written;
926 
927         if (!g_output_stream_write_all (ostream, ogout.header,
928                                         ogout.header_len, &bytes_written, NULL,
929                                         error))
930         {
931             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of data "
932                      "were written", bytes_written, ogout.header_len);
933             g_assert (error == NULL || *error != NULL);
934             goto cleanup;
935         }
936 
937         if (!g_output_stream_write_all (ostream, ogout.body, ogout.body_len,
938                                         &bytes_written, NULL, error))
939         {
940             g_debug ("Only %" G_GSIZE_FORMAT " bytes out of %ld bytes of data "
941                      "were written", bytes_written, ogout.body_len);
942             g_assert (error == NULL || *error != NULL);
943             goto cleanup;
944         }
945     }
946 
947     state->eosin = 0; /* clear it, because not all paths to here do */
948 
949     while (!state->eosin) /* We reached eos, not eof */
950     {
951         /* We copy the rest of the stream (other logical streams)
952          * through, a page at a time. */
953         while(1)
954         {
955             result = ogg_sync_pageout (state->oy, &ogout);
956 
957             if (result == 0)
958             {
959                 break;
960             }
961 
962             if (result < 0)
963             {
964                 g_debug ("%s", "Corrupt or missing data, continuing");
965             }
966             else
967             {
968                 gsize bytes_written;
969 
970                 /* Don't bother going through the rest, we can just
971                  * write the page out now */
972                 if (!g_output_stream_write_all (ostream, ogout.header,
973                                                 ogout.header_len,
974                                                 &bytes_written, NULL, error))
975                 {
976                     g_assert (error == NULL || *error != NULL);
977                     goto cleanup;
978                 }
979 
980                 if (!g_output_stream_write_all (ostream, ogout.body,
981                                                 ogout.body_len, &bytes_written,
982                                                 NULL, error))
983                 {
984                     g_assert (error == NULL || *error != NULL);
985                     goto cleanup;
986                 }
987             }
988         }
989 
990         buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
991 
992         bytes = g_input_stream_read (G_INPUT_STREAM (state->in), buffer,
993                                      CHUNKSIZE, NULL, error);
994 
995         if (bytes == -1)
996         {
997             g_assert (error == NULL || *error != NULL);
998             goto cleanup;
999         }
1000 
1001         ogg_sync_wrote (state->oy, bytes);
1002 
1003         if (bytes == 0)
1004         {
1005             state->eosin = 1;
1006             break;
1007         }
1008     }
1009 
1010 
1011 cleanup:
1012     ogg_stream_clear (&streamout);
1013     ogg_packet_clear (&header_comments);
1014 
1015     g_free (state->mainbuf);
1016     g_free (state->bookbuf);
1017     state->mainbuf = state->bookbuf = NULL;
1018 
1019     if (!state->eosin)
1020     {
1021         if (!error)
1022         {
1023             g_set_error (error, ET_OGG_ERROR, ET_OGG_ERROR_OUTPUT,
1024                          "Error writing stream to output. Output stream may be corrupted or truncated");
1025         }
1026     }
1027 
1028     if (error == NULL || *error != NULL)
1029     {
1030         g_object_unref (ostream);
1031         return FALSE;
1032     }
1033 
1034     g_assert (error == NULL || *error == NULL);
1035 
1036     if (!g_output_stream_close (ostream, NULL, error))
1037     {
1038         g_object_unref (ostream);
1039         g_assert (error == NULL || *error != NULL);
1040         return FALSE;
1041     }
1042 
1043     g_assert (error == NULL || *error == NULL);
1044 
1045     buf = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (ostream));
1046     size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (ostream));
1047 
1048     /* At least on Windows, writing to a file with an open-for-reading stream
1049      * fails, so close the input stream before writing to the file. */
1050     if (!g_input_stream_close (G_INPUT_STREAM (state->in), NULL, error))
1051     {
1052         /* Ignore the _close() failure, and try the write anyway. */
1053         g_warning ("Error closing Ogg file for reading: %s",
1054                    (*error)->message);
1055         g_clear_error (error);
1056     }
1057 
1058     g_object_unref (state->in);
1059     state->in = NULL;
1060 
1061     /* Write the in-memory data back out to the original file. */
1062     if (!g_file_replace_contents (file, buf, size, NULL, FALSE,
1063                                   G_FILE_CREATE_NONE, NULL, NULL, error))
1064     {
1065         GError *tmp_error = NULL;
1066 
1067         g_object_unref (ostream);
1068         g_free (buf);
1069 
1070         /* Re-open the file for reading, to keep the internal state
1071          * consistent. */
1072         state->in = g_file_read (file, NULL, &tmp_error);
1073 
1074         if (!state->in)
1075         {
1076             g_warning ("Error opening Ogg file for reading after write failure: %s",
1077                        tmp_error->message);
1078             g_clear_error (&tmp_error);
1079             g_assert (error == NULL || *error != NULL);
1080             return FALSE;
1081         }
1082 
1083         g_assert (error == NULL || *error != NULL);
1084         return FALSE;
1085     }
1086 
1087     g_free (buf);
1088     g_object_unref (ostream);
1089 
1090     /* Re-open the file, now that the write has completed. */
1091     state->in = g_file_read (file, NULL, error);
1092 
1093     if (!state->in)
1094     {
1095         g_assert (error == NULL || *error != NULL);
1096         return FALSE;
1097     }
1098 
1099 
1100     return TRUE;
1101 }
1102 
1103 #endif /* ENABLE_OGG */
1104