/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
* Copyright (c) 2011-2014, FrostWire(R). All rights reserved.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package com.frostwire.util;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import com.coremedia.iso.BoxParser;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.PropertyBoxParserImpl;
import com.coremedia.iso.boxes.Box;
import com.coremedia.iso.boxes.ContainerBox;
import com.coremedia.iso.boxes.FileTypeBox;
import com.coremedia.iso.boxes.HandlerBox;
import com.coremedia.iso.boxes.MetaBox;
import com.coremedia.iso.boxes.MovieBox;
import com.coremedia.iso.boxes.TrackBox;
import com.coremedia.iso.boxes.UserDataBox;
import com.coremedia.iso.boxes.apple.AppleAlbumArtistBox;
import com.coremedia.iso.boxes.apple.AppleAlbumBox;
import com.coremedia.iso.boxes.apple.AppleArtistBox;
import com.coremedia.iso.boxes.apple.AppleCoverBox;
import com.coremedia.iso.boxes.apple.AppleItemListBox;
import com.coremedia.iso.boxes.apple.AppleMediaTypeBox;
import com.coremedia.iso.boxes.apple.AppleTrackTitleBox;
import com.googlecode.mp4parser.AbstractBox;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Mp4TrackImpl;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
/**
*
* @author gubatron
* @author aldenml
*
*/
public final class MP4Muxer {
public void mux(String video, String audio, String output, final MP4Metadata mt) throws IOException {
FileInputStream videoIn = new FileInputStream(video);
FileInputStream audioIn = new FileInputStream(audio);
try {
FileChannel videoChannel = videoIn.getChannel();
Movie videoMovie = buildMovie(videoChannel);
FileChannel audioChannel = audioIn.getChannel();
Movie audioMovie = buildMovie(audioChannel);
Movie outMovie = new Movie();
for (Track trk : videoMovie.getTracks()) {
outMovie.addTrack(trk);
}
for (Track trk : audioMovie.getTracks()) {
outMovie.addTrack(trk);
}
IsoFile out = new DefaultMp4Builder() {
@Override
protected FileTypeBox createFileTypeBox(Movie movie) {
List<String> minorBrands = new LinkedList<String>();
minorBrands.add("iso6");
minorBrands.add("avc1");
minorBrands.add("mp41");
minorBrands.add("\0\0\0\0");
return new FileTypeBox("MP4 ", 0, minorBrands);
}
@Override
protected MovieBox createMovieBox(Movie movie, Map<Track, int[]> chunks) {
MovieBox moov = super.createMovieBox(movie, chunks);
moov.getMovieHeaderBox().setVersion(0);
return moov;
}
@Override
protected TrackBox createTrackBox(Track track, Movie movie, Map<Track, int[]> chunks) {
TrackBox trak = super.createTrackBox(track, movie, chunks);
trak.getTrackHeaderBox().setVersion(0);
trak.getTrackHeaderBox().setVolume(1.0f);
return trak;
}
@Override
protected Box createUdta(Movie movie) {
return mt != null ? addUserDataBox(mt) : null;
}
}.build(outMovie);
FileOutputStream fos = new FileOutputStream(output);
try {
out.getBox(fos.getChannel());
} finally {
IOUtils.closeQuietly(fos);
}
} finally {
IOUtils.closeQuietly(videoIn);
IOUtils.closeQuietly(audioIn);
}
}
public void demuxAudio(String video, String output, final MP4Metadata mt) throws IOException {
FileInputStream videoIn = new FileInputStream(video);
try {
FileChannel videoChannel = videoIn.getChannel();
Movie videoMovie = buildMovie(videoChannel);
Track audioTrack = null;
for (Track trk : videoMovie.getTracks()) {
if (trk.getHandler().equals("soun")) {
audioTrack = trk;
break;
}
}
if (audioTrack == null) {
IOUtils.closeQuietly(videoIn);
return;
}
Movie outMovie = new Movie();
outMovie.addTrack(audioTrack);
IsoFile out = new DefaultMp4Builder() {
@Override
protected FileTypeBox createFileTypeBox(Movie movie) {
List<String> minorBrands = new LinkedList<String>();
minorBrands.add("M4A ");
minorBrands.add("mp42");
minorBrands.add("isom");
minorBrands.add("\0\0\0\0");
return new FileTypeBox("M4A ", 0, minorBrands);
}
@Override
protected MovieBox createMovieBox(Movie movie, Map<Track, int[]> chunks) {
MovieBox moov = super.createMovieBox(movie, chunks);
moov.getMovieHeaderBox().setVersion(0);
return moov;
}
@Override
protected TrackBox createTrackBox(Track track, Movie movie, Map<Track, int[]> chunks) {
TrackBox trak = super.createTrackBox(track, movie, chunks);
trak.getTrackHeaderBox().setVersion(0);
trak.getTrackHeaderBox().setVolume(1.0f);
return trak;
}
@Override
protected Box createUdta(Movie movie) {
return mt != null ? addUserDataBox(mt) : null;
}
}.build(outMovie);
FileOutputStream fos = new FileOutputStream(output);
try {
out.getBox(fos.getChannel());
} finally {
IOUtils.closeQuietly(fos);
}
} finally {
IOUtils.closeQuietly(videoIn);
}
}
private static Movie buildMovie(ReadableByteChannel channel) throws IOException {
BoxParser parser = new PropertyBoxParserImpl() {
@Override
public Box parseBox(ReadableByteChannel byteChannel, ContainerBox parent) throws IOException {
Box box = super.parseBox(byteChannel, parent);
if (box instanceof AbstractBox) {
((AbstractBox) box).parseDetails();
}
return box;
}
};
@SuppressWarnings("resource")
IsoFile isoFile = new IsoFile(channel, parser);
Movie m = new Movie();
List<TrackBox> trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);
for (TrackBox trackBox : trackBoxes) {
m.addTrack(new Mp4TrackImpl(trackBox));
}
return m;
}
private static UserDataBox addUserDataBox(MP4Metadata mt) {
//"/moov/udta/meta/ilst/covr/data"
UserDataBox udta = new UserDataBox();
MetaBox meta = new MetaBox();
udta.addBox(meta);
HandlerBox hdlr = new HandlerBox();
hdlr.setHandlerType("mdir");
meta.addBox(hdlr);
AppleItemListBox ilst = new AppleItemListBox();
meta.addBox(ilst);
if (mt.title != null) {
AppleTrackTitleBox cnam = new AppleTrackTitleBox();
cnam.setValue(mt.title);
ilst.addBox(cnam);
}
if (mt.author != null) {
AppleArtistBox cART = new AppleArtistBox();
cART.setValue(mt.author);
ilst.addBox(cART);
}
AppleAlbumArtistBox aART = new AppleAlbumArtistBox();
aART.setValue(mt.title + " " + mt.author);
ilst.addBox(aART);
if (mt.source != null) {
AppleAlbumBox calb = new AppleAlbumBox();
calb.setValue(mt.title + " " + mt.author + " via " + mt.source);
ilst.addBox(calb);
}
AppleMediaTypeBox stik = new AppleMediaTypeBox();
stik.setValue("1");
ilst.addBox(stik);
if (mt.jpg != null) {
AppleCoverBox covr = new AppleCoverBox();
covr.setJpg(mt.jpg);
ilst.addBox(covr);
}
return udta;
}
public static final class MP4Metadata {
public MP4Metadata(String title, String author, String source, byte[] jpg) {
this.title = title;
this.author = author;
this.source = source;
this.jpg = jpg;
}
public final String title;
public final String author;
public final String source;
public final byte[] jpg;
}
}