Skip to content

Commit a796055

Browse files
committed
Fixing chapter writing bug.
1 parent 6c1f81b commit a796055

File tree

3 files changed

+44
-39
lines changed

3 files changed

+44
-39
lines changed

Knuckleball.Tests/Knuckleball.Tests.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@
8484
</ItemGroup>
8585
<ItemGroup>
8686
<Content Include="TestFiles\Chapter.m4v">
87-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
87+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
8888
</Content>
8989
<Content Include="TestFiles\Movie.m4v">
90-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
90+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
9191
</Content>
9292
<Content Include="TestFiles\NewArtwork.png">
9393
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
9494
</Content>
9595
<Content Include="TestFiles\TVEpisode.m4v">
96-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
96+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
9797
</Content>
9898
</ItemGroup>
9999
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Knuckleball/MP4File.cs

+37-26
Original file line numberDiff line numberDiff line change
@@ -982,8 +982,8 @@ private void ReadChapters(IntPtr fileHandle)
982982
this.chapters.Clear();
983983
IntPtr chapterListPointer = IntPtr.Zero;
984984
int chapterCount = 0;
985-
NativeMethods.MP4GetChapters(fileHandle, ref chapterListPointer, ref chapterCount, NativeMethods.MP4ChapterType.Qt);
986-
if (chapterListPointer != IntPtr.Zero)
985+
NativeMethods.MP4ChapterType chapterType = NativeMethods.MP4GetChapters(fileHandle, ref chapterListPointer, ref chapterCount, NativeMethods.MP4ChapterType.Qt);
986+
if (chapterType != NativeMethods.MP4ChapterType.None && chapterCount != 0)
987987
{
988988
IntPtr currentChapterPointer = chapterListPointer;
989989
for (int i = 0; i < chapterCount; i++)
@@ -1001,63 +1001,74 @@ private void ReadChapters(IntPtr fileHandle)
10011001
this.chapters.Add(new Chapter() { Duration = duration, Title = title });
10021002
currentChapterPointer = IntPtr.Add(currentChapterPointer, Marshal.SizeOf(currentChapter));
10031003
}
1004+
}
1005+
else
1006+
{
1007+
int timeScale = NativeMethods.MP4GetTimeScale(fileHandle);
1008+
long duration = NativeMethods.MP4GetDuration(fileHandle);
1009+
this.chapters.Add(new Chapter() { Duration = TimeSpan.FromSeconds(duration / timeScale), Title = "Chapter 1" });
1010+
}
10041011

1012+
if (chapterListPointer != IntPtr.Zero)
1013+
{
10051014
NativeMethods.MP4Free(chapterListPointer);
10061015
}
10071016
}
10081017

10091018
private void WriteChapters(IntPtr fileHandle)
10101019
{
1011-
IntPtr chapterList = IntPtr.Zero;
1012-
int chapterCount = 0;
1013-
NativeMethods.MP4GetChapters(fileHandle, ref chapterList, ref chapterCount, NativeMethods.MP4ChapterType.Qt);
1014-
NativeMethods.MP4DeleteChapters(fileHandle, NativeMethods.MP4ChapterType.Any, NativeMethods.MP4InvalidTrackId);
1015-
1016-
int maxTrackId = 0;
1020+
// Find the first video track, so that we make sure the total duration
1021+
// of the chapters we add does not exceed the length of the file.
10171022
int referenceTrackId = -1;
10181023
for (short i = 0; i < NativeMethods.MP4GetNumberOfTracks(fileHandle, null, 0); i++)
10191024
{
1020-
maxTrackId = NativeMethods.MP4FindTrackId(fileHandle, i, null, 0);
1021-
string trackType = NativeMethods.MP4GetTrackType(fileHandle, maxTrackId);
1022-
if (trackType == NativeMethods.MP4VideoTrackType && referenceTrackId < 0)
1025+
int currentTrackId = NativeMethods.MP4FindTrackId(fileHandle, i, null, 0);
1026+
string trackType = NativeMethods.MP4GetTrackType(fileHandle, currentTrackId);
1027+
if (trackType == NativeMethods.MP4VideoTrackType)
10231028
{
1024-
referenceTrackId = maxTrackId;
1025-
}
1026-
1027-
if (NativeMethods.MP4HaveTrackAtom(fileHandle, maxTrackId, "tref.chap"))
1028-
{
1029-
// Subler does some work here to remove references to
1030-
// the chapter track that other tracks may have. If/when
1031-
// the Subler changes to the mp4v2 library are merged,
1032-
// we will consider doing that here.
1029+
referenceTrackId = currentTrackId;
1030+
break;
10331031
}
10341032
}
10351033

1036-
NativeMethods.MP4SetIntegerProperty(fileHandle, "moov.mvhd.nextTrackId", maxTrackId + 1);
1034+
// If we don't have a video track, then we have an audio file, which has
1035+
// only one track, and we can use it to find the duration.
10371036
referenceTrackId = referenceTrackId <= 0 ? 1 : referenceTrackId;
10381037
long referenceTrackDuration = NativeMethods.MP4ConvertFromTrackDuration(fileHandle, referenceTrackId, NativeMethods.MP4GetTrackDuration(fileHandle, referenceTrackId), NativeMethods.MP4TimeScale.Milliseconds);
10391038

1040-
long total = 0;
1039+
long runningTotal = 0;
10411040
List<NativeMethods.MP4Chapter> nativeChapters = new List<NativeMethods.MP4Chapter>();
10421041
foreach (Chapter chapter in this.chapters)
10431042
{
10441043
NativeMethods.MP4Chapter nativeChapter = new NativeMethods.MP4Chapter();
1044+
1045+
// Set the title
10451046
nativeChapter.title = new byte[1024];
10461047
byte[] titleByteArray = Encoding.UTF8.GetBytes(chapter.Title);
10471048
Array.Copy(titleByteArray, nativeChapter.title, titleByteArray.Length);
1049+
1050+
// Set the duration, making sure that we only use durations up to
1051+
// the length of the reference track.
10481052
long chapterLength = (long)chapter.Duration.TotalMilliseconds;
1049-
nativeChapter.duration = total + chapterLength > referenceTrackDuration ? referenceTrackDuration - total : chapterLength;
1050-
total += chapterLength;
1053+
if (runningTotal + chapterLength > referenceTrackDuration)
1054+
{
1055+
nativeChapter.duration = referenceTrackDuration - runningTotal;
1056+
}
1057+
else
1058+
{
1059+
nativeChapter.duration = chapterLength;
1060+
}
1061+
1062+
runningTotal += chapterLength;
10511063
nativeChapters.Add(nativeChapter);
1052-
if (total > referenceTrackDuration)
1064+
if (runningTotal > referenceTrackDuration)
10531065
{
10541066
break;
10551067
}
10561068
}
10571069

10581070
NativeMethods.MP4Chapter[] chapterArray = nativeChapters.ToArray();
10591071
NativeMethods.MP4SetChapters(fileHandle, chapterArray, chapterArray.Length, NativeMethods.MP4ChapterType.Qt);
1060-
NativeMethods.MP4Free(chapterList);
10611072
}
10621073
}
10631074
}

Knuckleball/NativeMethods.cs

+4-10
Original file line numberDiff line numberDiff line change
@@ -609,14 +609,6 @@ internal enum MP4TimeScale
609609
[DllImport("libMP4V2.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
610610
internal static extern long MP4GetTrackDuration(IntPtr hFile, int trackId);
611611

612-
[DllImport("libMP4V2.dll", CharSet = CharSet.Ansi, ExactSpelling = true, BestFitMapping = false, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
613-
[return: MarshalAs(UnmanagedType.U1)]
614-
internal static extern bool MP4HaveTrackAtom(IntPtr hFile, int trackId, [MarshalAs(UnmanagedType.LPStr)]string atomName);
615-
616-
[DllImport("libMP4V2.dll", CharSet = CharSet.Ansi, ExactSpelling = true, BestFitMapping = false, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
617-
[return: MarshalAs(UnmanagedType.U1)]
618-
internal static extern bool MP4SetIntegerProperty(IntPtr hFile, [MarshalAs(UnmanagedType.LPStr)]string propName, long value);
619-
620612
[DllImport("libMP4V2.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
621613
[return: MarshalAs(UnmanagedType.I4)]
622614
internal static extern MP4ChapterType MP4GetChapters(IntPtr hFile, ref IntPtr chapterList, ref int chapterCount, MP4ChapterType chapterType);
@@ -626,8 +618,10 @@ internal enum MP4TimeScale
626618
internal static extern MP4ChapterType MP4SetChapters(IntPtr hFile, [In, Out]MP4Chapter[] chapterList, int chapterCount, MP4ChapterType chapterType);
627619

628620
[DllImport("libMP4V2.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
629-
[return: MarshalAs(UnmanagedType.I4)]
630-
internal static extern MP4ChapterType MP4DeleteChapters(IntPtr hFile, MP4ChapterType chapterType, int chapterTrackId);
621+
internal static extern long MP4GetDuration(IntPtr hFile);
622+
623+
[DllImport("libMP4V2.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
624+
internal static extern int MP4GetTimeScale(IntPtr hFile);
631625

632626
/// <summary>
633627
/// Models an iTunes Metadata Format data atom contained in an iTMF metadata item atom.

0 commit comments

Comments
 (0)