Skip to content

Commit

Permalink
Merge pull request #364 from Netflix/feature/detect-sample-rate-dur-i…
Browse files Browse the repository at this point in the history
…mpassembler

Detect sample rate & duration in IMPAssembler, parameterize unit test…
  • Loading branch information
tcase-netflix committed Mar 8, 2024
2 parents 0315d5f + cf986c3 commit d5e17f6
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
* A simple application to exercise the core logic of Photon for reading and validating IMF Track files.
*/
@ThreadSafe
final class IMFTrackFileReader
public final class IMFTrackFileReader
{
private final File workingDirectory;
private final ResourceByteRangeProvider resourceByteRangeProvider;
Expand All @@ -89,7 +89,7 @@ final class IMFTrackFileReader
* @param workingDirectory the working directory
* @param resourceByteRangeProvider the MXF file represented as a {@link com.netflix.imflibrary.utils.ResourceByteRangeProvider}
*/
IMFTrackFileReader(File workingDirectory, ResourceByteRangeProvider resourceByteRangeProvider)
public IMFTrackFileReader(File workingDirectory, ResourceByteRangeProvider resourceByteRangeProvider)
{
this.workingDirectory = workingDirectory;
this.resourceByteRangeProvider = resourceByteRangeProvider;
Expand Down Expand Up @@ -602,7 +602,7 @@ BigInteger getEssenceEditRate(@Nonnull IMFErrorLogger imfErrorLogger) throws IOE
* @param imfErrorLogger an error logger for recording any errors - cannot be null
* @return editRate of the essence as a List of Long Integers
*/
List<Long> getEssenceEditRateAsList(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
public List<Long> getEssenceEditRateAsList(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
if(!(this.getHeaderPartition(imfErrorLogger).getEssenceDescriptors().size() > 0)){
throw new MXFException(String.format("No EssenceDescriptors were found in the MXF essence"));
}
Expand All @@ -615,7 +615,7 @@ List<Long> getEssenceEditRateAsList(@Nonnull IMFErrorLogger imfErrorLogger) thro
* @param imfErrorLogger an error logger for recording any errors - cannot be null
* @return essenceDuration
*/
BigInteger getEssenceDuration(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
public BigInteger getEssenceDuration(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
return this.getHeaderPartition(imfErrorLogger).getEssenceDuration();
}

Expand Down
66 changes: 56 additions & 10 deletions src/main/java/com/netflix/imflibrary/writerTools/IMPAssembler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.netflix.imflibrary.IMFErrorLogger;
import com.netflix.imflibrary.IMFErrorLoggerImpl;
import com.netflix.imflibrary.RESTfulInterfaces.PayloadRecord;
import com.netflix.imflibrary.app.IMFTrackFileReader;
import com.netflix.imflibrary.app.IMPFixer;
import com.netflix.imflibrary.st0429_8.PackingList;
import com.netflix.imflibrary.st0429_9.AssetMap;
Expand Down Expand Up @@ -30,6 +31,8 @@
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
Expand All @@ -39,7 +42,6 @@ public class IMPAssembler {
private static final Logger logger = LoggerFactory.getLogger(IMPAssembler.class);



/**
* Generate the CPL, PKL, and AssetMap XML files given a simple timeline of track entries
* Code adapted from IMPFixer
Expand All @@ -52,6 +54,8 @@ public AssembledIMPResult assembleIMFFromFiles(SimpleTimeline simpleTimeline, Fi
IMFErrorLogger imfErrors = new IMFErrorLoggerImpl();
List<Composition.VirtualTrack> virtualTracks = new ArrayList<>();
Map<UUID, UUID> trackFileIdToResourceMap = new HashMap<>();
Map<UUID, List<Long>> sampleRateMap = new HashMap<>();
Map<UUID, BigInteger> sampleCountMap = new HashMap<>();


for (Track track : simpleTimeline.getTracks()) {
Expand Down Expand Up @@ -86,16 +90,58 @@ public AssembledIMPResult assembleIMFFromFiles(SimpleTimeline simpleTimeline, Fi
Files.copy(trackEntry.getFile().toPath(), outputTrackFile.toPath(), REPLACE_EXISTING);
}

IMFTrackFileReader imfTrackFileReader = new IMFTrackFileReader(outputDirectory, resourceByteRangeProvider);

// get sample rate or use cached value
List<Long> sampleRate = null;
if (trackEntry.getSampleRate() != null) {
// if user provided sample rate, use it
sampleRate = Arrays.asList(trackEntry.getSampleRate().getNumerator(), trackEntry.getSampleRate().getDenominator());
logger.info("Using sample rate from user: {}/{}", sampleRate.get(0), sampleRate.get(1));
} else if (!sampleRateMap.containsKey(trackFileId)) {
// sample rate has not already been found, find it
sampleRate = imfTrackFileReader.getEssenceEditRateAsList(imfErrors);
sampleRateMap.put(trackFileId, sampleRate);
logger.info("Found sample rate of: {}/{}", sampleRate.get(0), sampleRate.get(1));
} else {
sampleRate = sampleRateMap.get(trackFileId);
logger.info("Using cached sample rate of: {}/{}", sampleRate.get(0), sampleRate.get(1));
}


// get sample count or use cached value
BigInteger sampleCount = null;
if (trackEntry.getIntrinsicDuration() != null) {
// use sample count provided by user
sampleCount = trackEntry.getIntrinsicDuration();
logger.info("Intrinsic duration from user: {}", sampleCount);
} else if (!sampleCountMap.containsKey(trackFileId)) {
// compute sample count
sampleCount = imfTrackFileReader.getEssenceDuration(imfErrors);
sampleCountMap.put(trackFileId, sampleCount);
logger.info("Found essence duration of: {}", sampleCount);
} else {
// use cached sample count
sampleCount = sampleCountMap.get(trackFileId);
logger.info("Using cached intrinsic duration of: {}", sampleCount);
}

// delete temporary file left over from FileByteRangeProvider or ByteArrayByteRangeProvider
Path tempFilePath = Paths.get(outputDirectory.getAbsolutePath(), "range");
logger.info("Deleting temporary file if it exists: {}", tempFilePath);
Files.deleteIfExists(tempFilePath);


// add to resources
logger.info("Adding file to resources: {}..", trackEntry.getFile().getName());
resources.add(
new IMFTrackFileResourceType(
UUIDHelper.fromUUID(IMFUUIDGenerator.getInstance().generateUUID()),
UUIDHelper.fromUUID(trackFileId),
Arrays.asList(trackEntry.getSampleRate().getNumerator(), trackEntry.getSampleRate().getDenominator()), // defaults to 1/1
trackEntry.getIntrinsicDuration(),
sampleRate, // defaults to 1/1
sampleCount,
trackEntry.getEntryPoint(), // defaults to 0 if null
trackEntry.getDuration(), // defaults to intrinsic duration if null
trackEntry.getDuration() == null ? sampleCount : trackEntry.getDuration(), // defaults to intrinsic duration if null
trackEntry.getRepeatCount(), // defaults to 1 if null
UUIDHelper.fromUUID(getOrGenerateSourceEncoding(trackFileIdToResourceMap, trackFileId)), // used as the essence descriptor id
hash,
Expand Down Expand Up @@ -336,13 +382,13 @@ public static class TrackEntry {
/**
* Constructor for a track entry to be used to construct a simple timeline
* @param file - the MXF file
* @param sampleRate - the sample rate
* @param intrinsicDuration - the intrinsic duration
* @param entryPoint - the entry point (if null, defaults to 0)
* @param duration - the duration (if null, defaults to intrinsic duration)
* @param repeatCount - the repeat count (if null, defaults to 1)
* @param sampleRate - the sample rate, optional, introspected if null
* @param intrinsicDuration - the intrinsic duration, optional, introspected if null
* @param entryPoint - the entry point, if null, defaults to 0
* @param duration - the duration, if null, defaults to intrinsic duration
* @param repeatCount - the repeat count, if null, defaults to 1
*/
public TrackEntry(@Nonnull File file, @Nonnull Composition.EditRate sampleRate, @Nonnull BigInteger intrinsicDuration, @Nullable BigInteger entryPoint, @Nullable BigInteger duration, @Nullable BigInteger repeatCount) {
public TrackEntry(@Nonnull File file, @Nullable Composition.EditRate sampleRate, @Nullable BigInteger intrinsicDuration, @Nullable BigInteger entryPoint, @Nullable BigInteger duration, @Nullable BigInteger repeatCount) {
this.file = file;
this.sampleRate = sampleRate;
this.intrinsicDuration = intrinsicDuration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.netflix.imflibrary.st2067_2.Composition;
import com.netflix.imflibrary.utils.ErrorLogger;
import org.slf4j.Logger;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.xml.sax.SAXException;
import testUtils.TestHelper;
Expand All @@ -27,48 +28,75 @@ public class IMPAssemblerTest {

private static final Logger logger = org.slf4j.LoggerFactory.getLogger(IMPAssemblerTest.class);

@Test
public void testAssembleIMFFromFiles() throws IOException, JAXBException, ParserConfigurationException, URISyntaxException, SAXException {

IMPAssembler.TrackEntry videoFile1 = new IMPAssembler.TrackEntry(
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf"),
new Composition.EditRate(60000L, 1001L),
BigInteger.valueOf(10),
BigInteger.valueOf(0),
BigInteger.valueOf(10),
BigInteger.valueOf(1)
);

@DataProvider(name = "trackEntries")
private Object[][] trackEntries() {
return new Object[][] {
{
// video & audio have all values provided by the user
new IMPAssembler.TrackEntry(
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf"),
new Composition.EditRate(60000L, 1001L),
BigInteger.valueOf(10),
BigInteger.valueOf(0),
BigInteger.valueOf(10),
BigInteger.valueOf(1)
),
new IMPAssembler.TrackEntry(
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_ENG-51_00.mxf"),
new Composition.EditRate(48000L, 1L),
BigInteger.valueOf(8008),
BigInteger.valueOf(0),
BigInteger.valueOf(8008),
BigInteger.valueOf(1)
)

},
{
// video & audio values are left null for Photon to figure out
new IMPAssembler.TrackEntry(
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf"),
null,
null,
null,
null,
null
),
new IMPAssembler.TrackEntry(
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_ENG-51_00.mxf"),
null,
null,
null,
null,
null
)
}


};
}

@Test(dataProvider = "trackEntries")
public void testAssembleIMFFromFiles(IMPAssembler.TrackEntry videoTrackEntry, IMPAssembler.TrackEntry audioTrackEntry) throws IOException, JAXBException, ParserConfigurationException, URISyntaxException, SAXException {

IMPAssembler.Track videoTrack = new IMPAssembler.Track();
videoTrack.getTrackEntries().add(videoFile1);
videoTrack.getTrackEntries().add(videoFile1);
videoTrack.getTrackEntries().add(videoTrackEntry);
videoTrack.getTrackEntries().add(videoTrackEntry);
videoTrack.setSequenceTypeEnum(Composition.SequenceTypeEnum.MainImageSequence);
List<IMPAssembler.Track> trackList = new ArrayList<>();
trackList.add(videoTrack);



IMPAssembler.TrackEntry audioFile1 = new IMPAssembler.TrackEntry(
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_ENG-51_00.mxf"),
new Composition.EditRate(48000L, 1L),
BigInteger.valueOf(8008),
BigInteger.valueOf(0),
BigInteger.valueOf(8008),
BigInteger.valueOf(1)
);

IMPAssembler.Track audioTrack = new IMPAssembler.Track();
audioTrack.getTrackEntries().add(audioFile1);
audioTrack.getTrackEntries().add(audioFile1);
audioTrack.getTrackEntries().add(audioTrackEntry);
audioTrack.getTrackEntries().add(audioTrackEntry);
audioTrack.setSequenceTypeEnum(Composition.SequenceTypeEnum.MainAudioSequence);
trackList.add(audioTrack);

IMPAssembler.SimpleTimeline simpleTimeline = new IMPAssembler.SimpleTimeline(trackList, new Composition.EditRate(Arrays.asList(60000L, 1001L)));



Path outputDirPath = Files.createTempDirectory(Paths.get(System.getProperty("java.io.tmpdir")), "IMPAssemblerTest");
File outputDir = outputDirPath.toFile();
// File outputDirectory = new File("outputDirectory");
IMPAssembler impAssembler = new IMPAssembler();
IMPAssembler.AssembledIMPResult result = impAssembler.assembleIMFFromFiles(simpleTimeline, outputDir, true);

Expand All @@ -95,7 +123,5 @@ public void testAssembleIMFFromFiles() throws IOException, JAXBException, Parser
for (File outputTrackFile : result.getTrackFiles()) {
assert outputTrackFile.exists();
}


}
}

0 comments on commit d5e17f6

Please sign in to comment.