/// <summary> /// Reads the chapter information from the specified file. /// </summary> /// <param name="fileHandle">The handle to the file from which to read the chapter information.</param> /// <returns>A new instance of a <see cref="ChapterList"/> object containing the information /// about the chapters for the file.</returns> internal static ChapterList ReadFromFile(IntPtr fileHandle) { ChapterList list = new ChapterList(); IntPtr chapterListPointer = IntPtr.Zero; int chapterCount = 0; NativeMethods.MP4ChapterType chapterType = NativeMethods.MP4GetChapters(fileHandle, ref chapterListPointer, ref chapterCount, NativeMethods.MP4ChapterType.Qt); if (chapterType != NativeMethods.MP4ChapterType.None && chapterCount != 0) { IntPtr currentChapterPointer = chapterListPointer; for (int i = 0; i < chapterCount; i++) { NativeMethods.MP4Chapter currentChapter = currentChapterPointer.ReadStructure <NativeMethods.MP4Chapter>(); TimeSpan duration = TimeSpan.FromMilliseconds(currentChapter.duration); string title = Encoding.UTF8.GetString(currentChapter.title); if ((currentChapter.title[0] == 0xFE && currentChapter.title[1] == 0xFF) || (currentChapter.title[0] == 0xFF && currentChapter.title[1] == 0xFE)) { title = Encoding.Unicode.GetString(currentChapter.title); } title = title.Substring(0, title.IndexOf('\0')); list.AddInternal(new Chapter() { Duration = duration, Title = title }); currentChapterPointer = IntPtr.Add(currentChapterPointer, Marshal.SizeOf(currentChapter)); } } else { int timeScale = NativeMethods.MP4GetTimeScale(fileHandle); long duration = NativeMethods.MP4GetDuration(fileHandle); list.AddInternal(new Chapter() { Duration = TimeSpan.FromSeconds(duration / timeScale), Title = "Chapter 1" }); } if (chapterListPointer != IntPtr.Zero) { NativeMethods.MP4Free(chapterListPointer); } return(list); }
/// <summary> /// Writes the chapter information to the file. /// </summary> /// <param name="fileHandle">The handle to the file to which to write the chapter information.</param> internal void WriteToFile(IntPtr fileHandle) { // Only write to the file if there have been changes since the chapters were read. // Note that a happy side effect of this is that if there were no chapters specified // in the file at read time, and no manipulation was done before write, we will not // write chapters into the file, even though our internal representation will contain // a single chapter with the full duration of the file, and the title of "Chapter 1". if (this.IsDirty) { // Find the first video track, so that we make sure the total duration // of the chapters we add does not exceed the length of the file. int referenceTrackId = -1; for (short i = 0; i < NativeMethods.MP4GetNumberOfTracks(fileHandle, null, 0); i++) { int currentTrackId = NativeMethods.MP4FindTrackId(fileHandle, i, null, 0); string trackType = NativeMethods.MP4GetTrackType(fileHandle, currentTrackId); if (trackType == NativeMethods.MP4VideoTrackType) { referenceTrackId = currentTrackId; break; } } // If we don't have a video track, then we have an audio file, which has // only one track, and we can use it to find the duration. referenceTrackId = referenceTrackId <= 0 ? 1 : referenceTrackId; long referenceTrackDuration = NativeMethods.MP4ConvertFromTrackDuration(fileHandle, referenceTrackId, NativeMethods.MP4GetTrackDuration(fileHandle, referenceTrackId), NativeMethods.MP4TimeScale.Milliseconds); long runningTotal = 0; List <NativeMethods.MP4Chapter> nativeChapters = new List <NativeMethods.MP4Chapter>(); foreach (Chapter chapter in this.chapters) { NativeMethods.MP4Chapter nativeChapter = new NativeMethods.MP4Chapter(); // Set the title nativeChapter.title = new byte[1024]; byte[] titleByteArray = Encoding.UTF8.GetBytes(chapter.Title); Array.Copy(titleByteArray, nativeChapter.title, titleByteArray.Length); // Set the duration, making sure that we only use durations up to // the length of the reference track. long chapterLength = (long)chapter.Duration.TotalMilliseconds; if (runningTotal + chapterLength > referenceTrackDuration) { nativeChapter.duration = referenceTrackDuration - runningTotal; } else { nativeChapter.duration = chapterLength; } runningTotal += chapterLength; nativeChapters.Add(nativeChapter); if (runningTotal > referenceTrackDuration) { break; } } NativeMethods.MP4Chapter[] chapterArray = nativeChapters.ToArray(); NativeMethods.MP4SetChapters(fileHandle, chapterArray, chapterArray.Length, NativeMethods.MP4ChapterType.Qt); } }