Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New video finalization method #1103

Merged
merged 11 commits into from
Jun 20, 2024
8 changes: 7 additions & 1 deletion TwitchDownloaderCore/ChatDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,16 @@ public async Task DownloadAsync(CancellationToken cancellationToken)
}
catch
{
await Task.Delay(100, CancellationToken.None);

outputFileInfo.Refresh();
if (outputFileInfo.Exists && outputFileInfo.Length == 0)
{
outputFileInfo.Delete();
try
{
outputFileInfo.Delete();
}
catch { }
}

throw;
Expand Down
14 changes: 12 additions & 2 deletions TwitchDownloaderCore/ChatRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,28 @@ public async Task RenderVideoAsync(CancellationToken cancellationToken)
}
catch
{
await Task.Delay(100, CancellationToken.None);

outputFileInfo.Refresh();
if (outputFileInfo.Exists && outputFileInfo.Length == 0)
{
outputFileInfo.Delete();
try
{
outputFileInfo.Delete();
}
catch { }
}

if (maskFileInfo is not null)
{
maskFileInfo.Refresh();
if (maskFileInfo.Exists && maskFileInfo.Length == 0)
{
maskFileInfo.Delete();
try
{
maskFileInfo.Delete();
}
catch { }
}
}

Expand Down
8 changes: 7 additions & 1 deletion TwitchDownloaderCore/ChatUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ public async Task UpdateAsync(CancellationToken cancellationToken)
}
catch
{
await Task.Delay(100, CancellationToken.None);

outputFileInfo.Refresh();
if (outputFileInfo.Exists && outputFileInfo.Length == 0)
{
outputFileInfo.Delete();
try
{
outputFileInfo.Delete();
}
catch { }
}

throw;
Expand Down
10 changes: 9 additions & 1 deletion TwitchDownloaderCore/ClipDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,16 @@ public async Task DownloadAsync(CancellationToken cancellationToken)
}
catch
{
await Task.Delay(100, CancellationToken.None);

outputFileInfo.Refresh();
if (outputFileInfo.Exists && outputFileInfo.Length == 0)
{
outputFileInfo.Delete();
try
{
outputFileInfo.Delete();
}
catch { }
}

throw;
Expand Down Expand Up @@ -101,6 +107,8 @@ private async Task DownloadAsyncImpl(FileInfo outputFileInfo, FileStream outputF
}
finally
{
await Task.Delay(100, CancellationToken.None);

File.Delete(tempFile);
}

Expand Down
35 changes: 35 additions & 0 deletions TwitchDownloaderCore/Tools/FfmpegConcatList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace TwitchDownloaderCore.Tools
{
// https://www.ffmpeg.org/ffmpeg-formats.html#toc-concat-1
public static class FfmpegConcatList
{
private const string LINE_FEED = "\u000A";

public static async Task SerializeAsync(string filePath, M3U8 playlist, Range videoListCrop, CancellationToken cancellationToken = default)
{
await using var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
await using var sw = new StreamWriter(fs) { NewLine = LINE_FEED };

await sw.WriteLineAsync("ffconcat version 1.0");

foreach (var stream in playlist.Streams.Take(videoListCrop))
{
cancellationToken.ThrowIfCancellationRequested();

await sw.WriteAsync("file '");
await sw.WriteAsync(stream.Path);
await sw.WriteLineAsync('\'');

await sw.WriteAsync("duration ");
await sw.WriteLineAsync(stream.PartInfo.Duration.ToString(CultureInfo.InvariantCulture));
}
}
}
}
23 changes: 19 additions & 4 deletions TwitchDownloaderCore/Tools/FfmpegMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public static class FfmpegMetadata
private const string LINE_FEED = "\u000A";

public static async Task SerializeAsync(string filePath, string streamerName, string videoId, string videoTitle, DateTime videoCreation, int viewCount, string videoDescription = null,
TimeSpan startOffset = default, IEnumerable<VideoMomentEdge> videoMomentEdges = null, CancellationToken cancellationToken = default)
TimeSpan startOffset = default, TimeSpan videoLength = default, IEnumerable<VideoMomentEdge> videoMomentEdges = null, CancellationToken cancellationToken = default)
{
await using var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
await using var sw = new StreamWriter(fs) { NewLine = LINE_FEED };

await SerializeGlobalMetadata(sw, streamerName, videoId, videoTitle, videoCreation, viewCount, videoDescription);
await fs.FlushAsync(cancellationToken);

await SerializeChapters(sw, videoMomentEdges, startOffset);
await SerializeChapters(sw, videoMomentEdges, startOffset, videoLength);
await fs.FlushAsync(cancellationToken);
}

Expand All @@ -44,14 +44,13 @@ private static async Task SerializeGlobalMetadata(StreamWriter sw, string stream
await sw.WriteLineAsync(@$"Views: {viewCount}");
}

private static async Task SerializeChapters(StreamWriter sw, IEnumerable<VideoMomentEdge> videoMomentEdges, TimeSpan startOffset)
private static async Task SerializeChapters(StreamWriter sw, IEnumerable<VideoMomentEdge> videoMomentEdges, TimeSpan startOffset, TimeSpan videoLength)
{
if (videoMomentEdges is null)
{
return;
}

// Note: FFmpeg automatically handles out of range chapters for us
var startOffsetMillis = (int)startOffset.TotalMilliseconds;
foreach (var momentEdge in videoMomentEdges)
{
Expand All @@ -64,6 +63,22 @@ private static async Task SerializeChapters(StreamWriter sw, IEnumerable<VideoMo
var lengthMillis = momentEdge.node.durationMilliseconds;
var gameName = momentEdge.node.details.game?.displayName ?? momentEdge.node.description;

// videoLength may be 0 if it is not passed as an arg
if (videoLength > TimeSpan.Zero)
{
var chapterStart = TimeSpan.FromMilliseconds(startMillis);
if (chapterStart >= videoLength)
{
continue;
}

var chapterEnd = chapterStart + TimeSpan.FromMilliseconds(lengthMillis);
if (chapterEnd > videoLength)
{
lengthMillis = (int)(videoLength - chapterStart).TotalMilliseconds;
}
}

await sw.WriteLineAsync("[CHAPTER]");
await sw.WriteLineAsync("TIMEBASE=1/1000");
await sw.WriteLineAsync($"START={startMillis}");
Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderCore/Tools/M3U8.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public enum PlaylistType
// Twitch specific
public uint TwitchLiveSequence { get; private set; }
public decimal TwitchElapsedSeconds { get; private set; }
public decimal TwitchTotalSeconds { get; private set; }
public decimal TwitchTotalSeconds { get; internal set; }

// Other headers that we don't have dedicated properties for. Useful for debugging.
private readonly List<KeyValuePair<string, string>> _unparsedValues = new();
Expand Down
8 changes: 7 additions & 1 deletion TwitchDownloaderCore/TsMerger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ public async Task MergeAsync(CancellationToken cancellationToken)
}
catch
{
await Task.Delay(100, CancellationToken.None);

outputFileInfo.Refresh();
if (outputFileInfo.Exists && outputFileInfo.Length == 0)
{
outputFileInfo.Delete();
try
{
outputFileInfo.Delete();
}
catch { }
}

throw;
Expand Down
Loading