/// <summary> /// </summary> /// <param name="manifestUri"></param> /// <param name="manifestPath"></param> /// <param name="mkvPath"></param> /// <param name="isDeterministic"></param> /// <param name="stopAfter"></param> /// <param name="setupStop"></param> /// <param name="displayDuration"></param> public static void DownloadAndMux(Uri manifestUri, string manifestPath, string mkvPath, bool isDeterministic, TimeSpan stopAfter, SetupStop setupStop, DisplayDuration displayDuration) { string manifestParentPath = null; ManifestInfo manifestInfo; if (manifestPath != null) { manifestParentPath = Path.GetDirectoryName(manifestPath); Console.WriteLine($"Parsing local manifest file: {manifestPath}"); using (var manifestStream = new FileStream(manifestPath, FileMode.Open)) { manifestInfo = ManifestInfo.ParseManifest(manifestStream, new Uri(LocalUrlPrefix)); } } else { Console.WriteLine($"Downloading and parsing manifest: {manifestUri}"); using (var manifestStream = new WebClient().OpenRead(manifestUri)) { manifestInfo = ManifestInfo.ParseManifest(manifestStream, manifestUri); } } Console.Write(manifestInfo.GetDescription()); var tracks = ( from streamInfo in manifestInfo.SelectedStreams from trackInfo in streamInfo.SelectedTracks select new Track(trackInfo) ).ToList(); var trackEntries = new List <TrackEntry>(); var trackSamples = new List <IList <MediaSample> >(); for (var i = 0; i < tracks.Count; ++i) { trackEntries.Add(tracks[i].TrackInfo.TrackEntry); trackEntries[i].TrackNumber = (ulong)(i + 1); trackSamples.Add(new List <MediaSample>()); } foreach (var track in tracks) { if (track.TrackInfo.Stream.ChunkList.Count == 0) { track.NextStartTime = 0; } else { track.NextStartTime = track.TrackInfo.Stream.ChunkList[0].StartTime; } } Console.WriteLine("Also muxing selected tracks to MKV: " + mkvPath); try { if (Directory.GetParent(mkvPath) != null && !Directory.GetParent(mkvPath).Exists) { Directory.GetParent(mkvPath).Create(); } } catch (IOException) { throw new Exception(); } var maxTrackEndTimeHint = manifestInfo.Duration; foreach (var track in tracks) { var chunkInfos = track.TrackInfo.Stream.ChunkList; var lastIndex = chunkInfos.Count - 1; if (lastIndex < 0) { continue; } var trackDuration = chunkInfos[lastIndex].StartTime + chunkInfos[lastIndex].Duration; if (maxTrackEndTimeHint < trackDuration) { maxTrackEndTimeHint = trackDuration; } } var muxStatePath = Path.ChangeExtension(mkvPath, "muxstate"); var muxStateOldPath = muxStatePath + ".old"; byte[] previousMuxState = null; if (File.Exists(muxStatePath)) { using (var stream = new FileStream(muxStatePath ?? throw new Exception(), FileMode.Open)) { previousMuxState = ReadFileStream(stream); } if (previousMuxState.Length > 0) { try { File.Move(muxStatePath, muxStateOldPath); } catch (IOException) { File.Replace(muxStatePath, muxStateOldPath, null, true); } } } var source = new DownloadingMediaDataSource(tracks, manifestParentPath, manifestInfo.TimeScale, manifestInfo.IsLive, (ulong)stopAfter.Ticks, manifestInfo.TotalTicks, displayDuration); setupStop(manifestInfo.IsLive, source); var muxStateWriter = new MuxStateWriter(new FileStream(muxStatePath, FileMode.Create)); try { MkvUtils.WriteMkv(mkvPath, trackEntries, source, maxTrackEndTimeHint, manifestInfo.TimeScale, isDeterministic, previousMuxState, muxStateWriter); } finally { muxStateWriter.Close(); } File.Delete(muxStatePath); if (File.Exists(muxStateOldPath)) { File.Delete(muxStateOldPath); } }
// Exactly one of manifestUri and manifestPath must be set. public static void DownloadAndMux(Uri manifestUri, string manifestPath, string mkvPath, bool isDeterministic, TimeSpan stopAfter, SetupStop setupStop, DisplayDuration displayDuration) { string manifestParentPath = null; // A null indicates a remote manifest file. ManifestInfo manifestInfo; if (manifestPath != null) { manifestParentPath = Path.GetDirectoryName(manifestPath); Console.WriteLine("Parsing local manifest file: " + manifestPath); using (FileStream manifestStream = new FileStream(manifestPath, FileMode.Open)) { manifestInfo = ManifestInfo.ParseManifest(manifestStream, /*manifestUri:*/ new Uri(LOCAL_URL_PREFIX)); } } else { Console.WriteLine("Downloading and parsing manifest: " + manifestUri); WebClient webClient = new WebClient(); using (Stream manifestStream = webClient.OpenRead(manifestUri)) { manifestInfo = ManifestInfo.ParseManifest(manifestStream, manifestUri); } } Console.Write(manifestInfo.GetDescription()); IList <Track> tracks = new List <Track>(); foreach (StreamInfo streamInfo in manifestInfo.SelectedStreams) { foreach (TrackInfo trackInfo in streamInfo.SelectedTracks) { tracks.Add(new Track(trackInfo)); } } IList <TrackEntry> trackEntries = new List <TrackEntry>(); IList <IList <MediaSample> > trackSamples = new List <IList <MediaSample> >(); for (int i = 0; i < tracks.Count; ++i) { trackEntries.Add(tracks[i].TrackInfo.TrackEntry); trackEntries[i].TrackNumber = (ulong)(i + 1); trackSamples.Add(new List <MediaSample>()); } for (int i = 0; i < tracks.Count; i++) { // TODO: Add a facility to start live streams from a later chunk (it was chunkIndex=10 previously). // Our design allows for an empty ChunkList, in case live streams are growing. tracks[i].NextStartTime = tracks[i].TrackInfo.Stream.ChunkList.Count == 0 ? 0 : tracks[i].TrackInfo.Stream.ChunkList[0].StartTime; } // TODO: Test for live streams (see the StackOverflow question). Console.WriteLine("Also muxing selected tracks to MKV: " + mkvPath); try { if (Directory.GetParent(mkvPath) != null && !Directory.GetParent(mkvPath).Exists) { Directory.GetParent(mkvPath).Create(); } } catch (IOException) { // TODO: Add nicer error reporting, without a stack trace. throw new Exception("Cannot not create the directory of .mkv: " + mkvPath); } ulong maxTrackEndTimeHint = manifestInfo.Duration; for (int i = 0; i < tracks.Count; ++i) { IList <ChunkInfo> chunkInfos = tracks[i].TrackInfo.Stream.ChunkList; int j = chunkInfos.Count - 1; if (j >= 0) // Our design allows for an empty ChunkList. { ulong trackDuration = chunkInfos[j].StartTime + chunkInfos[j].Duration; if (maxTrackEndTimeHint < trackDuration) { maxTrackEndTimeHint = trackDuration; } } } // The .muxstate file is approximately 1/5441.43 of the size of the .mkv. // The .muxstate file is around 28.088 bytes per second. TODO: Update this after n. // Sometimes totalDuration of video is 1156420602, audio is 1156818141 (larger), so we just take the maximum. string muxStatePath = Path.ChangeExtension(mkvPath, "muxstate"); string muxStateOldPath = muxStatePath + ".old"; byte[] oldMuxState = null; if (File.Exists(muxStatePath)) // False for directories. { using (FileStream fileStream = new FileStream(muxStatePath, FileMode.Open)) { oldMuxState = ReadFileStream(fileStream); } if (oldMuxState.Length > 0) { // File.Move fails with IOException if the destination already exists. // C# and .NET SUXX: There is no atomic overwrite-move. try { File.Move(muxStatePath, muxStateOldPath); } catch (IOException) { File.Replace(muxStatePath, muxStateOldPath, null, true); } } } DownloadingMediaDataSource source = new DownloadingMediaDataSource( tracks, manifestParentPath, manifestInfo.TimeScale, manifestInfo.IsLive, (ulong)stopAfter.Ticks, manifestInfo.TotalTicks, displayDuration); setupStop(manifestInfo.IsLive, source); MuxStateWriter muxStateWriter = new MuxStateWriter(new FileStream(muxStatePath, FileMode.Create)); try { MkvUtils.WriteMkv(mkvPath, trackEntries, source, maxTrackEndTimeHint, manifestInfo.TimeScale, isDeterministic, oldMuxState, muxStateWriter); } finally { muxStateWriter.Close(); } File.Delete(muxStatePath); if (File.Exists(muxStateOldPath)) { File.Delete(muxStateOldPath); } }
// Exactly one of manifestUri and manifestPath must be set. public static void DownloadAndMux(Uri manifestUri, string manifestPath, string mkvPath, bool isDeterministic, TimeSpan stopAfter, SetupStop setupStop, DisplayDuration displayDuration) { string manifestParentPath = null; // A null indicates a remote manifest file. ManifestInfo manifestInfo; if (manifestPath != null) { manifestParentPath = Path.GetDirectoryName(manifestPath); Console.WriteLine("Parsing local manifest file: " + manifestPath); using (FileStream manifestStream = new FileStream(manifestPath, FileMode.Open)) { manifestInfo = ManifestInfo.ParseManifest(manifestStream, /*manifestUri:*/new Uri(LOCAL_URL_PREFIX)); } } else { Console.WriteLine("Downloading and parsing manifest: " + manifestUri); WebClient webClient = new WebClient(); using (Stream manifestStream = webClient.OpenRead(manifestUri)) { manifestInfo = ManifestInfo.ParseManifest(manifestStream, manifestUri); } } Console.Write(manifestInfo.GetDescription()); IList<Track> tracks = new List<Track>(); foreach (StreamInfo streamInfo in manifestInfo.SelectedStreams) { foreach (TrackInfo trackInfo in streamInfo.SelectedTracks) { tracks.Add(new Track(trackInfo)); } } IList<TrackEntry> trackEntries = new List<TrackEntry>(); IList<IList<MediaSample>> trackSamples = new List<IList<MediaSample>>(); for (int i = 0; i < tracks.Count; ++i) { trackEntries.Add(tracks[i].TrackInfo.TrackEntry); trackEntries[i].TrackNumber = (ulong)(i + 1); trackSamples.Add(new List<MediaSample>()); } for (int i = 0; i < tracks.Count; i++) { // TODO: Add a facility to start live streams from a later chunk (it was chunkIndex=10 previously). // Our design allows for an empty ChunkList, in case live streams are growing. tracks[i].NextStartTime = tracks[i].TrackInfo.Stream.ChunkList.Count == 0 ? 0 : tracks[i].TrackInfo.Stream.ChunkList[0].StartTime; } // TODO: Test for live streams (see the StackOverflow question). Console.WriteLine("Also muxing selected tracks to MKV: " + mkvPath); try { if (Directory.GetParent(mkvPath) != null && !Directory.GetParent(mkvPath).Exists) Directory.GetParent(mkvPath).Create(); } catch (IOException) { // TODO: Add nicer error reporting, without a stack trace. throw new Exception("Cannot not create the directory of .mkv: " + mkvPath); } ulong maxTrackEndTimeHint = manifestInfo.Duration; for (int i = 0; i < tracks.Count; ++i) { IList<ChunkInfo> chunkInfos = tracks[i].TrackInfo.Stream.ChunkList; int j = chunkInfos.Count - 1; if (j >= 0) { // Our design allows for an empty ChunkList. ulong trackDuration = chunkInfos[j].StartTime + chunkInfos[j].Duration; if (maxTrackEndTimeHint < trackDuration) maxTrackEndTimeHint = trackDuration; } } // The .muxstate file is approximately 1/5441.43 of the size of the .mkv. // The .muxstate file is around 28.088 bytes per second. TODO: Update this after n. // Sometimes totalDuration of video is 1156420602, audio is 1156818141 (larger), so we just take the maximum. string muxStatePath = Path.ChangeExtension(mkvPath, "muxstate"); string muxStateOldPath = muxStatePath + ".old"; byte[] oldMuxState = null; if (File.Exists(muxStatePath)) { // False for directories. using (FileStream fileStream = new FileStream(muxStatePath, FileMode.Open)) { oldMuxState = ReadFileStream(fileStream); } if (oldMuxState.Length > 0) { // File.Move fails with IOException if the destination already exists. // C# and .NET SUXX: There is no atomic overwrite-move. try { File.Move(muxStatePath, muxStateOldPath); } catch (IOException) { File.Replace(muxStatePath, muxStateOldPath, null, true); } } } DownloadingMediaDataSource source = new DownloadingMediaDataSource( tracks, manifestParentPath, manifestInfo.TimeScale, manifestInfo.IsLive, (ulong)stopAfter.Ticks, manifestInfo.TotalTicks, displayDuration); setupStop(manifestInfo.IsLive, source); MuxStateWriter muxStateWriter = new MuxStateWriter(new FileStream(muxStatePath, FileMode.Create)); try { MkvUtils.WriteMkv(mkvPath, trackEntries, source, maxTrackEndTimeHint, manifestInfo.TimeScale, isDeterministic, oldMuxState, muxStateWriter); } finally { muxStateWriter.Close(); } File.Delete(muxStatePath); if (File.Exists(muxStateOldPath)) { File.Delete(muxStateOldPath); } }