org.jcodec.containers.mp4.QTTimeUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcodec Show documentation
Show all versions of jcodec Show documentation
Pure Java implementation of video/audio codecs and formats
package org.jcodec.containers.mp4;
import static org.jcodec.containers.mp4.boxes.Box.findFirst;
import java.io.IOException;
import java.util.List;
import org.jcodec.common.model.RationalLarge;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TimecodeSampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.demuxer.TimecodeMP4DemuxerTrack;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Quicktime time conversion utilities
*
* @author The JCodec project
*
*/
public class QTTimeUtil {
/**
* Calculates track duration considering edits
*
* @param track
* @return
*/
public static long getEditedDuration(TrakBox track) {
List edits = track.getEdits();
if (edits == null)
return track.getDuration();
long duration = 0;
for (Edit edit : edits) {
duration += edit.getDuration();
}
return duration;
}
/**
* Finds timevalue of a frame number
*
* might be an expensive operation sinse it traverses compressed time to
* sample table
*
* @param frameNumber
* @return
*/
public static long frameToTimevalue(TrakBox trak, int frameNumber) {
TimeToSampleBox stts = findFirst(trak, TimeToSampleBox.class, "mdia", "minf", "stbl", "stts");
TimeToSampleEntry[] timeToSamples = stts.getEntries();
long pts = 0;
int sttsInd = 0, sttsSubInd = frameNumber;
while (sttsSubInd >= timeToSamples[sttsInd].getSampleCount()) {
sttsSubInd -= timeToSamples[sttsInd].getSampleCount();
pts += timeToSamples[sttsInd].getSampleCount() * timeToSamples[sttsInd].getSampleDuration();
sttsInd++;
}
return pts + timeToSamples[sttsInd].getSampleDuration() * sttsSubInd;
}
/**
* Finds frame by timevalue
*
* @param tv
* @return
*/
public static int timevalueToFrame(TrakBox trak, long tv) {
TimeToSampleEntry[] tts = findFirst(trak, TimeToSampleBox.class, "mdia", "minf", "stbl", "stts").getEntries();
int frame = 0;
for (int i = 0; tv > 0 && i < tts.length; i++) {
long rem = tv / tts[i].getSampleDuration();
tv -= tts[i].getSampleCount() * tts[i].getSampleDuration();
frame += tv > 0 ? tts[i].getSampleCount() : rem;
}
return frame;
}
/**
* Converts media timevalue to edited timevalue
*
* @param trak
* @param mediaTv
* @param movieTimescale
* @return
*/
public static long mediaToEdited(TrakBox trak, long mediaTv, int movieTimescale) {
if (trak.getEdits() == null)
return mediaTv;
long accum = 0;
for (Edit edit : trak.getEdits()) {
if (mediaTv < edit.getMediaTime())
return accum;
long duration = trak.rescale(edit.getDuration(), movieTimescale);
if (edit.getMediaTime() != -1
&& (mediaTv >= edit.getMediaTime() && mediaTv < edit.getMediaTime() + duration)) {
accum += mediaTv - edit.getMediaTime();
break;
}
accum += duration;
}
return accum;
}
/**
* Converts edited timevalue to media timevalue
*
* @param trak
* @param mediaTv
* @param movieTimescale
* @return
*/
public static long editedToMedia(TrakBox trak, long editedTv, int movieTimescale) {
if (trak.getEdits() == null)
return editedTv;
long accum = 0;
for (Edit edit : trak.getEdits()) {
long duration = trak.rescale(edit.getDuration(), movieTimescale);
if (accum + duration > editedTv) {
return edit.getMediaTime() + editedTv - accum;
}
accum += duration;
}
return accum;
}
/**
* Calculates frame number as it shows in quicktime player
*
* @param movie
* @param mediaFrameNo
* @return
*/
public static int qtPlayerFrameNo(MovieBox movie, int mediaFrameNo) {
TrakBox videoTrack = movie.getVideoTrack();
long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());
return tv2QTFrameNo(movie, editedTv);
}
public static int tv2QTFrameNo(MovieBox movie, long tv) {
TrakBox videoTrack = movie.getVideoTrack();
TrakBox timecodeTrack = movie.getTimecodeTrack();
if (timecodeTrack != null && Box.findFirst(videoTrack, "tref", "tmcd") != null) {
return timevalueToTimecodeFrame(timecodeTrack, new RationalLarge(tv, videoTrack.getTimescale()),
movie.getTimescale());
} else {
return timevalueToFrame(videoTrack, tv);
}
}
/**
* Calculates and formats standard time as in Quicktime player
*
* @param movie
* @param mediaFrameNo
* @return
*/
public static String qtPlayerTime(MovieBox movie, int mediaFrameNo) {
TrakBox videoTrack = movie.getVideoTrack();
long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());
int sec = (int) (editedTv / videoTrack.getTimescale());
return String.format("%02d", sec / 3600) + "_" + String.format("%02d", (sec % 3600) / 60) + "_"
+ String.format("%02d", sec % 60);
}
/**
* Calculates and formats tape timecode as in Quicktime player
*
* @param timecodeTrack
* @param tv
* @param startCounter
* @return
* @throws IOException
*/
public static String qtPlayerTimecode(MovieBox movie, TimecodeMP4DemuxerTrack timecodeTrack, int mediaFrameNo)
throws IOException {
TrakBox videoTrack = movie.getVideoTrack();
long editedTv = mediaToEdited(videoTrack, frameToTimevalue(videoTrack, mediaFrameNo), movie.getTimescale());
TrakBox tt = timecodeTrack.getBox();
int ttTimescale = tt.getTimescale();
long ttTv = editedToMedia(tt, editedTv * ttTimescale / videoTrack.getTimescale(), movie.getTimescale());
return formatTimecode(
timecodeTrack.getBox(),
timecodeTrack.getStartTimecode()
+ timevalueToTimecodeFrame(timecodeTrack.getBox(), new RationalLarge(ttTv, ttTimescale),
movie.getTimescale()));
}
/**
* Calculates and formats tape timecode as in Quicktime player
*
* @param timecodeTrack
* @param tv
* @param startCounter
* @return
* @throws IOException
*/
public static String qtPlayerTimecode(TimecodeMP4DemuxerTrack timecodeTrack, RationalLarge tv, int movieTimescale)
throws IOException {
TrakBox tt = timecodeTrack.getBox();
int ttTimescale = tt.getTimescale();
long ttTv = editedToMedia(tt, tv.multiplyS(ttTimescale), movieTimescale);
return formatTimecode(
timecodeTrack.getBox(),
timecodeTrack.getStartTimecode()
+ timevalueToTimecodeFrame(timecodeTrack.getBox(), new RationalLarge(ttTv, ttTimescale),
movieTimescale));
}
/**
* Converts timevalue to frame number based on timecode track
*
* @param timecodeTrack
* @param tv
* @return
*/
public static int timevalueToTimecodeFrame(TrakBox timecodeTrack, RationalLarge tv, int movieTimescale) {
TimecodeSampleEntry se = (TimecodeSampleEntry) timecodeTrack.getSampleEntries()[0];
return (int) ((2 * tv.multiplyS(se.getTimescale()) / se.getFrameDuration()) + 1) / 2;
}
/**
* Formats tape timecode based on frame counter
*
* @param timecodeTrack
* @param counter
* @return
*/
public static String formatTimecode(TrakBox timecodeTrack, int counter) {
TimecodeSampleEntry tmcd = Box.findFirst(timecodeTrack, TimecodeSampleEntry.class, "mdia", "minf", "stbl",
"stsd", "tmcd");
byte nf = tmcd.getNumFrames();
String tc = String.format("%02d", counter % nf);
counter /= nf;
tc = String.format("%02d", counter % 60) + ":" + tc;
counter /= 60;
tc = String.format("%02d", counter % 60) + ":" + tc;
counter /= 60;
tc = String.format("%02d", counter) + ":" + tc;
return tc;
}
}