-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
125 lines (105 loc) · 5.41 KB
/
Program.cs
File metadata and controls
125 lines (105 loc) · 5.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using System.Globalization;
using YoutubeExplode.Common;
using CsvHelper;
using McMaster.Extensions.CommandLineUtils;
class Program
{
[Option(Description = "The URL of the YouTube channel", ShortName = "u", LongName = "url")]
public string ChannelUrl { get; set; } = "https://www.youtube.com/@PirateSoftware";
[Option(Description = "The output directory for transcripts", ShortName = "o", LongName = "output")]
public string OutputDirectory { get; set; } = "S:\\Transcripts";
[Option(Description = "The length cutoff for videos (in hh:mm:ss format)", ShortName = "l", LongName = "length")]
public TimeSpan LengthCutOff { get; set; } = new TimeSpan(1, 3, 0);
[Option(Description = "The YouTube handle name", ShortName = "h", LongName = "handle")]
public string YTHandleName { get; set; } = "PirateSoftware";
static async Task Main(string[] args)
{
var app = new CommandLineApplication<Program>();
app.Conventions.UseDefaultConventions();
await app.ExecuteAsync(args);
}
private async Task OnExecuteAsync()
{
var youtube = new YoutubeExplode.YoutubeClient();
Console.WriteLine($"Fetching videos from channel: {ChannelUrl}");
Directory.CreateDirectory(OutputDirectory);
try
{
var channel = await youtube.Channels.GetByHandleAsync(YTHandleName);
Console.WriteLine($"Found channel: {channel.Title}");
var videos = await youtube.Channels.GetUploadsAsync(channel.Id).CollectAsync();
// Filter for livestreams
foreach (var video in videos)
{
// Get detailed video info to check if it's a pants, shorts, or vod
var videoDetails = await youtube.Videos.GetAsync(video.Id);
// Check if this was a vod, most pants are less than an hour and a half so thats the catch-all
if (!videoDetails.Description.Contains("Streamed", StringComparison.OrdinalIgnoreCase) &&
!videoDetails.Description.Contains("Live stream", StringComparison.OrdinalIgnoreCase) &&
!(videoDetails.Duration > LengthCutOff))
{
Console.WriteLine($"Skipping non-livestream: {video.Title}, {videoDetails.Description}");
continue;
}
Console.WriteLine($"Processing livestream: {video.Title}");
try
{
// Get closed captions tracks
var trackManifest = await youtube.Videos.ClosedCaptions.GetManifestAsync(video.Id);
var track = trackManifest.GetByLanguage("en");
if (track != null)
{
// Download the actual captions
YoutubeExplode.Videos.ClosedCaptions.ClosedCaptionTrack closedCaptions = await youtube.Videos.ClosedCaptions.GetAsync(track);
var transcriptEntries = new List<TranscriptEntry>();
foreach (var caption in closedCaptions.Captions)
{
int timestampSeconds = (int)caption.Offset.TotalSeconds;
transcriptEntries.Add(new TranscriptEntry
{
VideoId = video.Id,
VideoTitle = video.Title,
VideoUrl = $"https://www.youtube.com/watch?v={video.Id}",
VideoUrlWithTimestamp = $"https://www.youtube.com/watch?v={video.Id}&t={timestampSeconds}s",
TimestampSeconds = timestampSeconds,
StreamDate = videoDetails.UploadDate.ToString("yyyy-MM-dd"),
Timestamp = caption.Offset.ToString(),
Duration = caption.Duration.ToString(),
Text = caption.Text
});
}
// Save to CSV
var filename = Path.Combine(OutputDirectory,
$"{videoDetails.UploadDate.ToString("yyyy-MM-dd")}_{SanitizeFileName(video.Title)}_{video.Id}.csv");
using (var writer = new StreamWriter(filename))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(transcriptEntries);
}
Console.WriteLine($"Saved transcript to: {filename}");
}
else
{
Console.WriteLine($"No English captions found for: {video.Title}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error processing video {video.Title}: {ex.Message}");
}
// Dont get flagged by the API
await Task.Delay(3000);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
private static string SanitizeFileName(string fileName)
{
var invalidChars = Path.GetInvalidFileNameChars();
return string.Join("_", fileName.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries));
}
}