All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jcodec.containers.mp4.QTTimeUtil Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy