vorbis_comment 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +482 -0
- data/extconf.rb +4 -0
- data/test/blank.ogg +0 -0
- data/test/corrupt.ogg +0 -0
- data/test/lt8k.ogg +0 -0
- data/test/manyfields.ogg +0 -0
- data/test/test_vorbis_comment.rb +130 -0
- data/test/title.ogg +0 -0
- data/vcedit.c +583 -0
- data/vcedit.h +52 -0
- data/vorbis_comment-1.0.0.gem +0 -0
- data/vorbis_comment.gemspec +19 -0
- data/vorbis_comment.rb +164 -0
- data/vorbis_comment_ext.c +161 -0
- metadata +68 -0
data/extconf.rb
ADDED
data/test/blank.ogg
ADDED
Binary file
|
data/test/corrupt.ogg
ADDED
Binary file
|
data/test/lt8k.ogg
ADDED
Binary file
|
data/test/manyfields.ogg
ADDED
Binary file
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'vorbis_comment'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
begin
|
8
|
+
VorbisComment.new(File.join(File.dirname(__FILE__), 'lt8k.ogg')).fields
|
9
|
+
rescue VorbisComment::InvalidDataError
|
10
|
+
puts('The linked version of libvorbis has problems with files < 8K in size')
|
11
|
+
end
|
12
|
+
|
13
|
+
class VorbisCommentTest < Test::Unit::TestCase
|
14
|
+
def vc(filename)
|
15
|
+
VorbisComment.new(File.join(File.dirname(__FILE__), filename))
|
16
|
+
end
|
17
|
+
|
18
|
+
def work_with_copy(filename, &block)
|
19
|
+
new_name = File.join(File.dirname(__FILE__), "copy-#{filename}")
|
20
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), filename), new_name)
|
21
|
+
begin
|
22
|
+
result = yield new_name
|
23
|
+
ensure
|
24
|
+
File.delete(new_name)
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_initialize
|
30
|
+
assert_equal File.join(File.dirname(__FILE__), 'title.ogg'), vc('title.ogg').filename
|
31
|
+
# Nonexistent files don't raise an error until they are accessed for the fields
|
32
|
+
assert_equal 'nonexistant.ogg', VorbisComment.new('nonexistant.ogg').filename
|
33
|
+
# Corrupt files don't raise an error until they are parsed for the fields
|
34
|
+
assert_equal File.join(File.dirname(__FILE__), 'corrupt.ogg'), vc('corrupt.ogg').filename
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_exists?
|
38
|
+
assert_equal true, vc('title.ogg').exists?
|
39
|
+
assert_equal true, vc('manyfields.ogg').exists?
|
40
|
+
# Blank tags still exist
|
41
|
+
assert_equal true, vc('blank.ogg').exists?
|
42
|
+
# Corrupt tags are considered not to exist
|
43
|
+
assert_equal false, vc('corrupt.ogg').exists?
|
44
|
+
# Files that aren't ogg files will be treated similarly
|
45
|
+
assert_equal false, vc('test_vorbis_comment.rb').exists?
|
46
|
+
# But nonexistant files raise an OpenError
|
47
|
+
assert_raises(VorbisComment::OpenError){vc('nonexistant.ogg').exists?}
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_fields
|
51
|
+
assert_equal Hash[], vc('blank.ogg').fields
|
52
|
+
assert_equal Hash['title'=>['Silence']], vc('title.ogg').fields
|
53
|
+
assert_equal Hash[{"ARTIST"=>["Test Artist 1", "Test Artist 2"], "TrackNumber"=>["1"], "Album"=>["Test Album"], "Title"=>["Test Title"], "Date"=>["1999-12-31"], "comment"=>[""]}], vc('manyfields.ogg').fields
|
54
|
+
assert_raises(VorbisComment::InvalidDataError){vc('corrupt.ogg').fields}
|
55
|
+
assert_raises(VorbisComment::OpenError){vc('nonexistant.ogg').fields}
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_remove!
|
59
|
+
assert_equal Hash[], work_with_copy('blank.ogg'){|filename| VorbisComment.new(filename).remove!}
|
60
|
+
assert_equal Hash[], work_with_copy('title.ogg'){|filename| VorbisComment.new(filename).remove!}
|
61
|
+
assert_equal Hash[], work_with_copy('manyfields.ogg'){|filename| VorbisComment.new(filename).remove!}
|
62
|
+
assert_raises(VorbisComment::InvalidDataError){work_with_copy('corrupt.ogg'){|filename| VorbisComment.new(filename).remove!}}
|
63
|
+
assert_raises(VorbisComment::OpenError){VorbisComment.new('nonexistant.ogg').remove!}
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_pretty_print
|
67
|
+
assert_equal "", vc('blank.ogg').pretty_print
|
68
|
+
assert_equal "title: Silence", vc('title.ogg').pretty_print
|
69
|
+
assert_equal "ARTIST: Test Artist 1, Test Artist 2\nAlbum: Test Album\nDate: 1999-12-31\nTitle: Test Title\nTrackNumber: 1\ncomment: ", vc('manyfields.ogg').pretty_print
|
70
|
+
assert_equal "CORRUPT TAG!", vc('corrupt.ogg').pretty_print
|
71
|
+
assert_equal "CORRUPT TAG!", vc('test_vorbis_comment.rb').pretty_print
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_update
|
75
|
+
f = {'Blah'=>['Blah']}
|
76
|
+
assert_raises(VorbisComment::InvalidDataError){work_with_copy('corrupt.ogg'){|filename| VorbisComment.new(filename).update{|fields| fields.merge!(f)}}}
|
77
|
+
assert_raises(VorbisComment::OpenError){VorbisComment.new('nonexistant.ogg').update{|fields| fields.merge!(f)}}
|
78
|
+
|
79
|
+
g = {'x'=>'y', 'y'=>:z, :z=>[:A], :zz=>[['A'], [[[:b]], 'c']]}
|
80
|
+
h = {'x'=>['y'], 'y'=>['z'], 'z'=>['A'], 'zz'=>['A', 'bc']}
|
81
|
+
|
82
|
+
%w'blank.ogg title.ogg manyfields.ogg'.each do |fname|
|
83
|
+
work_with_copy(fname) do |filename|
|
84
|
+
v = VorbisComment.new(filename)
|
85
|
+
fx = v.fields.merge(f)
|
86
|
+
assert_equal fx, v.update{|fields| fields.merge!(f)}
|
87
|
+
assert_equal fx, v.fields
|
88
|
+
v = VorbisComment.new(filename)
|
89
|
+
assert_equal fx, v.fields
|
90
|
+
gx = v.fields.merge(g)
|
91
|
+
assert_equal gx, v.update{|fields| fields.merge!(g)}
|
92
|
+
v = VorbisComment.new(filename)
|
93
|
+
hx = v.fields.merge(h)
|
94
|
+
assert_equal hx, VorbisComment.new(filename).fields
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_bad_keys_and_values
|
100
|
+
work_with_copy('blank.ogg') do |filename|
|
101
|
+
v = VorbisComment.new(filename)
|
102
|
+
# Test for bad keys
|
103
|
+
(("\x00".."\x1f").to_a + ['='] + ("\x7e".."\xff").to_a).each do |c|
|
104
|
+
assert_raises(VorbisComment::InvalidCommentError){v.update{|fields| fields[c] = 'Blah'}}
|
105
|
+
end
|
106
|
+
# Test for bad vales (Invalid UTF8)
|
107
|
+
assert_raises(VorbisComment::InvalidCommentError){v.update{|fields| fields['Blah'] = "\x81\x81"}}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_add_to_fields_and_normalize_fields
|
112
|
+
work_with_copy('blank.ogg') do |filename|
|
113
|
+
v = VorbisComment.new(filename)
|
114
|
+
assert_equal Hash[], v.fields
|
115
|
+
assert_equal [], v.send(:normalize_fields)
|
116
|
+
v.send(:add_to_fields, 'A', 'b')
|
117
|
+
assert_equal CICPHash['A'=>['b']], v.fields
|
118
|
+
assert_equal [['A', 'b']], v.send(:normalize_fields)
|
119
|
+
v.send(:add_to_fields, :a, ['C'])
|
120
|
+
assert_equal CICPHash['A'=>['b',['C']]], v.fields
|
121
|
+
assert_equal [['A', 'C'], ['A', 'b']], v.send(:normalize_fields)
|
122
|
+
v.send(:add_to_fields, 12, 42)
|
123
|
+
assert_equal CICPHash['A'=>['b',['C']], 12=>[42]], v.fields
|
124
|
+
assert_equal [['12', '42'], ['A', 'C'], ['A', 'b']], v.send(:normalize_fields)
|
125
|
+
v.update{}
|
126
|
+
assert_equal CICPHash['A'=>['C','b'], '12'=>['42']], VorbisComment.new(filename).fields
|
127
|
+
assert_equal [['12', '42'], ['A', 'C'], ['A', 'b']], v.send(:normalize_fields)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/test/title.ogg
ADDED
Binary file
|
data/vcedit.c
ADDED
@@ -0,0 +1,583 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (C) 2000-2001 Michael Smith (msmith at xiph org)
|
3
|
+
*
|
4
|
+
* This library is free software; you can redistribute it and/or
|
5
|
+
* modify it under the terms of the GNU Lesser General Public
|
6
|
+
* License as published by the Free Software Foundation, version 2.
|
7
|
+
*
|
8
|
+
* This library 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 GNU
|
11
|
+
* Lesser General Public License for more details.
|
12
|
+
*
|
13
|
+
* You should have received a copy of the GNU Lesser General Public
|
14
|
+
* License along with this library; if not, write to the Free Software
|
15
|
+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
16
|
+
* MA 02110-1301 USA
|
17
|
+
*/
|
18
|
+
|
19
|
+
#include <stdio.h>
|
20
|
+
#include <stdbool.h>
|
21
|
+
#include <stdlib.h>
|
22
|
+
#include <string.h>
|
23
|
+
#include <errno.h>
|
24
|
+
#include <limits.h>
|
25
|
+
#include <unistd.h>
|
26
|
+
#include <sys/types.h>
|
27
|
+
#include <sys/stat.h>
|
28
|
+
#include <ogg/ogg.h>
|
29
|
+
#include <vorbis/codec.h>
|
30
|
+
#include <assert.h>
|
31
|
+
|
32
|
+
#include "vcedit.h"
|
33
|
+
|
34
|
+
#define CHUNKSIZE 4096
|
35
|
+
|
36
|
+
struct vcedit_state_St {
|
37
|
+
int refcount;
|
38
|
+
|
39
|
+
ogg_sync_state oy;
|
40
|
+
ogg_stream_state os;
|
41
|
+
|
42
|
+
vorbis_comment vc;
|
43
|
+
vorbis_info vi;
|
44
|
+
|
45
|
+
FILE *in;
|
46
|
+
mode_t file_mode;
|
47
|
+
|
48
|
+
bool opened;
|
49
|
+
long serial;
|
50
|
+
|
51
|
+
ogg_packet packet_main;
|
52
|
+
ogg_packet packet_code_books;
|
53
|
+
|
54
|
+
char *vendor;
|
55
|
+
int prevW;
|
56
|
+
|
57
|
+
bool extra_page;
|
58
|
+
bool eos;
|
59
|
+
|
60
|
+
char filename[0];
|
61
|
+
};
|
62
|
+
|
63
|
+
static void
|
64
|
+
ogg_packet_init (ogg_packet *p, unsigned char *buf, long len)
|
65
|
+
{
|
66
|
+
p->packet = buf;
|
67
|
+
p->bytes = len;
|
68
|
+
|
69
|
+
p->b_o_s = p->e_o_s = 0;
|
70
|
+
p->granulepos = p->packetno = 0;
|
71
|
+
}
|
72
|
+
|
73
|
+
static bool
|
74
|
+
ogg_packet_dup_data (ogg_packet *p)
|
75
|
+
{
|
76
|
+
unsigned char *tmp;
|
77
|
+
|
78
|
+
tmp = malloc (p->bytes);
|
79
|
+
if (!tmp)
|
80
|
+
return false;
|
81
|
+
|
82
|
+
memcpy (tmp, p->packet, p->bytes);
|
83
|
+
p->packet = tmp;
|
84
|
+
|
85
|
+
return true;
|
86
|
+
}
|
87
|
+
|
88
|
+
static void
|
89
|
+
vcedit_state_free (vcedit_state *s)
|
90
|
+
{
|
91
|
+
free (s->vendor);
|
92
|
+
|
93
|
+
if (s->in) {
|
94
|
+
fclose (s->in);
|
95
|
+
s->in = NULL;
|
96
|
+
}
|
97
|
+
|
98
|
+
free (s);
|
99
|
+
}
|
100
|
+
|
101
|
+
static bool
|
102
|
+
vcedit_state_init (vcedit_state *s, const char *filename)
|
103
|
+
{
|
104
|
+
s->refcount = 1;
|
105
|
+
|
106
|
+
ogg_packet_init (&s->packet_main, NULL, 0);
|
107
|
+
ogg_packet_init (&s->packet_code_books, NULL, 0);
|
108
|
+
|
109
|
+
strcpy (s->filename, filename);
|
110
|
+
|
111
|
+
return true;
|
112
|
+
}
|
113
|
+
|
114
|
+
vcedit_state *
|
115
|
+
vcedit_state_new (const char *filename)
|
116
|
+
{
|
117
|
+
vcedit_state *s;
|
118
|
+
size_t len;
|
119
|
+
|
120
|
+
len = strlen (filename);
|
121
|
+
if (len > PATH_MAX)
|
122
|
+
return NULL;
|
123
|
+
|
124
|
+
s = malloc (sizeof (vcedit_state) + len + 1);
|
125
|
+
if (!s)
|
126
|
+
return NULL;
|
127
|
+
|
128
|
+
memset (s, 0, sizeof (vcedit_state));
|
129
|
+
|
130
|
+
if (!vcedit_state_init (s, filename)) {
|
131
|
+
vcedit_state_free (s);
|
132
|
+
return NULL;
|
133
|
+
}
|
134
|
+
|
135
|
+
return s;
|
136
|
+
}
|
137
|
+
|
138
|
+
vorbis_comment *
|
139
|
+
vcedit_comments (vcedit_state *s)
|
140
|
+
{
|
141
|
+
return s->opened ? &s->vc : NULL;
|
142
|
+
}
|
143
|
+
|
144
|
+
static void
|
145
|
+
vcedit_clear_internals (vcedit_state *s)
|
146
|
+
{
|
147
|
+
ogg_stream_clear (&s->os);
|
148
|
+
ogg_sync_clear (&s->oy);
|
149
|
+
|
150
|
+
vorbis_info_clear (&s->vi);
|
151
|
+
vorbis_comment_clear (&s->vc);
|
152
|
+
|
153
|
+
free (s->vendor);
|
154
|
+
s->vendor = NULL;
|
155
|
+
|
156
|
+
ogg_packet_clear (&s->packet_main);
|
157
|
+
ogg_packet_clear (&s->packet_code_books);
|
158
|
+
|
159
|
+
s->serial = 0;
|
160
|
+
s->opened = false;
|
161
|
+
}
|
162
|
+
|
163
|
+
void
|
164
|
+
vcedit_state_ref (vcedit_state *s)
|
165
|
+
{
|
166
|
+
s->refcount++;
|
167
|
+
}
|
168
|
+
|
169
|
+
void
|
170
|
+
vcedit_state_unref (vcedit_state *s)
|
171
|
+
{
|
172
|
+
if (--s->refcount)
|
173
|
+
return;
|
174
|
+
|
175
|
+
if (s->opened)
|
176
|
+
vcedit_clear_internals (s);
|
177
|
+
|
178
|
+
vcedit_state_free (s);
|
179
|
+
}
|
180
|
+
|
181
|
+
/* Next two functions pulled straight from libvorbis, apart from one change
|
182
|
+
* - we don't want to overwrite the vendor string.
|
183
|
+
*/
|
184
|
+
static void
|
185
|
+
_v_writestring (oggpack_buffer *o, const char *s, int len)
|
186
|
+
{
|
187
|
+
while (len--)
|
188
|
+
oggpack_write (o, *s++, 8);
|
189
|
+
}
|
190
|
+
|
191
|
+
static bool
|
192
|
+
write_comments (vcedit_state *s, ogg_packet *packet)
|
193
|
+
{
|
194
|
+
oggpack_buffer opb;
|
195
|
+
size_t len;
|
196
|
+
int i;
|
197
|
+
|
198
|
+
ogg_packet_init (packet, NULL, 0);
|
199
|
+
|
200
|
+
oggpack_writeinit (&opb);
|
201
|
+
|
202
|
+
/* preamble */
|
203
|
+
oggpack_write (&opb, 0x03, 8);
|
204
|
+
_v_writestring (&opb, "vorbis", 6);
|
205
|
+
|
206
|
+
/* vendor */
|
207
|
+
len = strlen (s->vendor);
|
208
|
+
oggpack_write (&opb, len, 32);
|
209
|
+
_v_writestring (&opb, s->vendor, len);
|
210
|
+
|
211
|
+
/* comments */
|
212
|
+
oggpack_write (&opb, s->vc.comments, 32);
|
213
|
+
|
214
|
+
for (i = 0; i < s->vc.comments; i++)
|
215
|
+
if (!s->vc.user_comments[i])
|
216
|
+
oggpack_write (&opb, 0, 32);
|
217
|
+
else {
|
218
|
+
oggpack_write (&opb, s->vc.comment_lengths[i], 32);
|
219
|
+
_v_writestring (&opb, s->vc.user_comments[i],
|
220
|
+
s->vc.comment_lengths[i]);
|
221
|
+
}
|
222
|
+
|
223
|
+
oggpack_write (&opb, 1, 1);
|
224
|
+
|
225
|
+
packet->bytes = oggpack_bytes (&opb);
|
226
|
+
|
227
|
+
packet->packet = _ogg_malloc (packet->bytes);
|
228
|
+
if (!packet->packet)
|
229
|
+
return false;
|
230
|
+
|
231
|
+
memcpy (packet->packet, opb.buffer, packet->bytes);
|
232
|
+
|
233
|
+
oggpack_writeclear (&opb);
|
234
|
+
|
235
|
+
return true;
|
236
|
+
}
|
237
|
+
|
238
|
+
static int
|
239
|
+
_blocksize (vcedit_state *s, ogg_packet *p)
|
240
|
+
{
|
241
|
+
int this, ret = 0;
|
242
|
+
|
243
|
+
this = vorbis_packet_blocksize (&s->vi, p);
|
244
|
+
|
245
|
+
if (s->prevW)
|
246
|
+
ret = (this + s->prevW) / 4;
|
247
|
+
|
248
|
+
s->prevW = this;
|
249
|
+
|
250
|
+
return ret;
|
251
|
+
}
|
252
|
+
|
253
|
+
static int
|
254
|
+
_fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
|
255
|
+
{
|
256
|
+
char *buffer;
|
257
|
+
int result, bytes;
|
258
|
+
|
259
|
+
result = ogg_stream_packetout (&s->os, p);
|
260
|
+
if (result == 1)
|
261
|
+
return 1;
|
262
|
+
|
263
|
+
if (s->eos)
|
264
|
+
return 0;
|
265
|
+
|
266
|
+
while (ogg_sync_pageout (&s->oy, page) != 1) {
|
267
|
+
buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
|
268
|
+
bytes = fread (buffer, 1, CHUNKSIZE, s->in);
|
269
|
+
ogg_sync_wrote (&s->oy, bytes);
|
270
|
+
|
271
|
+
if (!bytes && (feof (s->in) || ferror (s->in)))
|
272
|
+
return 0;
|
273
|
+
}
|
274
|
+
|
275
|
+
if (ogg_page_eos (page))
|
276
|
+
s->eos = true;
|
277
|
+
else if (ogg_page_serialno (page) != s->serial) {
|
278
|
+
s->eos = true;
|
279
|
+
s->extra_page = true;
|
280
|
+
|
281
|
+
return 0;
|
282
|
+
}
|
283
|
+
|
284
|
+
ogg_stream_pagein (&s->os, page);
|
285
|
+
|
286
|
+
return _fetch_next_packet (s, p, page);
|
287
|
+
}
|
288
|
+
|
289
|
+
vcedit_error
|
290
|
+
vcedit_open (vcedit_state *s)
|
291
|
+
{
|
292
|
+
vcedit_error ret;
|
293
|
+
ogg_packet packet_comments, *header = &packet_comments;
|
294
|
+
ogg_page page;
|
295
|
+
struct stat st;
|
296
|
+
char *buffer;
|
297
|
+
size_t bytes, total = 0;
|
298
|
+
int i = 0;
|
299
|
+
|
300
|
+
s->in = fopen (s->filename, "rb");
|
301
|
+
if (!s->in)
|
302
|
+
return VCEDIT_ERR_OPEN;
|
303
|
+
|
304
|
+
s->file_mode = stat (s->filename, &st) ? 0664 : st.st_mode;
|
305
|
+
|
306
|
+
ogg_sync_init (&s->oy);
|
307
|
+
|
308
|
+
do {
|
309
|
+
/* Bail if we don't find data in the first 40 kB */
|
310
|
+
if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) {
|
311
|
+
ogg_sync_clear (&s->oy);
|
312
|
+
|
313
|
+
return VCEDIT_ERR_INVAL;
|
314
|
+
}
|
315
|
+
|
316
|
+
buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
|
317
|
+
|
318
|
+
bytes = fread (buffer, 1, CHUNKSIZE, s->in);
|
319
|
+
total += bytes;
|
320
|
+
|
321
|
+
ogg_sync_wrote (&s->oy, bytes);
|
322
|
+
} while (ogg_sync_pageout (&s->oy, &page) != 1);
|
323
|
+
|
324
|
+
s->serial = ogg_page_serialno (&page);
|
325
|
+
|
326
|
+
ogg_stream_init (&s->os, s->serial);
|
327
|
+
vorbis_info_init (&s->vi);
|
328
|
+
vorbis_comment_init (&s->vc);
|
329
|
+
|
330
|
+
if (ogg_stream_pagein (&s->os, &page) < 0) {
|
331
|
+
ret = VCEDIT_ERR_INVAL;
|
332
|
+
goto err;
|
333
|
+
}
|
334
|
+
|
335
|
+
if (ogg_stream_packetout (&s->os, &s->packet_main) != 1) {
|
336
|
+
ret = VCEDIT_ERR_INVAL;
|
337
|
+
goto err;
|
338
|
+
}
|
339
|
+
|
340
|
+
if (!ogg_packet_dup_data (&s->packet_main)) {
|
341
|
+
s->packet_main.packet = NULL;
|
342
|
+
ret = VCEDIT_ERR_INVAL;
|
343
|
+
goto err;
|
344
|
+
}
|
345
|
+
|
346
|
+
if (vorbis_synthesis_headerin (&s->vi, &s->vc, &s->packet_main) < 0) {
|
347
|
+
ret = VCEDIT_ERR_INVAL;
|
348
|
+
goto err;
|
349
|
+
}
|
350
|
+
|
351
|
+
ogg_packet_init (&packet_comments, NULL, 0);
|
352
|
+
|
353
|
+
while (i < 2) {
|
354
|
+
if (feof (s->in) || ferror (s->in)) {
|
355
|
+
ret = VCEDIT_ERR_INVAL;
|
356
|
+
goto err;
|
357
|
+
}
|
358
|
+
|
359
|
+
while (i < 2) {
|
360
|
+
int result;
|
361
|
+
|
362
|
+
result = ogg_sync_pageout (&s->oy, &page);
|
363
|
+
if (!result)
|
364
|
+
break; /* Too little data so far */
|
365
|
+
|
366
|
+
if (result != 1)
|
367
|
+
continue;
|
368
|
+
|
369
|
+
ogg_stream_pagein (&s->os, &page);
|
370
|
+
|
371
|
+
while (i < 2) {
|
372
|
+
result = ogg_stream_packetout (&s->os, header);
|
373
|
+
if (!result)
|
374
|
+
break;
|
375
|
+
|
376
|
+
if (result != 1) {
|
377
|
+
ret = VCEDIT_ERR_INVAL;
|
378
|
+
goto err;
|
379
|
+
}
|
380
|
+
|
381
|
+
if (i++ == 1 && !ogg_packet_dup_data (header)) {
|
382
|
+
header->packet = NULL;
|
383
|
+
ret = VCEDIT_ERR_INVAL;
|
384
|
+
goto err;
|
385
|
+
}
|
386
|
+
|
387
|
+
vorbis_synthesis_headerin (&s->vi, &s->vc, header);
|
388
|
+
|
389
|
+
header = &s->packet_code_books;
|
390
|
+
}
|
391
|
+
}
|
392
|
+
|
393
|
+
buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
|
394
|
+
|
395
|
+
bytes = fread (buffer, 1, CHUNKSIZE, s->in);
|
396
|
+
ogg_sync_wrote (&s->oy, bytes);
|
397
|
+
}
|
398
|
+
|
399
|
+
/* Copy the vendor tag */
|
400
|
+
s->vendor = strdup (s->vc.vendor);
|
401
|
+
|
402
|
+
/* Headers are done! */
|
403
|
+
s->opened = true;
|
404
|
+
|
405
|
+
return VCEDIT_ERR_SUCCESS;
|
406
|
+
|
407
|
+
err:
|
408
|
+
vcedit_clear_internals (s);
|
409
|
+
|
410
|
+
return ret;
|
411
|
+
}
|
412
|
+
|
413
|
+
static bool
|
414
|
+
write_data (const void *buf, size_t size, size_t nmemb, FILE *stream)
|
415
|
+
{
|
416
|
+
while (nmemb > 0) {
|
417
|
+
size_t w;
|
418
|
+
|
419
|
+
w = fwrite (buf, size, nmemb, stream);
|
420
|
+
if (!w && ferror (stream))
|
421
|
+
return false;
|
422
|
+
|
423
|
+
nmemb -= w;
|
424
|
+
buf += size * w;
|
425
|
+
}
|
426
|
+
|
427
|
+
return true;
|
428
|
+
}
|
429
|
+
|
430
|
+
static bool
|
431
|
+
write_page (FILE *f, ogg_page *p)
|
432
|
+
{
|
433
|
+
return write_data (p->header, 1, p->header_len, f) &&
|
434
|
+
write_data (p->body, 1, p->body_len, f);
|
435
|
+
}
|
436
|
+
|
437
|
+
vcedit_error
|
438
|
+
vcedit_write (vcedit_state *s)
|
439
|
+
{
|
440
|
+
ogg_stream_state stream;
|
441
|
+
ogg_packet packet;
|
442
|
+
ogg_page page_out, page_in;
|
443
|
+
ogg_int64_t granpos = 0;
|
444
|
+
FILE *out;
|
445
|
+
char *buffer, tmpfile[PATH_MAX];
|
446
|
+
bool success = false, need_flush = false, need_out = false;
|
447
|
+
int fd, result, bytes;
|
448
|
+
|
449
|
+
if (!s->opened)
|
450
|
+
return VCEDIT_ERR_INVAL;
|
451
|
+
|
452
|
+
strcpy (tmpfile, s->filename);
|
453
|
+
strcat (tmpfile, ".XXXXXX");
|
454
|
+
|
455
|
+
fd = mkstemp (tmpfile);
|
456
|
+
if (fd == -1)
|
457
|
+
return VCEDIT_ERR_TMPFILE;
|
458
|
+
|
459
|
+
out = fdopen (fd, "wb");
|
460
|
+
if (!out) {
|
461
|
+
unlink (tmpfile);
|
462
|
+
close (fd);
|
463
|
+
|
464
|
+
return VCEDIT_ERR_TMPFILE;
|
465
|
+
}
|
466
|
+
|
467
|
+
s->prevW = 0;
|
468
|
+
s->extra_page = s->eos = false;
|
469
|
+
|
470
|
+
ogg_stream_init (&stream, s->serial);
|
471
|
+
|
472
|
+
/* write "main" packet */
|
473
|
+
s->packet_main.b_o_s = 1;
|
474
|
+
ogg_stream_packetin (&stream, &s->packet_main);
|
475
|
+
s->packet_main.b_o_s = 0;
|
476
|
+
|
477
|
+
/* prepare and write comments */
|
478
|
+
if (!write_comments (s, &packet)) {
|
479
|
+
ogg_stream_clear (&stream);
|
480
|
+
unlink (tmpfile);
|
481
|
+
fclose (out);
|
482
|
+
|
483
|
+
return VCEDIT_ERR_INVAL;
|
484
|
+
}
|
485
|
+
|
486
|
+
ogg_stream_packetin (&stream, &packet);
|
487
|
+
ogg_packet_clear (&packet);
|
488
|
+
|
489
|
+
/* write codebooks */
|
490
|
+
ogg_stream_packetin (&stream, &s->packet_code_books);
|
491
|
+
|
492
|
+
while (ogg_stream_flush (&stream, &page_out))
|
493
|
+
if (!write_page (out, &page_out))
|
494
|
+
goto cleanup;
|
495
|
+
|
496
|
+
while (_fetch_next_packet (s, &packet, &page_in)) {
|
497
|
+
bool write = false;
|
498
|
+
int size;
|
499
|
+
|
500
|
+
size = _blocksize (s, &packet);
|
501
|
+
granpos += size;
|
502
|
+
|
503
|
+
if (need_flush)
|
504
|
+
write = ogg_stream_flush (&stream, &page_out);
|
505
|
+
else if (need_out)
|
506
|
+
write = ogg_stream_pageout (&stream, &page_out);
|
507
|
+
|
508
|
+
if (write && !write_page (out, &page_out))
|
509
|
+
goto cleanup;
|
510
|
+
|
511
|
+
need_flush = need_out = false;
|
512
|
+
|
513
|
+
if (packet.granulepos == -1) {
|
514
|
+
packet.granulepos = granpos;
|
515
|
+
ogg_stream_packetin (&stream, &packet);
|
516
|
+
} else {
|
517
|
+
/* granulepos is set, validly. Use it, and force a flush to
|
518
|
+
* account for shortened blocks (vcut) when appropriate
|
519
|
+
*/
|
520
|
+
if (granpos > packet.granulepos) {
|
521
|
+
granpos = packet.granulepos;
|
522
|
+
ogg_stream_packetin (&stream, &packet);
|
523
|
+
need_flush = true;
|
524
|
+
} else {
|
525
|
+
ogg_stream_packetin (&stream, &packet);
|
526
|
+
need_out = true;
|
527
|
+
}
|
528
|
+
}
|
529
|
+
}
|
530
|
+
|
531
|
+
stream.e_o_s = 1;
|
532
|
+
|
533
|
+
while (ogg_stream_flush (&stream, &page_out))
|
534
|
+
if (!write_page (out, &page_out))
|
535
|
+
goto cleanup;
|
536
|
+
|
537
|
+
if (s->extra_page && !write_page (out, &page_in))
|
538
|
+
goto cleanup;
|
539
|
+
|
540
|
+
/* clear it, because not all paths to here do */
|
541
|
+
s->eos = false;
|
542
|
+
|
543
|
+
do {
|
544
|
+
/* We copy the rest of the stream (other logical streams)
|
545
|
+
* through, a page at a time.
|
546
|
+
*/
|
547
|
+
while ((result = ogg_sync_pageout (&s->oy, &page_out)))
|
548
|
+
if (result == 1 && !write_page (out, &page_out))
|
549
|
+
goto cleanup;
|
550
|
+
|
551
|
+
buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
|
552
|
+
bytes = fread (buffer, 1, CHUNKSIZE, s->in);
|
553
|
+
ogg_sync_wrote (&s->oy, bytes);
|
554
|
+
|
555
|
+
if (ferror (s->in))
|
556
|
+
goto cleanup;
|
557
|
+
} while (bytes || !feof (s->in));
|
558
|
+
|
559
|
+
s->eos = success = true;
|
560
|
+
|
561
|
+
cleanup:
|
562
|
+
fclose (s->in);
|
563
|
+
|
564
|
+
if (!success) {
|
565
|
+
unlink (tmpfile);
|
566
|
+
fclose (out);
|
567
|
+
} else {
|
568
|
+
fclose (out);
|
569
|
+
unlink (s->filename);
|
570
|
+
rename (tmpfile, s->filename);
|
571
|
+
chmod (s->filename, s->file_mode);
|
572
|
+
}
|
573
|
+
|
574
|
+
ogg_stream_clear (&stream);
|
575
|
+
|
576
|
+
if (!s->eos)
|
577
|
+
return VCEDIT_ERR_INVAL;
|
578
|
+
|
579
|
+
vcedit_clear_internals (s);
|
580
|
+
|
581
|
+
return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ?
|
582
|
+
VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;
|
583
|
+
}
|