Skip to content

Commit d5e17f6

Browse files
Merge pull request #364 from Netflix/feature/detect-sample-rate-dur-impassembler
Detect sample rate & duration in IMPAssembler, parameterize unit test…
2 parents 0315d5f + cf986c3 commit d5e17f6

File tree

3 files changed

+116
-44
lines changed

3 files changed

+116
-44
lines changed

src/main/java/com/netflix/imflibrary/app/IMFTrackFileReader.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
* A simple application to exercise the core logic of Photon for reading and validating IMF Track files.
7272
*/
7373
@ThreadSafe
74-
final class IMFTrackFileReader
74+
public final class IMFTrackFileReader
7575
{
7676
private final File workingDirectory;
7777
private final ResourceByteRangeProvider resourceByteRangeProvider;
@@ -89,7 +89,7 @@ final class IMFTrackFileReader
8989
* @param workingDirectory the working directory
9090
* @param resourceByteRangeProvider the MXF file represented as a {@link com.netflix.imflibrary.utils.ResourceByteRangeProvider}
9191
*/
92-
IMFTrackFileReader(File workingDirectory, ResourceByteRangeProvider resourceByteRangeProvider)
92+
public IMFTrackFileReader(File workingDirectory, ResourceByteRangeProvider resourceByteRangeProvider)
9393
{
9494
this.workingDirectory = workingDirectory;
9595
this.resourceByteRangeProvider = resourceByteRangeProvider;
@@ -602,7 +602,7 @@ BigInteger getEssenceEditRate(@Nonnull IMFErrorLogger imfErrorLogger) throws IOE
602602
* @param imfErrorLogger an error logger for recording any errors - cannot be null
603603
* @return editRate of the essence as a List of Long Integers
604604
*/
605-
List<Long> getEssenceEditRateAsList(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
605+
public List<Long> getEssenceEditRateAsList(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
606606
if(!(this.getHeaderPartition(imfErrorLogger).getEssenceDescriptors().size() > 0)){
607607
throw new MXFException(String.format("No EssenceDescriptors were found in the MXF essence"));
608608
}
@@ -615,7 +615,7 @@ List<Long> getEssenceEditRateAsList(@Nonnull IMFErrorLogger imfErrorLogger) thro
615615
* @param imfErrorLogger an error logger for recording any errors - cannot be null
616616
* @return essenceDuration
617617
*/
618-
BigInteger getEssenceDuration(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
618+
public BigInteger getEssenceDuration(@Nonnull IMFErrorLogger imfErrorLogger) throws IOException {
619619
return this.getHeaderPartition(imfErrorLogger).getEssenceDuration();
620620
}
621621

src/main/java/com/netflix/imflibrary/writerTools/IMPAssembler.java

+56-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.netflix.imflibrary.IMFErrorLogger;
44
import com.netflix.imflibrary.IMFErrorLoggerImpl;
55
import com.netflix.imflibrary.RESTfulInterfaces.PayloadRecord;
6+
import com.netflix.imflibrary.app.IMFTrackFileReader;
67
import com.netflix.imflibrary.app.IMPFixer;
78
import com.netflix.imflibrary.st0429_8.PackingList;
89
import com.netflix.imflibrary.st0429_9.AssetMap;
@@ -30,6 +31,8 @@
3031
import java.math.BigInteger;
3132
import java.net.URISyntaxException;
3233
import java.nio.file.Files;
34+
import java.nio.file.Path;
35+
import java.nio.file.Paths;
3336
import java.util.*;
3437

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

4144

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

5660

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

93+
IMFTrackFileReader imfTrackFileReader = new IMFTrackFileReader(outputDirectory, resourceByteRangeProvider);
94+
95+
// get sample rate or use cached value
96+
List<Long> sampleRate = null;
97+
if (trackEntry.getSampleRate() != null) {
98+
// if user provided sample rate, use it
99+
sampleRate = Arrays.asList(trackEntry.getSampleRate().getNumerator(), trackEntry.getSampleRate().getDenominator());
100+
logger.info("Using sample rate from user: {}/{}", sampleRate.get(0), sampleRate.get(1));
101+
} else if (!sampleRateMap.containsKey(trackFileId)) {
102+
// sample rate has not already been found, find it
103+
sampleRate = imfTrackFileReader.getEssenceEditRateAsList(imfErrors);
104+
sampleRateMap.put(trackFileId, sampleRate);
105+
logger.info("Found sample rate of: {}/{}", sampleRate.get(0), sampleRate.get(1));
106+
} else {
107+
sampleRate = sampleRateMap.get(trackFileId);
108+
logger.info("Using cached sample rate of: {}/{}", sampleRate.get(0), sampleRate.get(1));
109+
}
110+
111+
112+
// get sample count or use cached value
113+
BigInteger sampleCount = null;
114+
if (trackEntry.getIntrinsicDuration() != null) {
115+
// use sample count provided by user
116+
sampleCount = trackEntry.getIntrinsicDuration();
117+
logger.info("Intrinsic duration from user: {}", sampleCount);
118+
} else if (!sampleCountMap.containsKey(trackFileId)) {
119+
// compute sample count
120+
sampleCount = imfTrackFileReader.getEssenceDuration(imfErrors);
121+
sampleCountMap.put(trackFileId, sampleCount);
122+
logger.info("Found essence duration of: {}", sampleCount);
123+
} else {
124+
// use cached sample count
125+
sampleCount = sampleCountMap.get(trackFileId);
126+
logger.info("Using cached intrinsic duration of: {}", sampleCount);
127+
}
128+
129+
// delete temporary file left over from FileByteRangeProvider or ByteArrayByteRangeProvider
130+
Path tempFilePath = Paths.get(outputDirectory.getAbsolutePath(), "range");
131+
logger.info("Deleting temporary file if it exists: {}", tempFilePath);
132+
Files.deleteIfExists(tempFilePath);
133+
134+
89135
// add to resources
90136
logger.info("Adding file to resources: {}..", trackEntry.getFile().getName());
91137
resources.add(
92138
new IMFTrackFileResourceType(
93139
UUIDHelper.fromUUID(IMFUUIDGenerator.getInstance().generateUUID()),
94140
UUIDHelper.fromUUID(trackFileId),
95-
Arrays.asList(trackEntry.getSampleRate().getNumerator(), trackEntry.getSampleRate().getDenominator()), // defaults to 1/1
96-
trackEntry.getIntrinsicDuration(),
141+
sampleRate, // defaults to 1/1
142+
sampleCount,
97143
trackEntry.getEntryPoint(), // defaults to 0 if null
98-
trackEntry.getDuration(), // defaults to intrinsic duration if null
144+
trackEntry.getDuration() == null ? sampleCount : trackEntry.getDuration(), // defaults to intrinsic duration if null
99145
trackEntry.getRepeatCount(), // defaults to 1 if null
100146
UUIDHelper.fromUUID(getOrGenerateSourceEncoding(trackFileIdToResourceMap, trackFileId)), // used as the essence descriptor id
101147
hash,
@@ -336,13 +382,13 @@ public static class TrackEntry {
336382
/**
337383
* Constructor for a track entry to be used to construct a simple timeline
338384
* @param file - the MXF file
339-
* @param sampleRate - the sample rate
340-
* @param intrinsicDuration - the intrinsic duration
341-
* @param entryPoint - the entry point (if null, defaults to 0)
342-
* @param duration - the duration (if null, defaults to intrinsic duration)
343-
* @param repeatCount - the repeat count (if null, defaults to 1)
385+
* @param sampleRate - the sample rate, optional, introspected if null
386+
* @param intrinsicDuration - the intrinsic duration, optional, introspected if null
387+
* @param entryPoint - the entry point, if null, defaults to 0
388+
* @param duration - the duration, if null, defaults to intrinsic duration
389+
* @param repeatCount - the repeat count, if null, defaults to 1
344390
*/
345-
public TrackEntry(@Nonnull File file, @Nonnull Composition.EditRate sampleRate, @Nonnull BigInteger intrinsicDuration, @Nullable BigInteger entryPoint, @Nullable BigInteger duration, @Nullable BigInteger repeatCount) {
391+
public TrackEntry(@Nonnull File file, @Nullable Composition.EditRate sampleRate, @Nullable BigInteger intrinsicDuration, @Nullable BigInteger entryPoint, @Nullable BigInteger duration, @Nullable BigInteger repeatCount) {
346392
this.file = file;
347393
this.sampleRate = sampleRate;
348394
this.intrinsicDuration = intrinsicDuration;

src/test/java/com/netflix/imflibrary/writerTools/IMPAssemblerTest.java

+56-30
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.netflix.imflibrary.st2067_2.Composition;
66
import com.netflix.imflibrary.utils.ErrorLogger;
77
import org.slf4j.Logger;
8+
import org.testng.annotations.DataProvider;
89
import org.testng.annotations.Test;
910
import org.xml.sax.SAXException;
1011
import testUtils.TestHelper;
@@ -27,48 +28,75 @@ public class IMPAssemblerTest {
2728

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

30-
@Test
31-
public void testAssembleIMFFromFiles() throws IOException, JAXBException, ParserConfigurationException, URISyntaxException, SAXException {
32-
33-
IMPAssembler.TrackEntry videoFile1 = new IMPAssembler.TrackEntry(
34-
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf"),
35-
new Composition.EditRate(60000L, 1001L),
36-
BigInteger.valueOf(10),
37-
BigInteger.valueOf(0),
38-
BigInteger.valueOf(10),
39-
BigInteger.valueOf(1)
40-
);
31+
32+
@DataProvider(name = "trackEntries")
33+
private Object[][] trackEntries() {
34+
return new Object[][] {
35+
{
36+
// video & audio have all values provided by the user
37+
new IMPAssembler.TrackEntry(
38+
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf"),
39+
new Composition.EditRate(60000L, 1001L),
40+
BigInteger.valueOf(10),
41+
BigInteger.valueOf(0),
42+
BigInteger.valueOf(10),
43+
BigInteger.valueOf(1)
44+
),
45+
new IMPAssembler.TrackEntry(
46+
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_ENG-51_00.mxf"),
47+
new Composition.EditRate(48000L, 1L),
48+
BigInteger.valueOf(8008),
49+
BigInteger.valueOf(0),
50+
BigInteger.valueOf(8008),
51+
BigInteger.valueOf(1)
52+
)
53+
54+
},
55+
{
56+
// video & audio values are left null for Photon to figure out
57+
new IMPAssembler.TrackEntry(
58+
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf"),
59+
null,
60+
null,
61+
null,
62+
null,
63+
null
64+
),
65+
new IMPAssembler.TrackEntry(
66+
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_ENG-51_00.mxf"),
67+
null,
68+
null,
69+
null,
70+
null,
71+
null
72+
)
73+
}
74+
75+
76+
};
77+
}
78+
79+
@Test(dataProvider = "trackEntries")
80+
public void testAssembleIMFFromFiles(IMPAssembler.TrackEntry videoTrackEntry, IMPAssembler.TrackEntry audioTrackEntry) throws IOException, JAXBException, ParserConfigurationException, URISyntaxException, SAXException {
81+
4182
IMPAssembler.Track videoTrack = new IMPAssembler.Track();
42-
videoTrack.getTrackEntries().add(videoFile1);
43-
videoTrack.getTrackEntries().add(videoFile1);
83+
videoTrack.getTrackEntries().add(videoTrackEntry);
84+
videoTrack.getTrackEntries().add(videoTrackEntry);
4485
videoTrack.setSequenceTypeEnum(Composition.SequenceTypeEnum.MainImageSequence);
4586
List<IMPAssembler.Track> trackList = new ArrayList<>();
4687
trackList.add(videoTrack);
4788

4889

49-
50-
IMPAssembler.TrackEntry audioFile1 = new IMPAssembler.TrackEntry(
51-
TestHelper.findResourceByPath("TestIMP/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_ENG-51_00.mxf"),
52-
new Composition.EditRate(48000L, 1L),
53-
BigInteger.valueOf(8008),
54-
BigInteger.valueOf(0),
55-
BigInteger.valueOf(8008),
56-
BigInteger.valueOf(1)
57-
);
58-
5990
IMPAssembler.Track audioTrack = new IMPAssembler.Track();
60-
audioTrack.getTrackEntries().add(audioFile1);
61-
audioTrack.getTrackEntries().add(audioFile1);
91+
audioTrack.getTrackEntries().add(audioTrackEntry);
92+
audioTrack.getTrackEntries().add(audioTrackEntry);
6293
audioTrack.setSequenceTypeEnum(Composition.SequenceTypeEnum.MainAudioSequence);
6394
trackList.add(audioTrack);
6495

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

67-
68-
6998
Path outputDirPath = Files.createTempDirectory(Paths.get(System.getProperty("java.io.tmpdir")), "IMPAssemblerTest");
7099
File outputDir = outputDirPath.toFile();
71-
// File outputDirectory = new File("outputDirectory");
72100
IMPAssembler impAssembler = new IMPAssembler();
73101
IMPAssembler.AssembledIMPResult result = impAssembler.assembleIMFFromFiles(simpleTimeline, outputDir, true);
74102

@@ -95,7 +123,5 @@ public void testAssembleIMFFromFiles() throws IOException, JAXBException, Parser
95123
for (File outputTrackFile : result.getTrackFiles()) {
96124
assert outputTrackFile.exists();
97125
}
98-
99-
100126
}
101127
}

0 commit comments

Comments
 (0)