2
* Entagged Audio Tag library
3
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
4
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
6
* This library is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU Lesser General Public
8
* License as published by the Free Software Foundation; either
9
* version 2.1 of the License, or (at your option) any later version.
11
* This library is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* Lesser General Public License for more details.
16
* You should have received a copy of the GNU Lesser General Public
17
* License along with this library; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
package org.jaudiotagger.audio.ogg;
22
import org.jaudiotagger.audio.exceptions.CannotReadException;
23
import org.jaudiotagger.audio.generic.Utils;
24
import org.jaudiotagger.audio.ogg.util.OggPageHeader;
25
import org.jaudiotagger.audio.ogg.util.VorbisHeader;
26
import org.jaudiotagger.audio.ogg.util.VorbisPacketType;
27
import org.jaudiotagger.fix.Fix;
28
import org.jaudiotagger.logging.ErrorMessage;
29
import org.jaudiotagger.tag.Tag;
30
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentReader;
31
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag;
33
import java.io.ByteArrayOutputStream;
34
import java.io.IOException;
35
import java.io.RandomAccessFile;
36
import java.util.ArrayList;
37
import java.util.List;
38
import java.util.logging.Logger;
41
* Read Vorbis Comment Tag within ogg
43
* Vorbis is the audiostream within an ogg file, Vorbis uses VorbisComments as its tag
45
public class OggVorbisTagReader
48
public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.ogg");
50
private VorbisCommentReader vorbisCommentReader;
52
public OggVorbisTagReader()
54
vorbisCommentReader = new VorbisCommentReader();
57
public OggVorbisTagReader(Fix fix)
60
vorbisCommentReader = new VorbisCommentReader(fix);
65
* Read the Logical VorbisComment Tag from the file
67
* <p>Read the CommenyTag, within an OggVorbis file the VorbisCommentTag is mandatory
71
* @throws CannotReadException
74
public Tag read(RandomAccessFile raf) throws CannotReadException, IOException
76
logger.info("Starting to read ogg vorbis tag from file:");
77
byte[] rawVorbisCommentData = readRawPacketData(raf);
80
VorbisCommentTag tag = vorbisCommentReader.read(rawVorbisCommentData, true);
81
logger.fine("CompletedReadCommentTag");
86
* Retrieve the Size of the VorbisComment packet including the oggvorbis header
90
* @throws CannotReadException
93
public int readOggVorbisRawSize(RandomAccessFile raf) throws CannotReadException, IOException
95
byte[] rawVorbisCommentData = readRawPacketData(raf);
96
return rawVorbisCommentData.length + VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH;
100
* Retrieve the raw VorbisComment packet data, does not include the OggVorbis header
104
* @throws CannotReadException if unable to find vorbiscomment header
105
* @throws IOException
107
public byte[] readRawPacketData(RandomAccessFile raf) throws CannotReadException, IOException
109
logger.fine("Read 1st page");
110
//1st page = codec infos
111
OggPageHeader pageHeader = OggPageHeader.read(raf);
112
//Skip over data to end of page header 1
113
raf.seek(raf.getFilePointer() + pageHeader.getPageLength());
115
logger.fine("Read 2nd page");
116
//2nd page = comment, may extend to additional pages or not , may also have setup header
117
pageHeader = OggPageHeader.read(raf);
119
//Now at start of packets on page 2 , check this is the vorbis comment header
120
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
122
if (!isVorbisCommentHeader(b))
124
throw new CannotReadException("Cannot find comment block (no vorbiscomment header)");
127
//Convert the comment raw data which maybe over many pages back into raw packet
128
byte[] rawVorbisCommentData = convertToVorbisCommentPacket(pageHeader, raf);
129
return rawVorbisCommentData;
134
* Is this a Vorbis Comment header, check
136
* Note this check only applies to Vorbis Comments embedded within an OggVorbis File which is why within here
139
* @return true if the headerData matches a VorbisComment header i.e is a Vorbis header of type COMMENT_HEADER
141
public boolean isVorbisCommentHeader(byte[] headerData)
143
String vorbis = Utils.getString(headerData, VorbisHeader.FIELD_CAPTURE_PATTERN_POS, VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH, "ISO-8859-1");
144
return !(headerData[VorbisHeader.FIELD_PACKET_TYPE_POS] != VorbisPacketType.COMMENT_HEADER.getType() || !vorbis.equals(VorbisHeader.CAPTURE_PATTERN));
148
* Is this a Vorbis SetupHeader check
151
* @return true if matches vorbis setupheader
153
public boolean isVorbisSetupHeader(byte[] headerData)
155
String vorbis = Utils.getString(headerData, VorbisHeader.FIELD_CAPTURE_PATTERN_POS, VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH, "ISO-8859-1");
156
return !(headerData[VorbisHeader.FIELD_PACKET_TYPE_POS] != VorbisPacketType.SETUP_HEADER.getType() || !vorbis.equals(VorbisHeader.CAPTURE_PATTERN));
160
* The Vorbis Comment may span multiple pages so we we need to identify the pages they contain and then
161
* extract the packet data from the pages
162
* @param startVorbisCommentPage
164
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
165
* @throws java.io.IOException
168
private byte[] convertToVorbisCommentPacket(OggPageHeader startVorbisCommentPage, RandomAccessFile raf) throws IOException, CannotReadException
170
ByteArrayOutputStream baos = new ByteArrayOutputStream();
171
byte[] b = new byte[startVorbisCommentPage.getPacketList().get(0).getLength() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH)];
175
//Because there is at least one other packet (SetupHeaderPacket) this means the Comment Packet has finished
176
//on this page so thats all we need and we can return
177
if (startVorbisCommentPage.getPacketList().size() > 1)
179
logger.info("Comments finish on 2nd Page because there is another packet on this page");
180
return baos.toByteArray();
183
//There is only the VorbisComment packet on page if it has completed on this page we can return
184
if (!startVorbisCommentPage.isLastPacketIncomplete())
186
logger.info("Comments finish on 2nd Page because this packet is complete");
187
return baos.toByteArray();
190
//The VorbisComment extends to the next page, so should be at end of page already
191
//so carry on reading pages until we get to the end of comment
194
logger.info("Reading next page");
195
OggPageHeader nextPageHeader = OggPageHeader.read(raf);
196
b = new byte[nextPageHeader.getPacketList().get(0).getLength()];
200
//Because there is at least one other packet (SetupHeaderPacket) this means the Comment Packet has finished
201
//on this page so thats all we need and we can return
202
if (nextPageHeader.getPacketList().size() > 1)
204
logger.info("Comments finish on Page because there is another packet on this page");
205
return baos.toByteArray();
208
//There is only the VorbisComment packet on page if it has completed on this page we can return
209
if (!nextPageHeader.isLastPacketIncomplete())
211
logger.info("Comments finish on Page because this packet is complete");
212
return baos.toByteArray();
218
* The Vorbis Setup Header may span multiple(2) pages, athough it doesnt normally. We pass the start of the
219
* file offset of the OggPage it belongs on, it probably won't be first packet.
220
* @param fileOffsetOfStartingOggPage
222
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
223
* @throws java.io.IOException
226
public byte[] convertToVorbisSetupHeaderPacket(long fileOffsetOfStartingOggPage, RandomAccessFile raf) throws IOException, CannotReadException
228
ByteArrayOutputStream baos = new ByteArrayOutputStream();
230
//Seek to specified offset
231
raf.seek(fileOffsetOfStartingOggPage);
234
OggPageHeader setupPageHeader = OggPageHeader.read(raf);
236
//Assume that if multiple packets first packet is VorbisComment and second packet
238
if (setupPageHeader.getPacketList().size() > 1)
240
raf.skipBytes(setupPageHeader.getPacketList().get(0).getLength());
243
//Now should be at start of next packet, check this is the vorbis setup header
244
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
246
if (!isVorbisSetupHeader(b))
248
throw new CannotReadException("Unable to find setup header(2), unable to write ogg file");
251
//Go back to start of setupheader data
252
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
255
if (setupPageHeader.getPacketList().size() > 1)
257
b = new byte[setupPageHeader.getPacketList().get(1).getLength()];
263
b = new byte[setupPageHeader.getPacketList().get(0).getLength()];
269
if (!setupPageHeader.isLastPacketIncomplete() || setupPageHeader.getPacketList().size() > 2)
271
logger.info("Setupheader finishes on this page");
272
return baos.toByteArray();
275
//The Setupheader extends to the next page, so should be at end of page already
276
//so carry on reading pages until we get to the end of comment
279
logger.info("Reading another page");
280
OggPageHeader nextPageHeader = OggPageHeader.read(raf);
281
b = new byte[nextPageHeader.getPacketList().get(0).getLength()];
285
//Because there is at least one other packet this means the Setupheader Packet has finished
286
//on this page so thats all we need and we can return
287
if (nextPageHeader.getPacketList().size() > 1)
289
logger.info("Setupheader finishes on this page");
290
return baos.toByteArray();
293
//There is only the Setupheader packet on page if it has completed on this page we can return
294
if (!nextPageHeader.isLastPacketIncomplete())
296
logger.info("Setupheader finish on Page because this packet is complete");
297
return baos.toByteArray();
304
* The Vorbis Setup Header may span multiple(2) pages, athough it doesnt normally. We pass the start of the
305
* file offset of the OggPage it belongs on, it probably won't be first packet, also returns any addditional
306
* packets that immediately follow the setup header in original file
307
* @param fileOffsetOfStartingOggPage
309
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
310
* @throws java.io.IOException
313
public byte[] convertToVorbisSetupHeaderPacketAndAdditionalPackets(long fileOffsetOfStartingOggPage, RandomAccessFile raf) throws IOException, CannotReadException
315
ByteArrayOutputStream baos = new ByteArrayOutputStream();
317
//Seek to specified offset
318
raf.seek(fileOffsetOfStartingOggPage);
321
OggPageHeader setupPageHeader = OggPageHeader.read(raf);
323
//Assume that if multiple packets first packet is VorbisComment and second packet
325
if (setupPageHeader.getPacketList().size() > 1)
327
raf.skipBytes(setupPageHeader.getPacketList().get(0).getLength());
330
//Now should be at start of next packet, check this is the vorbis setup header
331
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
333
if (!isVorbisSetupHeader(b))
335
throw new CannotReadException("Unable to find setup header(2), unable to write ogg file");
338
//Go back to start of setupheader data
339
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
342
if (setupPageHeader.getPacketList().size() > 1)
344
b = new byte[setupPageHeader.getPacketList().get(1).getLength()];
350
b = new byte[setupPageHeader.getPacketList().get(0).getLength()];
356
if (!setupPageHeader.isLastPacketIncomplete() || setupPageHeader.getPacketList().size() > 2)
358
logger.info("Setupheader finishes on this page");
359
if (setupPageHeader.getPacketList().size() > 2)
361
for (int i = 2; i < setupPageHeader.getPacketList().size(); i++)
363
b = new byte[setupPageHeader.getPacketList().get(i).getLength()];
368
return baos.toByteArray();
371
//The Setupheader extends to the next page, so should be at end of page already
372
//so carry on reading pages until we get to the end of comment
375
logger.info("Reading another page");
376
OggPageHeader nextPageHeader = OggPageHeader.read(raf);
377
b = new byte[nextPageHeader.getPacketList().get(0).getLength()];
381
//Because there is at least one other packet this means the Setupheader Packet has finished
382
//on this page so thats all we need and we can return
383
if (nextPageHeader.getPacketList().size() > 1)
385
logger.info("Setupheader finishes on this page");
386
return baos.toByteArray();
389
//There is only the Setupheader packet on page if it has completed on this page we can return
390
if (!nextPageHeader.isLastPacketIncomplete())
392
logger.info("Setupheader finish on Page because this packet is complete");
393
return baos.toByteArray();
400
* Calculate the size of the packet data for the comment and setup headers
404
* @throws CannotReadException
405
* @throws IOException
407
public OggVorbisHeaderSizes readOggVorbisHeaderSizes(RandomAccessFile raf) throws CannotReadException, IOException
409
logger.fine("Started to read comment and setup header sizes:");
411
//Stores filepointers so return file in same state
412
long filepointer = raf.getFilePointer();
414
//Extra Packets on same page as setup header
415
List<OggPageHeader.PacketStartAndLength> extraPackets = new ArrayList<OggPageHeader.PacketStartAndLength>();
417
long commentHeaderStartPosition;
418
long setupHeaderStartPosition;
419
int commentHeaderSize = 0;
421
//1st page = codec infos
422
OggPageHeader pageHeader = OggPageHeader.read(raf);
423
//Skip over data to end of page header 1
424
raf.seek(raf.getFilePointer() + pageHeader.getPageLength());
426
//2nd page = comment, may extend to additional pages or not , may also have setup header
427
pageHeader = OggPageHeader.read(raf);
428
commentHeaderStartPosition = raf.getFilePointer() - (OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length);
430
//Now at start of packets on page 2 , check this is the vorbis comment header
431
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
433
if (!isVorbisCommentHeader(b))
435
throw new CannotReadException("Cannot find comment block (no vorbiscomment header)");
437
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
438
logger.info("Found start of comment header at:" + raf.getFilePointer());
440
//Calculate Comment Size (not inc header)
443
List<OggPageHeader.PacketStartAndLength> packetList = pageHeader.getPacketList();
444
commentHeaderSize += packetList.get(0).getLength();
445
raf.skipBytes(packetList.get(0).getLength());
447
//If this page contains multiple packets or if this last packet is complete then the Comment header
448
//end son this page and we can break
449
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
452
logger.info("Found end of comment:size:" + commentHeaderSize + "finishes at file position:" + raf.getFilePointer());
455
pageHeader = OggPageHeader.read(raf);
458
//If there are no more packets on this page we need to go to next page to get the setup header
459
OggPageHeader.PacketStartAndLength packet;
460
if(pageHeader.getPacketList().size()==1)
462
pageHeader = OggPageHeader.read(raf);
463
List<OggPageHeader.PacketStartAndLength> packetList = pageHeader.getPacketList();
464
packet = pageHeader.getPacketList().get(0);
466
//Now at start of next packet , check this is the vorbis setup header
467
b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
469
if (!isVorbisSetupHeader(b))
471
throw new CannotReadException(ErrorMessage.OGG_VORBIS_NO_VORBIS_HEADER_FOUND.getMsg());
473
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
474
logger.info("Found start of vorbis setup header at file position:" + raf.getFilePointer());
476
//Set this to the start of the OggPage that setupheader was found on
477
setupHeaderStartPosition = raf.getFilePointer() - (OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length);
479
//Add packet data to size to the setup header size
480
setupHeaderSize = packet.getLength();
481
logger.fine("Adding:" + packet.getLength() + " to setup header size");
483
//Skip over the packet data
484
raf.skipBytes(packet.getLength());
486
//If there are other packets that follow this one, or if the last packet is complete then we must have
487
//got the size of the setup header.
488
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
490
logger.info("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
491
if (packetList.size() > 1)
493
extraPackets = packetList.subList(1, packetList.size());
496
//The setup header continues onto the next page
499
pageHeader = OggPageHeader.read(raf);
500
packetList = pageHeader.getPacketList();
503
setupHeaderSize += packetList.get(0).getLength();
504
logger.fine("Adding:" + packetList.get(0).getLength() + " to setup header size");
505
raf.skipBytes(packetList.get(0).getLength());
506
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
509
logger.fine("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
510
if (packetList.size() > 1)
512
extraPackets = packetList.subList(1, packetList.size());
516
//Continues onto another page
517
pageHeader = OggPageHeader.read(raf);
521
//else its next packet on this page
524
packet = pageHeader.getPacketList().get(1);
525
List<OggPageHeader.PacketStartAndLength> packetList = pageHeader.getPacketList();
527
//Now at start of next packet , check this is the vorbis setup header
528
b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
530
if (!isVorbisSetupHeader(b))
532
logger.warning("Expecting but got:"+new String(b)+ "at "+(raf.getFilePointer() - b.length));
533
throw new CannotReadException(ErrorMessage.OGG_VORBIS_NO_VORBIS_HEADER_FOUND.getMsg());
535
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
536
logger.info("Found start of vorbis setup header at file position:" + raf.getFilePointer());
538
//Set this to the start of the OggPage that setupheader was found on
539
setupHeaderStartPosition = raf.getFilePointer() - (OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length)
540
- pageHeader.getPacketList().get(0).getLength();
542
//Add packet data to size to the setup header size
543
setupHeaderSize = packet.getLength();
544
logger.fine("Adding:" + packet.getLength() + " to setup header size");
546
//Skip over the packet data
547
raf.skipBytes(packet.getLength());
549
//If there are other packets that follow this one, or if the last packet is complete then we must have
550
//got the size of the setup header.
551
if (packetList.size() > 2 || !pageHeader.isLastPacketIncomplete())
553
logger.fine("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
554
if (packetList.size() > 2)
556
extraPackets = packetList.subList(2, packetList.size());
559
//The setup header continues onto the next page
562
pageHeader = OggPageHeader.read(raf);
563
packetList = pageHeader.getPacketList();
566
setupHeaderSize += packetList.get(0).getLength();
567
logger.fine("Adding:" + packetList.get(0).getLength() + " to setup header size");
568
raf.skipBytes(packetList.get(0).getLength());
569
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
572
logger.fine("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
573
if (packetList.size() > 1)
575
extraPackets = packetList.subList(1, packetList.size());
579
//Continues onto another page
580
pageHeader = OggPageHeader.read(raf);
585
//Reset filepointer to location that it was in at start of method
586
raf.seek(filepointer);
587
return new OggVorbisHeaderSizes(commentHeaderStartPosition, setupHeaderStartPosition, commentHeaderSize, setupHeaderSize, extraPackets);
591
* Find the length of the raw packet data and the start position of the ogg page header they start in
592
* for the two OggVorbisHeader we need to know about when writing data (sizes included vorbis header)
594
public static class OggVorbisHeaderSizes
596
private long commentHeaderStartPosition;
597
private long setupHeaderStartPosition;
598
private int commentHeaderSize;
599
private int setupHeaderSize;
600
private List<OggPageHeader.PacketStartAndLength> packetList;
602
OggVorbisHeaderSizes(long commentHeaderStartPosition, long setupHeaderStartPosition, int commentHeaderSize, int setupHeaderSize, List<OggPageHeader.PacketStartAndLength> packetList)
604
this.packetList = packetList;
605
this.commentHeaderStartPosition = commentHeaderStartPosition;
606
this.setupHeaderStartPosition = setupHeaderStartPosition;
607
this.commentHeaderSize = commentHeaderSize;
608
this.setupHeaderSize = setupHeaderSize;
612
* @return the size of the raw packet data for the vorbis comment header (includes vorbis header)
614
public int getCommentHeaderSize()
616
return commentHeaderSize;
620
* @return he size of the raw packet data for the vorbis setup header (includes vorbis header)
622
public int getSetupHeaderSize()
624
return setupHeaderSize;
628
* Return the size required by all the extra packets on same page as setup header, usually there are
629
* no packets immediately after the setup packet.
631
* @return extra data size required for additional packets on same page
633
public int getExtraPacketDataSize()
635
int extraPacketSize = 0;
636
for (OggPageHeader.PacketStartAndLength packet : packetList)
638
extraPacketSize += packet.getLength();
640
return extraPacketSize;
644
* @return the start position in the file of the ogg header which contains the start of the Vorbis Comment
646
public long getCommentHeaderStartPosition()
648
return commentHeaderStartPosition;
652
* @return the start position in the file of the ogg header which contains the start of the Setup Header
654
public long getSetupHeaderStartPosition()
656
return setupHeaderStartPosition;
659
public List<OggPageHeader.PacketStartAndLength> getExtraPacketList()
2
* Entagged Audio Tag library
3
* Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
4
* Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
6
* This library is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU Lesser General Public
8
* License as published by the Free Software Foundation; either
9
* version 2.1 of the License, or (at your option) any later version.
11
* This library is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* Lesser General Public License for more details.
16
* You should have received a copy of the GNU Lesser General Public
17
* License along with this library; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
package org.jaudiotagger.audio.ogg;
22
import org.jaudiotagger.audio.exceptions.CannotReadException;
23
import org.jaudiotagger.audio.generic.Utils;
24
import org.jaudiotagger.audio.ogg.util.OggPageHeader;
25
import org.jaudiotagger.audio.ogg.util.VorbisHeader;
26
import org.jaudiotagger.audio.ogg.util.VorbisPacketType;
27
import org.jaudiotagger.fix.Fix;
28
import org.jaudiotagger.logging.ErrorMessage;
29
import org.jaudiotagger.tag.Tag;
30
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentReader;
31
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag;
33
import java.io.ByteArrayOutputStream;
34
import java.io.IOException;
35
import java.io.RandomAccessFile;
36
import java.util.ArrayList;
37
import java.util.List;
38
import java.util.logging.Logger;
41
* Read Vorbis Comment Tag within ogg
43
* Vorbis is the audiostream within an ogg file, Vorbis uses VorbisComments as its tag
45
public class OggVorbisTagReader
48
public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.ogg");
50
private VorbisCommentReader vorbisCommentReader;
52
public OggVorbisTagReader()
54
vorbisCommentReader = new VorbisCommentReader();
57
public OggVorbisTagReader(Fix fix)
60
vorbisCommentReader = new VorbisCommentReader(fix);
65
* Read the Logical VorbisComment Tag from the file
67
* <p>Read the CommenyTag, within an OggVorbis file the VorbisCommentTag is mandatory
71
* @throws CannotReadException
74
public Tag read(RandomAccessFile raf) throws CannotReadException, IOException
76
logger.info("Starting to read ogg vorbis tag from file:");
77
byte[] rawVorbisCommentData = readRawPacketData(raf);
80
VorbisCommentTag tag = vorbisCommentReader.read(rawVorbisCommentData, true);
81
logger.fine("CompletedReadCommentTag");
86
* Retrieve the Size of the VorbisComment packet including the oggvorbis header
90
* @throws CannotReadException
93
public int readOggVorbisRawSize(RandomAccessFile raf) throws CannotReadException, IOException
95
byte[] rawVorbisCommentData = readRawPacketData(raf);
96
return rawVorbisCommentData.length + VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH;
100
* Retrieve the raw VorbisComment packet data, does not include the OggVorbis header
104
* @throws CannotReadException if unable to find vorbiscomment header
105
* @throws IOException
107
public byte[] readRawPacketData(RandomAccessFile raf) throws CannotReadException, IOException
109
logger.fine("Read 1st page");
110
//1st page = codec infos
111
OggPageHeader pageHeader = OggPageHeader.read(raf);
112
//Skip over data to end of page header 1
113
raf.seek(raf.getFilePointer() + pageHeader.getPageLength());
115
logger.fine("Read 2nd page");
116
//2nd page = comment, may extend to additional pages or not , may also have setup header
117
pageHeader = OggPageHeader.read(raf);
119
//Now at start of packets on page 2 , check this is the vorbis comment header
120
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
122
if (!isVorbisCommentHeader(b))
124
throw new CannotReadException("Cannot find comment block (no vorbiscomment header)");
127
//Convert the comment raw data which maybe over many pages back into raw packet
128
byte[] rawVorbisCommentData = convertToVorbisCommentPacket(pageHeader, raf);
129
return rawVorbisCommentData;
134
* Is this a Vorbis Comment header, check
136
* Note this check only applies to Vorbis Comments embedded within an OggVorbis File which is why within here
139
* @return true if the headerData matches a VorbisComment header i.e is a Vorbis header of type COMMENT_HEADER
141
public boolean isVorbisCommentHeader(byte[] headerData)
143
String vorbis = Utils.getString(headerData, VorbisHeader.FIELD_CAPTURE_PATTERN_POS, VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH, "ISO-8859-1");
144
return !(headerData[VorbisHeader.FIELD_PACKET_TYPE_POS] != VorbisPacketType.COMMENT_HEADER.getType() || !vorbis.equals(VorbisHeader.CAPTURE_PATTERN));
148
* Is this a Vorbis SetupHeader check
151
* @return true if matches vorbis setupheader
153
public boolean isVorbisSetupHeader(byte[] headerData)
155
String vorbis = Utils.getString(headerData, VorbisHeader.FIELD_CAPTURE_PATTERN_POS, VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH, "ISO-8859-1");
156
return !(headerData[VorbisHeader.FIELD_PACKET_TYPE_POS] != VorbisPacketType.SETUP_HEADER.getType() || !vorbis.equals(VorbisHeader.CAPTURE_PATTERN));
160
* The Vorbis Comment may span multiple pages so we we need to identify the pages they contain and then
161
* extract the packet data from the pages
162
* @param startVorbisCommentPage
164
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
165
* @throws java.io.IOException
168
private byte[] convertToVorbisCommentPacket(OggPageHeader startVorbisCommentPage, RandomAccessFile raf) throws IOException, CannotReadException
170
ByteArrayOutputStream baos = new ByteArrayOutputStream();
171
byte[] b = new byte[startVorbisCommentPage.getPacketList().get(0).getLength() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH)];
175
//Because there is at least one other packet (SetupHeaderPacket) this means the Comment Packet has finished
176
//on this page so thats all we need and we can return
177
if (startVorbisCommentPage.getPacketList().size() > 1)
179
logger.info("Comments finish on 2nd Page because there is another packet on this page");
180
return baos.toByteArray();
183
//There is only the VorbisComment packet on page if it has completed on this page we can return
184
if (!startVorbisCommentPage.isLastPacketIncomplete())
186
logger.info("Comments finish on 2nd Page because this packet is complete");
187
return baos.toByteArray();
190
//The VorbisComment extends to the next page, so should be at end of page already
191
//so carry on reading pages until we get to the end of comment
194
logger.info("Reading next page");
195
OggPageHeader nextPageHeader = OggPageHeader.read(raf);
196
b = new byte[nextPageHeader.getPacketList().get(0).getLength()];
200
//Because there is at least one other packet (SetupHeaderPacket) this means the Comment Packet has finished
201
//on this page so thats all we need and we can return
202
if (nextPageHeader.getPacketList().size() > 1)
204
logger.info("Comments finish on Page because there is another packet on this page");
205
return baos.toByteArray();
208
//There is only the VorbisComment packet on page if it has completed on this page we can return
209
if (!nextPageHeader.isLastPacketIncomplete())
211
logger.info("Comments finish on Page because this packet is complete");
212
return baos.toByteArray();
218
* The Vorbis Setup Header may span multiple(2) pages, athough it doesnt normally. We pass the start of the
219
* file offset of the OggPage it belongs on, it probably won't be first packet.
220
* @param fileOffsetOfStartingOggPage
222
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
223
* @throws java.io.IOException
226
public byte[] convertToVorbisSetupHeaderPacket(long fileOffsetOfStartingOggPage, RandomAccessFile raf) throws IOException, CannotReadException
228
ByteArrayOutputStream baos = new ByteArrayOutputStream();
230
//Seek to specified offset
231
raf.seek(fileOffsetOfStartingOggPage);
234
OggPageHeader setupPageHeader = OggPageHeader.read(raf);
236
//Assume that if multiple packets first packet is VorbisComment and second packet
238
if (setupPageHeader.getPacketList().size() > 1)
240
raf.skipBytes(setupPageHeader.getPacketList().get(0).getLength());
243
//Now should be at start of next packet, check this is the vorbis setup header
244
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
246
if (!isVorbisSetupHeader(b))
248
throw new CannotReadException("Unable to find setup header(2), unable to write ogg file");
251
//Go back to start of setupheader data
252
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
255
if (setupPageHeader.getPacketList().size() > 1)
257
b = new byte[setupPageHeader.getPacketList().get(1).getLength()];
263
b = new byte[setupPageHeader.getPacketList().get(0).getLength()];
269
if (!setupPageHeader.isLastPacketIncomplete() || setupPageHeader.getPacketList().size() > 2)
271
logger.info("Setupheader finishes on this page");
272
return baos.toByteArray();
275
//The Setupheader extends to the next page, so should be at end of page already
276
//so carry on reading pages until we get to the end of comment
279
logger.info("Reading another page");
280
OggPageHeader nextPageHeader = OggPageHeader.read(raf);
281
b = new byte[nextPageHeader.getPacketList().get(0).getLength()];
285
//Because there is at least one other packet this means the Setupheader Packet has finished
286
//on this page so thats all we need and we can return
287
if (nextPageHeader.getPacketList().size() > 1)
289
logger.info("Setupheader finishes on this page");
290
return baos.toByteArray();
293
//There is only the Setupheader packet on page if it has completed on this page we can return
294
if (!nextPageHeader.isLastPacketIncomplete())
296
logger.info("Setupheader finish on Page because this packet is complete");
297
return baos.toByteArray();
304
* The Vorbis Setup Header may span multiple(2) pages, athough it doesnt normally. We pass the start of the
305
* file offset of the OggPage it belongs on, it probably won't be first packet, also returns any addditional
306
* packets that immediately follow the setup header in original file
307
* @param fileOffsetOfStartingOggPage
309
* @throws org.jaudiotagger.audio.exceptions.CannotReadException
310
* @throws java.io.IOException
313
public byte[] convertToVorbisSetupHeaderPacketAndAdditionalPackets(long fileOffsetOfStartingOggPage, RandomAccessFile raf) throws IOException, CannotReadException
315
ByteArrayOutputStream baos = new ByteArrayOutputStream();
317
//Seek to specified offset
318
raf.seek(fileOffsetOfStartingOggPage);
321
OggPageHeader setupPageHeader = OggPageHeader.read(raf);
323
//Assume that if multiple packets first packet is VorbisComment and second packet
325
if (setupPageHeader.getPacketList().size() > 1)
327
raf.skipBytes(setupPageHeader.getPacketList().get(0).getLength());
330
//Now should be at start of next packet, check this is the vorbis setup header
331
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
333
if (!isVorbisSetupHeader(b))
335
throw new CannotReadException("Unable to find setup header(2), unable to write ogg file");
338
//Go back to start of setupheader data
339
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
342
if (setupPageHeader.getPacketList().size() > 1)
344
b = new byte[setupPageHeader.getPacketList().get(1).getLength()];
350
b = new byte[setupPageHeader.getPacketList().get(0).getLength()];
356
if (!setupPageHeader.isLastPacketIncomplete() || setupPageHeader.getPacketList().size() > 2)
358
logger.info("Setupheader finishes on this page");
359
if (setupPageHeader.getPacketList().size() > 2)
361
for (int i = 2; i < setupPageHeader.getPacketList().size(); i++)
363
b = new byte[setupPageHeader.getPacketList().get(i).getLength()];
368
return baos.toByteArray();
371
//The Setupheader extends to the next page, so should be at end of page already
372
//so carry on reading pages until we get to the end of comment
375
logger.info("Reading another page");
376
OggPageHeader nextPageHeader = OggPageHeader.read(raf);
377
b = new byte[nextPageHeader.getPacketList().get(0).getLength()];
381
//Because there is at least one other packet this means the Setupheader Packet has finished
382
//on this page so thats all we need and we can return
383
if (nextPageHeader.getPacketList().size() > 1)
385
logger.info("Setupheader finishes on this page");
386
return baos.toByteArray();
389
//There is only the Setupheader packet on page if it has completed on this page we can return
390
if (!nextPageHeader.isLastPacketIncomplete())
392
logger.info("Setupheader finish on Page because this packet is complete");
393
return baos.toByteArray();
400
* Calculate the size of the packet data for the comment and setup headers
404
* @throws CannotReadException
405
* @throws IOException
407
public OggVorbisHeaderSizes readOggVorbisHeaderSizes(RandomAccessFile raf) throws CannotReadException, IOException
409
logger.fine("Started to read comment and setup header sizes:");
411
//Stores filepointers so return file in same state
412
long filepointer = raf.getFilePointer();
414
//Extra Packets on same page as setup header
415
List<OggPageHeader.PacketStartAndLength> extraPackets = new ArrayList<OggPageHeader.PacketStartAndLength>();
417
long commentHeaderStartPosition;
418
long setupHeaderStartPosition;
419
int commentHeaderSize = 0;
421
//1st page = codec infos
422
OggPageHeader pageHeader = OggPageHeader.read(raf);
423
//Skip over data to end of page header 1
424
raf.seek(raf.getFilePointer() + pageHeader.getPageLength());
426
//2nd page = comment, may extend to additional pages or not , may also have setup header
427
pageHeader = OggPageHeader.read(raf);
428
commentHeaderStartPosition = raf.getFilePointer() - (OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length);
430
//Now at start of packets on page 2 , check this is the vorbis comment header
431
byte[] b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
433
if (!isVorbisCommentHeader(b))
435
throw new CannotReadException("Cannot find comment block (no vorbiscomment header)");
437
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
438
logger.info("Found start of comment header at:" + raf.getFilePointer());
440
//Calculate Comment Size (not inc header)
443
List<OggPageHeader.PacketStartAndLength> packetList = pageHeader.getPacketList();
444
commentHeaderSize += packetList.get(0).getLength();
445
raf.skipBytes(packetList.get(0).getLength());
447
//If this page contains multiple packets or if this last packet is complete then the Comment header
448
//end son this page and we can break
449
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
452
logger.info("Found end of comment:size:" + commentHeaderSize + "finishes at file position:" + raf.getFilePointer());
455
pageHeader = OggPageHeader.read(raf);
458
//If there are no more packets on this page we need to go to next page to get the setup header
459
OggPageHeader.PacketStartAndLength packet;
460
if(pageHeader.getPacketList().size()==1)
462
pageHeader = OggPageHeader.read(raf);
463
List<OggPageHeader.PacketStartAndLength> packetList = pageHeader.getPacketList();
464
packet = pageHeader.getPacketList().get(0);
466
//Now at start of next packet , check this is the vorbis setup header
467
b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
469
if (!isVorbisSetupHeader(b))
471
throw new CannotReadException(ErrorMessage.OGG_VORBIS_NO_VORBIS_HEADER_FOUND.getMsg());
473
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
474
logger.info("Found start of vorbis setup header at file position:" + raf.getFilePointer());
476
//Set this to the start of the OggPage that setupheader was found on
477
setupHeaderStartPosition = raf.getFilePointer() - (OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length);
479
//Add packet data to size to the setup header size
480
setupHeaderSize = packet.getLength();
481
logger.fine("Adding:" + packet.getLength() + " to setup header size");
483
//Skip over the packet data
484
raf.skipBytes(packet.getLength());
486
//If there are other packets that follow this one, or if the last packet is complete then we must have
487
//got the size of the setup header.
488
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
490
logger.info("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
491
if (packetList.size() > 1)
493
extraPackets = packetList.subList(1, packetList.size());
496
//The setup header continues onto the next page
499
pageHeader = OggPageHeader.read(raf);
500
packetList = pageHeader.getPacketList();
503
setupHeaderSize += packetList.get(0).getLength();
504
logger.fine("Adding:" + packetList.get(0).getLength() + " to setup header size");
505
raf.skipBytes(packetList.get(0).getLength());
506
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
509
logger.fine("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
510
if (packetList.size() > 1)
512
extraPackets = packetList.subList(1, packetList.size());
516
//Continues onto another page
517
pageHeader = OggPageHeader.read(raf);
521
//else its next packet on this page
524
packet = pageHeader.getPacketList().get(1);
525
List<OggPageHeader.PacketStartAndLength> packetList = pageHeader.getPacketList();
527
//Now at start of next packet , check this is the vorbis setup header
528
b = new byte[VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH];
530
if (!isVorbisSetupHeader(b))
532
logger.warning("Expecting but got:"+new String(b)+ "at "+(raf.getFilePointer() - b.length));
533
throw new CannotReadException(ErrorMessage.OGG_VORBIS_NO_VORBIS_HEADER_FOUND.getMsg());
535
raf.seek(raf.getFilePointer() - (VorbisHeader.FIELD_PACKET_TYPE_LENGTH + VorbisHeader.FIELD_CAPTURE_PATTERN_LENGTH));
536
logger.info("Found start of vorbis setup header at file position:" + raf.getFilePointer());
538
//Set this to the start of the OggPage that setupheader was found on
539
setupHeaderStartPosition = raf.getFilePointer() - (OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length)
540
- pageHeader.getPacketList().get(0).getLength();
542
//Add packet data to size to the setup header size
543
setupHeaderSize = packet.getLength();
544
logger.fine("Adding:" + packet.getLength() + " to setup header size");
546
//Skip over the packet data
547
raf.skipBytes(packet.getLength());
549
//If there are other packets that follow this one, or if the last packet is complete then we must have
550
//got the size of the setup header.
551
if (packetList.size() > 2 || !pageHeader.isLastPacketIncomplete())
553
logger.fine("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
554
if (packetList.size() > 2)
556
extraPackets = packetList.subList(2, packetList.size());
559
//The setup header continues onto the next page
562
pageHeader = OggPageHeader.read(raf);
563
packetList = pageHeader.getPacketList();
566
setupHeaderSize += packetList.get(0).getLength();
567
logger.fine("Adding:" + packetList.get(0).getLength() + " to setup header size");
568
raf.skipBytes(packetList.get(0).getLength());
569
if (packetList.size() > 1 || !pageHeader.isLastPacketIncomplete())
572
logger.fine("Found end of setupheader:size:" + setupHeaderSize + "finishes at:" + raf.getFilePointer());
573
if (packetList.size() > 1)
575
extraPackets = packetList.subList(1, packetList.size());
579
//Continues onto another page
580
pageHeader = OggPageHeader.read(raf);
585
//Reset filepointer to location that it was in at start of method
586
raf.seek(filepointer);
587
return new OggVorbisHeaderSizes(commentHeaderStartPosition, setupHeaderStartPosition, commentHeaderSize, setupHeaderSize, extraPackets);
591
* Find the length of the raw packet data and the start position of the ogg page header they start in
592
* for the two OggVorbisHeader we need to know about when writing data (sizes included vorbis header)
594
public static class OggVorbisHeaderSizes
596
private long commentHeaderStartPosition;
597
private long setupHeaderStartPosition;
598
private int commentHeaderSize;
599
private int setupHeaderSize;
600
private List<OggPageHeader.PacketStartAndLength> packetList;
602
OggVorbisHeaderSizes(long commentHeaderStartPosition, long setupHeaderStartPosition, int commentHeaderSize, int setupHeaderSize, List<OggPageHeader.PacketStartAndLength> packetList)
604
this.packetList = packetList;
605
this.commentHeaderStartPosition = commentHeaderStartPosition;
606
this.setupHeaderStartPosition = setupHeaderStartPosition;
607
this.commentHeaderSize = commentHeaderSize;
608
this.setupHeaderSize = setupHeaderSize;
612
* @return the size of the raw packet data for the vorbis comment header (includes vorbis header)
614
public int getCommentHeaderSize()
616
return commentHeaderSize;
620
* @return he size of the raw packet data for the vorbis setup header (includes vorbis header)
622
public int getSetupHeaderSize()
624
return setupHeaderSize;
628
* Return the size required by all the extra packets on same page as setup header, usually there are
629
* no packets immediately after the setup packet.
631
* @return extra data size required for additional packets on same page
633
public int getExtraPacketDataSize()
635
int extraPacketSize = 0;
636
for (OggPageHeader.PacketStartAndLength packet : packetList)
638
extraPacketSize += packet.getLength();
640
return extraPacketSize;
644
* @return the start position in the file of the ogg header which contains the start of the Vorbis Comment
646
public long getCommentHeaderStartPosition()
648
return commentHeaderStartPosition;
652
* @return the start position in the file of the ogg header which contains the start of the Setup Header
654
public long getSetupHeaderStartPosition()
656
return setupHeaderStartPosition;
659
public List<OggPageHeader.PacketStartAndLength> getExtraPacketList()