<commit_before>package org.jcodec.movtool.streaming;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.LongArrayList;
import org.jcodec.common.model.Rational;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.Brand;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.ChannelBox;
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ClearApertureBox;
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox;
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox.Entry;
import org.jcodec.containers.mp4.boxes.DataInfoBox;
import org.jcodec.containers.mp4.boxes.DataRefBox;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.EncodedPixelBox;
import org.jcodec.containers.mp4.boxes.GenericMediaInfoBox;
import org.jcodec.containers.mp4.boxes.HandlerBox;
import org.jcodec.containers.mp4.boxes.Header;
import org.jcodec.containers.mp4.boxes.LeafBox;
import org.jcodec.containers.mp4.boxes.MediaBox;
import org.jcodec.containers.mp4.boxes.MediaHeaderBox;
import org.jcodec.containers.mp4.boxes.MediaInfoBox;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.MovieHeaderBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.PixelAspectExt;
import org.jcodec.containers.mp4.boxes.ProductionApertureBox;
import org.jcodec.containers.mp4.boxes.SampleDescriptionBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
import org.jcodec.containers.mp4.boxes.SoundMediaHeaderBox;
import org.jcodec.containers.mp4.boxes.SyncSamplesBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TimecodeMediaInfoBox;
import org.jcodec.containers.mp4.boxes.TrackHeaderBox;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.boxes.VideoMediaHeaderBox;
import org.jcodec.containers.mp4.boxes.channel.ChannelUtils;
import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.movtool.streaming.VirtualMP4Movie.PacketChunk;
import org.jcodec.movtool.streaming.VirtualTrack.VirtualEdit;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 * 
 * Contains methods to mux virtual movie tracks into real real MP4 movie header
 * 
 * @author The JCodec project
 * 
 */
public class MovieHelper {

    private static final int MEBABYTE = 1024 * 1024;

    private static int timescales[] = { 10000, 12000, 15000, 24000, 25000, 30000, 50000, 41000, 48000, 96000 };

    public static ByteBuffer produceHeader(PacketChunk[] chunks, VirtualTrack[] tracks, long dataSize, Brand brand)
            throws IOException {
        int defaultTimescale = 1000;

        ByteBuffer buf = ByteBuffer.allocate(6 * MEBABYTE);
        MovieBox movie = new MovieBox();

        double[] trackDurations = calcTrackDurations(chunks, tracks);
        long movieDur = calcMovieDuration(tracks, defaultTimescale, trackDurations);
        movie.add(movieHeader(movie, tracks.length, movieDur, defaultTimescale));

        for (int trackId = 0; trackId < tracks.length; trackId++) {

            // TODO: optimal timescale selection
            VirtualTrack track = tracks[trackId];
            CodecMeta codecMeta = track.getCodecMeta();

            boolean pcm = (codecMeta instanceof AudioCodecMeta) && ((AudioCodecMeta) codecMeta).isPCM();

            int trackTimescale = track.getPreferredTimescale();
            if (trackTimescale <= 0) {
                if (pcm)
                    trackTimescale = getPCMTs((AudioCodecMeta) codecMeta, chunks, trackId);
                else
                    trackTimescale = chooseTimescale(chunks, trackId);
            } else if (trackTimescale < 100) {
                trackTimescale *= 1000;
            } else if (trackTimescale < 1000) {
                trackTimescale *= 100;
            } else if (trackTimescale < 10000) {
                trackTimescale *= 10;
            }

            long totalDur = (long) (trackTimescale * trackDurations[trackId]);

            TrakBox trak = new TrakBox();

            Size dd = new Size(0, 0), sd = new Size(0, 0);
            if (codecMeta instanceof VideoCodecMeta) {
                VideoCodecMeta meta = (VideoCodecMeta) codecMeta;
                Rational pasp = meta.getPasp();
                if (pasp == null)
                    sd = dd = meta.getSize();
                else {
                    sd = meta.getSize();
                    dd = new Size(pasp.multiplyS(sd.getWidth()), sd.getHeight());
                }
            }
            TrackHeaderBox tkhd = new TrackHeaderBox(trackId + 1, movieDur, dd.getWidth(), dd.getHeight(),
                    new Date().getTime(), new Date().getTime(), 1.0f, (short) 0, 0, new int[] { 0x10000, 0, 0, 0,
                            0x10000, 0, 0, 0, 0x40000000 });
            tkhd.setFlags(0xf);
            trak.add(tkhd);

            MediaBox media = new MediaBox();
            trak.add(media);
            media.add(new MediaHeaderBox(trackTimescale, totalDur, 0, new Date().getTime(), new Date().getTime(), 0));

            TrackType tt = (codecMeta instanceof AudioCodecMeta) ? TrackType.SOUND : TrackType.VIDEO;
            if (tt == TrackType.VIDEO) {
                NodeBox tapt = new NodeBox(new Header("tapt"));
                tapt.add(new ClearApertureBox(dd.getWidth(), dd.getHeight()));
                tapt.add(new ProductionApertureBox(dd.getWidth(), dd.getHeight()));
                tapt.add(new EncodedPixelBox(sd.getWidth(), sd.getHeight()));
                trak.add(tapt);
            }

            HandlerBox hdlr = new HandlerBox("mhlr", tt.getHandler(), "appl", 0, 0);
            media.add(hdlr);

            MediaInfoBox minf = new MediaInfoBox();
            media.add(minf);
            mediaHeader(minf, tt);
            minf.add(new HandlerBox("dhlr", "url ", "appl", 0, 0));
            addDref(minf);
            NodeBox stbl = new NodeBox(new Header("stbl"));
            minf.add(stbl);

            stbl.add(new SampleDescriptionBox(new SampleEntry[] { toSampleEntry(codecMeta) }));
            if (pcm) {
                populateStblPCM(stbl, chunks, trackId, codecMeta);
            } else {
                populateStblGeneric(stbl, chunks, trackId, codecMeta, trackTimescale);
            }

            addEdits(trak, track, defaultTimescale, trackTimescale);

            movie.add(trak);
        }

        brand.getFileTypeBox().write(buf);
        movie.write(buf);
        new Header("mdat", dataSize).write(buf);
        buf.flip();

        return buf;
    }

    private static SampleEntry toSampleEntry(CodecMeta se) {

        Rational pasp = null;
        SampleEntry vse;
        if ("avc1".equals(se.getFourcc())) {
            AvcCBox avcC = H264Utils.parseAVCC(se.getCodecPrivate().duplicate());
            vse = H264Utils.createMOVSampleEntry(avcC);
            pasp = ((VideoCodecMeta) se).getPasp();
        } else if (se instanceof VideoCodecMeta) {
            VideoCodecMeta ss = (VideoCodecMeta) se;
            pasp = ss.getPasp();
            vse = MP4Muxer.videoSampleEntry(se.getFourcc(), ss.getSize(), "JCodec");
        } else {
            AudioCodecMeta ss = (AudioCodecMeta) se;

            if (ss.isPCM()) {
                vse = MP4Muxer.audioSampleEntry(se.getFourcc(), 0, ss.getSampleSize(), ss.getChannelCount(),
                        ss.getSampleRate(), ss.getEndian());
            } else {
                vse = MP4Muxer.compressedAudioSampleEntry(se.getFourcc(), 0, ss.getSampleSize(), ss.getChannelCount(),
                        ss.getSampleRate(), ss.getSamplesPerPacket(), ss.getBytesPerPacket(), ss.getBytesPerFrame());
            }

            ChannelBox chan = new ChannelBox();
            ChannelUtils.setLabels(ss.getChannelLabels(), chan);
            vse.add(chan);
        }

        if (pasp != null)
            vse.add(new PixelAspectExt(pasp));
        return vse;
    }

    private static int chooseTimescale(PacketChunk[] chunks, int trackId) {
        for (int ch = 0; ch < chunks.length; ch++) {
            if (chunks[ch].getTrackNo() == trackId) {
                double dur = chunks[ch].getPacket().getDuration(), min = Double.MAX_VALUE;
                int minTs = -1;
                for (int ts = 0; ts < timescales.length; ts++) {
                    double dd = timescales[ts] * dur;
                    double diff = dd - (int) dd;
                    if (diff < min) {
                        minTs = ts;
                        min = diff;
                    }
                }
                return timescales[minTs];
            }
        }<commit_msg>made data reference index in audio sample entries consistent with video sample entries<commit_after>package org.jcodec.movtool.streaming;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.LongArrayList;
import org.jcodec.common.model.Rational;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.Brand;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.boxes.AudioSampleEntry;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.ChannelBox;
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ClearApertureBox;
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox;
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox.Entry;
import org.jcodec.containers.mp4.boxes.DataInfoBox;
import org.jcodec.containers.mp4.boxes.DataRefBox;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.EncodedPixelBox;
import org.jcodec.containers.mp4.boxes.GenericMediaInfoBox;
import org.jcodec.containers.mp4.boxes.HandlerBox;
import org.jcodec.containers.mp4.boxes.Header;
import org.jcodec.containers.mp4.boxes.LeafBox;
import org.jcodec.containers.mp4.boxes.MediaBox;
import org.jcodec.containers.mp4.boxes.MediaHeaderBox;
import org.jcodec.containers.mp4.boxes.MediaInfoBox;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.MovieHeaderBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.PixelAspectExt;
import org.jcodec.containers.mp4.boxes.ProductionApertureBox;
import org.jcodec.containers.mp4.boxes.SampleDescriptionBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
import org.jcodec.containers.mp4.boxes.SoundMediaHeaderBox;
import org.jcodec.containers.mp4.boxes.SyncSamplesBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TimecodeMediaInfoBox;
import org.jcodec.containers.mp4.boxes.TrackHeaderBox;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.boxes.VideoMediaHeaderBox;
import org.jcodec.containers.mp4.boxes.channel.ChannelUtils;
import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.movtool.streaming.VirtualMP4Movie.PacketChunk;
import org.jcodec.movtool.streaming.VirtualTrack.VirtualEdit;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 * 
 * Contains methods to mux virtual movie tracks into real real MP4 movie header
 * 
 * @author The JCodec project
 * 
 */
public class MovieHelper {

    private static final int MEBABYTE = 1024 * 1024;

    private static int timescales[] = { 10000, 12000, 15000, 24000, 25000, 30000, 50000, 41000, 48000, 96000 };

    public static ByteBuffer produceHeader(PacketChunk[] chunks, VirtualTrack[] tracks, long dataSize, Brand brand)
            throws IOException {
        int defaultTimescale = 1000;

        ByteBuffer buf = ByteBuffer.allocate(6 * MEBABYTE);
        MovieBox movie = new MovieBox();

        double[] trackDurations = calcTrackDurations(chunks, tracks);
        long movieDur = calcMovieDuration(tracks, defaultTimescale, trackDurations);
        movie.add(movieHeader(movie, tracks.length, movieDur, defaultTimescale));

        for (int trackId = 0; trackId < tracks.length; trackId++) {

            // TODO: optimal timescale selection
            VirtualTrack track = tracks[trackId];
            CodecMeta codecMeta = track.getCodecMeta();

            boolean pcm = (codecMeta instanceof AudioCodecMeta) && ((AudioCodecMeta) codecMeta).isPCM();

            int trackTimescale = track.getPreferredTimescale();
            if (trackTimescale <= 0) {
                if (pcm)
                    trackTimescale = getPCMTs((AudioCodecMeta) codecMeta, chunks, trackId);
                else
                    trackTimescale = chooseTimescale(chunks, trackId);
            } else if (trackTimescale < 100) {
                trackTimescale *= 1000;
            } else if (trackTimescale < 1000) {
                trackTimescale *= 100;
            } else if (trackTimescale < 10000) {
                trackTimescale *= 10;
            }

            long totalDur = (long) (trackTimescale * trackDurations[trackId]);

            TrakBox trak = new TrakBox();

            Size dd = new Size(0, 0), sd = new Size(0, 0);
            if (codecMeta instanceof VideoCodecMeta) {
                VideoCodecMeta meta = (VideoCodecMeta) codecMeta;
                Rational pasp = meta.getPasp();
                if (pasp == null)
                    sd = dd = meta.getSize();
                else {
                    sd = meta.getSize();
                    dd = new Size(pasp.multiplyS(sd.getWidth()), sd.getHeight());
                }
            }
            TrackHeaderBox tkhd = new TrackHeaderBox(trackId + 1, movieDur, dd.getWidth(), dd.getHeight(),
                    new Date().getTime(), new Date().getTime(), 1.0f, (short) 0, 0, new int[] { 0x10000, 0, 0, 0,
                            0x10000, 0, 0, 0, 0x40000000 });
            tkhd.setFlags(0xf);
            trak.add(tkhd);

            MediaBox media = new MediaBox();
            trak.add(media);
            media.add(new MediaHeaderBox(trackTimescale, totalDur, 0, new Date().getTime(), new Date().getTime(), 0));

            TrackType tt = (codecMeta instanceof AudioCodecMeta) ? TrackType.SOUND : TrackType.VIDEO;
            if (tt == TrackType.VIDEO) {
                NodeBox tapt = new NodeBox(new Header("tapt"));
                tapt.add(new ClearApertureBox(dd.getWidth(), dd.getHeight()));
                tapt.add(new ProductionApertureBox(dd.getWidth(), dd.getHeight()));
                tapt.add(new EncodedPixelBox(sd.getWidth(), sd.getHeight()));
                trak.add(tapt);
            }

            HandlerBox hdlr = new HandlerBox("mhlr", tt.getHandler(), "appl", 0, 0);
            media.add(hdlr);

            MediaInfoBox minf = new MediaInfoBox();
            media.add(minf);
            mediaHeader(minf, tt);
            minf.add(new HandlerBox("dhlr", "url ", "appl", 0, 0));
            addDref(minf);
            NodeBox stbl = new NodeBox(new Header("stbl"));
            minf.add(stbl);

            stbl.add(new SampleDescriptionBox(new SampleEntry[] { toSampleEntry(codecMeta) }));
            if (pcm) {
                populateStblPCM(stbl, chunks, trackId, codecMeta);
            } else {
                populateStblGeneric(stbl, chunks, trackId, codecMeta, trackTimescale);
            }

            addEdits(trak, track, defaultTimescale, trackTimescale);

            movie.add(trak);
        }

        brand.getFileTypeBox().write(buf);
        movie.write(buf);
        new Header("mdat", dataSize).write(buf);
        buf.flip();

        return buf;
    }

    private static SampleEntry toSampleEntry(CodecMeta se) {

        Rational pasp = null;
        SampleEntry vse;
        if ("avc1".equals(se.getFourcc())) {
            AvcCBox avcC = H264Utils.parseAVCC(se.getCodecPrivate().duplicate());
            vse = H264Utils.createMOVSampleEntry(avcC);
            pasp = ((VideoCodecMeta) se).getPasp();
        } else if (se instanceof VideoCodecMeta) {
            VideoCodecMeta ss = (VideoCodecMeta) se;
            pasp = ss.getPasp();
            vse = MP4Muxer.videoSampleEntry(se.getFourcc(), ss.getSize(), "JCodec");
        } else {
            AudioCodecMeta ss = (AudioCodecMeta) se;

            if (ss.isPCM()) {
                vse = MP4Muxer.audioSampleEntry(se.getFourcc(), 1, ss.getSampleSize(), ss.getChannelCount(),
                        ss.getSampleRate(), ss.getEndian());
            } else {
                vse = MP4Muxer.compressedAudioSampleEntry(se.getFourcc(), 1, ss.getSampleSize(), ss.getChannelCount(),
                        ss.getSampleRate(), ss.getSamplesPerPacket(), ss.getBytesPerPacket(), ss.getBytesPerFrame());
            }

            ChannelBox chan = new ChannelBox();
            ChannelUtils.setLabels(ss.getChannelLabels(), chan);
            vse.add(chan);
        }

        if (pasp != null)
            vse.add(new PixelAspectExt(pasp));
        return vse;
    }

    private static int chooseTimescale(PacketChunk[] chunks, int trackId) {
        for (int ch = 0; ch < chunks.length; ch++) {
            if (chunks[ch].getTrackNo() == trackId) {
                double dur = chunks[ch].getPacket().getDuration(), min = Double.MAX_VALUE;
                int minTs = -1;
                for (int ts = 0; ts < timescales.length; ts++) {
                    double dd = timescales[ts] * dur;
                    double diff = dd - (int) dd;
                    if (diff < min) {
                        minTs = ts;
                        min = diff;
                    }
                }
                return timescales[minTs];
            }
        }
{'fraction_non_alphanumeric': 0.11353665627147312, 'fraction_numerical': 0.021248480363655585, 'mean_word_length': 2.5318275154004106, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5449506', 'n_tokens_mistral': 6169, 'n_tokens_neox': 5681, 'n_words': 1120}