/* JOrbisComment -- pure Java Ogg Vorbis Comment Editor * * Copyright (C) 2000 ymnk, JCraft,Inc. * * Written by: 2000 ymnk<ymnk@jcaft.com> * * Many thanks to * Monty <monty@xiph.org> and * The XIPHOPHORUS Company http://www.xiph.org/ . * JOrbis has been based on their awesome works, Vorbis codec and * JOrbisPlayer depends on JOrbis. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * modified heavily to not be standalane program for * inlusion in the limewire code. */ package com.limegroup.gnutella.metadata; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.jcraft.jogg.Packet; import com.jcraft.jogg.Page; import com.jcraft.jogg.StreamState; import com.jcraft.jogg.SyncState; import com.jcraft.jorbis.Comment; import com.jcraft.jorbis.Info; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.util.FileUtils; public class JOrbisComment { private static final Log LOG = LogFactory.getLog(JOrbisComment.class); private State state=null; /** * updates the given ogg file with the new Comment field * @param comment the <tt>com.jcraft.jorbis.Comment</tt> object to * put in the file * @param file the .ogg file to be updated */ public void update(Comment comment, File file) throws IOException{ InputStream in =null; OutputStream out = null; File tempFile = null; try { state =new State(); in =new BufferedInputStream(new FileInputStream(file)); read(in); //update the comment state.vc=comment; //copy the newly created file in a temp folder tempFile=null; try { tempFile = File.createTempFile(file.getName(),"tmp"); }catch(IOException e) { //sometimes either the temp path is messed up or // there isn't enough space on that partition. //try to create a temp file on the same folder as the //original. It will not be around long enough to get shared //if an exception is thrown, let it propagate LOG.debug("couldn't create temp file in $TEMP, trying elsewhere"); tempFile = new File(file.getAbsolutePath(), file.getName()+".tmp"); } out=new BufferedOutputStream(new FileOutputStream(tempFile)); LOG.debug("about to write ogg file"); write(out); out.flush(); }finally { if (out!=null) try {out.close(); }catch(IOException ignored){} if (in!=null) try {in.close(); }catch(IOException ignored){} } if (tempFile.length() == 0) throw new IOException("writing of file failed"); //rename fails on some rare filesystem setups if (!FileUtils.forceRename(tempFile,file)) //something's seriously wrong throw new IOException("couldn't rename file"); } private static int CHUNKSIZE=4096; void read(InputStream in) throws IOException{ state.in=in; Page og=new Page(); int index; byte[] buffer; int bytes=0; state.oy=new SyncState(); state.oy.init(); index=state.oy.buffer(CHUNKSIZE); buffer=state.oy.data; bytes=state.in.read(buffer, index, CHUNKSIZE); state.oy.wrote(bytes); if(state.oy.pageout(og)!=1) throw new IOException("input truncated, empty or not an ogg"); state.serial=og.serialno(); state.os= new StreamState(); state.os.init(state.serial); // os.reset(); state.vi=new Info(); state.vi.init(); state.vc=new Comment(); state.vc.init(); if(state.os.pagein(og)<0) throw new IOException ("Error reading first page of Ogg bitstream data."); Packet header_main = new Packet(); if(state.os.packetout(header_main)!=1) throw new IOException("Error reading initial header packet."); if(state.vi.synthesis_headerin(state.vc, header_main)<0) throw new IOException("This Ogg bitstream does not contain Vorbis data."); state.mainlen=header_main.bytes; state.mainbuf=new byte[state.mainlen]; System.arraycopy(header_main.packet_base, header_main.packet, state.mainbuf, 0, state.mainlen); int i=0; Packet header; Packet header_comments=new Packet(); Packet header_codebooks=new Packet(); header=header_comments; while(i<2) { while(i<2) { int result = state.oy.pageout(og); if(result == 0) break; /* Too little data so far */ else if(result == 1){ state.os.pagein(og); while(i<2){ result = state.os.packetout(header); if(result == 0) break; if(result == -1) throw new IOException("Corrupt secondary header."); state.vi.synthesis_headerin(state.vc, header); if(i==1) { state.booklen=header.bytes; state.bookbuf=new byte[state.booklen]; System.arraycopy(header.packet_base, header.packet, state.bookbuf, 0, header.bytes); } i++; header = header_codebooks; } } } index=state.oy.buffer(CHUNKSIZE); buffer=state.oy.data; bytes=state.in.read(buffer, index, CHUNKSIZE); if(bytes == 0 && i < 2) throw new IOException("EOF before end of vorbis headers."); state.oy.wrote(bytes); } //System.out.println(state.vi); } int write(OutputStream out) throws IOException{ StreamState streamout=new StreamState(); Packet header_main=new Packet(); Packet header_comments=new Packet(); Packet header_codebooks=new Packet(); Page ogout=new Page(); Packet op=new Packet(); long granpos = 0; int result; int index; byte[] buffer; int bytes, eosin=0; int needflush=0, needout=0; header_main.bytes = state.mainlen; header_main.packet_base= state.mainbuf; header_main.packet = 0; header_main.b_o_s = 1; header_main.e_o_s = 0; header_main.granulepos = 0; header_codebooks.bytes = state.booklen; header_codebooks.packet_base = state.bookbuf; header_codebooks.packet = 0; header_codebooks.b_o_s = 0; header_codebooks.e_o_s = 0; header_codebooks.granulepos = 0; streamout.init(state.serial); state.vc.header_out(header_comments); streamout.packetin(header_main); streamout.packetin(header_comments); streamout.packetin(header_codebooks); //System.out.println("%1"); while((result=streamout.flush(ogout))!=0){ //System.out.println("result="+result); out.write(ogout.header_base, ogout.header, ogout.header_len); out.flush(); out.write(ogout.body_base, ogout.body,ogout.body_len); out.flush(); } //System.out.println("%2"); while(state.fetch_next_packet(op)!=0){ int size=state.blocksize(op); granpos+=size; //System.out.println("#1"); if(needflush!=0){ //System.out.println("##1"); if(streamout.flush(ogout)!=0){ out.write(ogout.header_base,ogout.header,ogout.header_len); out.flush(); out.write(ogout.body_base,ogout.body,ogout.body_len); out.flush(); } } //System.out.println("%2 eosin="+eosin); else if(needout!=0){ //System.out.println("##2"); if(streamout.pageout(ogout)!=0){ out.write(ogout.header_base,ogout.header,ogout.header_len); out.flush(); out.write(ogout.body_base,ogout.body,ogout.body_len); out.flush(); } } //System.out.println("#2"); needflush=needout=0; if(op.granulepos==-1){ op.granulepos=granpos; streamout.packetin(op); } else{ if(granpos>op.granulepos){ granpos=op.granulepos; streamout.packetin(op); needflush=1; } else{ streamout.packetin(op); needout=1; } } //System.out.println("#3"); } //System.out.println("%3"); streamout.e_o_s=1; while(streamout.flush(ogout)!=0){ out.write(ogout.header_base,ogout.header,ogout.header_len); out.flush(); out.write(ogout.body_base,ogout.body,ogout.body_len); out.flush(); } //System.out.println("%4"); state.vi.clear(); //System.out.println("%3 eosin="+eosin); //System.out.println("%5"); eosin=0; /* clear it, because not all paths to here do */ while(eosin==0){ /* We reached eos, not eof */ /* We copy the rest of the stream (other logical streams) * through, a page at a time. */ while(true){ result=state.oy.pageout(ogout); //System.out.println(" result4="+result); if(result==0) break; if(result<0){ if (LOG.isDebugEnabled()) LOG.debug("Corrupt or missing data, continuing..."); } else{ /* Don't bother going through the rest, we can just * write the page out now */ out.write(ogout.header_base,ogout.header,ogout.header_len); out.flush(); out.write(ogout.body_base,ogout.body,ogout.body_len); out.flush(); } } index=state.oy.buffer(CHUNKSIZE); buffer=state.oy.data; bytes=state.in.read(buffer, index, CHUNKSIZE); //System.out.println("bytes="+bytes); state.oy.wrote(bytes); if(bytes == 0 || bytes==-1) { eosin = 1; break; } } /* cleanup: ogg_stream_clear(&streamout); ogg_packet_clear(&header_comments); free(state->mainbuf); free(state->bookbuf); jorbiscomment_clear_internals(state); if(!eosin) { state->lasterror = "Error writing stream to output. " "Output stream may be corrupted or truncated."; return -1; } return 0; } */ return 0; } class State{ private final int CHUNKSIZE=4096; SyncState oy; StreamState os; Comment vc; Info vi; InputStream in; int serial; byte[] mainbuf; byte[] bookbuf; int mainlen; int booklen; String lasterror; int prevW; int blocksize(Packet p){ int _this = vi.blocksize(p); int ret = (_this + prevW)/4; if(prevW==0){ prevW=_this; return 0; } prevW = _this; return ret; } Page og=new Page(); int fetch_next_packet(Packet p){ int result; byte[] buffer; int index; int bytes; result = os.packetout(p); if(result > 0){ return 1; } while(oy.pageout(og) <= 0){ index=oy.buffer(CHUNKSIZE); buffer=oy.data; try{ bytes=in.read(buffer, index, CHUNKSIZE); } catch(Exception e){ ErrorService.error(e); return 0; } if(bytes>0) oy.wrote(bytes); if(bytes==0 || bytes==-1) { return 0; } } os.pagein(og); return fetch_next_packet(p); } } }