Skip to content

Commit

Permalink
YouTube Upload (MathewSachin#313)
Browse files Browse the repository at this point in the history
  • Loading branch information
MathewSachin authored Jan 17, 2019
1 parent e7297fd commit c2eed2f
Show file tree
Hide file tree
Showing 20 changed files with 609 additions and 4 deletions.
4 changes: 4 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ configuration:
environment:
imgur_client_id:
secure: Y/2WUSisk7oLSQNY1YzUxw==
yt_client_id:
secure: CPwUWOhTyZ2IiE+lvVilTKQz1h8sbn7FmkTJvtTlY6mJf2pDlfDhFmhDk1i47M9OPnD4y8a8BlB6yfkpVt/0U/PODAXPzFR0QuOWIfJvltc=
yt_client_secret:
secure: fsl/BRldjlmEJCroWa0iqw5tAxbGx84BbHT3BpzaeX4=
choco_key:
secure: h5jOVeiDmjLnF3EotkOhBFJharX/9TWx6OWykiXn30pWSIfVjSvAaCJM1Y48zjXr
git_key:
Expand Down
13 changes: 12 additions & 1 deletion src/Captura.Base/DelegateCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Windows.Input;

namespace Captura
Expand All @@ -7,6 +8,7 @@ public class DelegateCommand : ICommand
{
readonly Action<object> _execute;
bool _canExecute;
readonly SynchronizationContext _syncContext = SynchronizationContext.Current;

public DelegateCommand(Action<object> OnExecute, bool CanExecute = true)
{
Expand All @@ -28,7 +30,16 @@ public void RaiseCanExecuteChanged(bool CanExecute)
{
_canExecute = CanExecute;

CanExecuteChanged?.Invoke(this, EventArgs.Empty);
void Do()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

if (_syncContext != null)
{
_syncContext.Post(S => Do(), null);
}
else Do();
}

public event EventHandler CanExecuteChanged;
Expand Down
2 changes: 2 additions & 0 deletions src/Captura.Base/Services/IMainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public interface IMainWindow
void CropImage(string FileName);

void TrimMedia(string FileName);

void UploadToYouTube(string FileName);
}
}
6 changes: 5 additions & 1 deletion src/Captura.Core/ApiKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ namespace Captura
/// On Production builds, AppVeyor embeds Api Keys into the app.
/// </summary>
// ReSharper disable once ClassNeverInstantiated.Global
class ApiKeys : IImgurApiKeys
class ApiKeys : IImgurApiKeys, IYouTubeApiKeys
{
static string Get(string Key) => Environment.GetEnvironmentVariable(Key, EnvironmentVariableTarget.User) ?? "";

public string ImgurClientId => Get("imgur_client_id");

public string ImgurSecret => Get("imgur_secret");

public string YouTubeClientId => Get("yt_client_id");

public string YouTubeClientSecret => Get("yt_client_secret");
}
}
1 change: 1 addition & 0 deletions src/Captura.Core/Captura.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ProjectReference Include="..\Captura.NAudio\Captura.NAudio.csproj" />
<ProjectReference Include="..\Captura.SharpAvi\Captura.SharpAvi.csproj" />
<ProjectReference Include="..\Captura.Webcam\Captura.Webcam.csproj" />
<ProjectReference Include="..\Captura.YouTube\Captura.YouTube.csproj" />
<ProjectReference Include="..\DesktopDuplication\DesktopDuplication.csproj" />
<ProjectReference Include="..\Screna\Screna.csproj" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Captura.Core/CoreModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public void OnLoad(IBinder Binder)
Binder.Bind<IImageUploader, ImgurUploader>();
Binder.Bind<IIconSet, MaterialDesignIcons>();
Binder.Bind<IImgurApiKeys, ApiKeys>();
Binder.Bind<IYouTubeApiKeys, ApiKeys>();

Binder.BindSingleton<FullScreenItem>();
Binder.BindSingleton<FFmpegLog>();
Expand Down
11 changes: 10 additions & 1 deletion src/Captura.Core/Models/Recents/FileRecentItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public FileRecentItem(string FileName, RecentFileType FileType, bool IsSaving =
new RecentAction(loc.CopyPath, icons.Clipboard, () => this.FileName.WriteToClipboard())
};

void AddTrimMedia()
{
list.Add(new RecentAction(loc.Trim, icons.Trim, () => windowService.TrimMedia(FileName)));
}

switch (FileType)
{
case RecentFileType.Image:
Expand All @@ -48,8 +53,12 @@ public FileRecentItem(string FileName, RecentFileType FileType, bool IsSaving =
break;

case RecentFileType.Audio:
AddTrimMedia();
break;

case RecentFileType.Video:
list.Add(new RecentAction(loc.Trim, icons.Trim, () => windowService.TrimMedia(FileName)));
AddTrimMedia();
list.Add(new RecentAction("Upload to YouTube", icons.YouTube, () => windowService.UploadToYouTube(FileName)));
break;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Captura.Fakes/FakeWindowProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public void EditImage(string FileName) { }
public void CropImage(string FileName) { }

public void TrimMedia(string FileName) { }

public void UploadToYouTube(string FileName) { }
}
}
18 changes: 18 additions & 0 deletions src/Captura.YouTube/Captura.YouTube.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Reference Include="System.Drawing" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.36.1.1226" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Captura.Base\Captura.Base.csproj" />
<ProjectReference Include="..\Screna\Screna.csproj" />
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions src/Captura.YouTube/IYouTubeApiKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Captura
{
public interface IYouTubeApiKeys
{
string YouTubeClientId { get; }

string YouTubeClientSecret { get; }
}
}
9 changes: 9 additions & 0 deletions src/Captura.YouTube/YouTubePrivacyStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Captura
{
public enum YouTubePrivacyStatus
{
Public,
Unlisted,
Private
}
}
56 changes: 56 additions & 0 deletions src/Captura.YouTube/YouTubeUploadRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Upload;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;

namespace Captura
{
public class YouTubeUploadRequest : IDisposable
{
readonly VideosResource.InsertMediaUpload _videoInsertRequest;
readonly Stream _dataStream;

internal YouTubeUploadRequest(string FileName,
YouTubeService YouTubeService,
Video Video)
{
_dataStream = new FileStream(FileName, FileMode.Open);
_videoInsertRequest = YouTubeService.Videos.Insert(Video, "snippet,status", _dataStream, "video/*");

_videoInsertRequest.ProgressChanged += VideosInsertRequest_ProgressChanged;
_videoInsertRequest.ResponseReceived += VideosInsertRequest_ResponseReceived;
}

void VideosInsertRequest_ProgressChanged(IUploadProgress Progress)
{
BytesSent?.Invoke(Progress.BytesSent);
}

void VideosInsertRequest_ResponseReceived(Video Video)
{
Uploaded?.Invoke($"https://youtube.com/watch?v={Video.Id}");
}

public async Task<IUploadProgress> Upload(CancellationToken CancellationToken)
{
return await _videoInsertRequest.UploadAsync(CancellationToken);
}

public async Task<IUploadProgress> Resume(CancellationToken CancellationToken)
{
return await _videoInsertRequest.ResumeAsync(CancellationToken);
}

public event Action<long> BytesSent;

public event Action<string> Uploaded;

public void Dispose()
{
_dataStream.Dispose();
}
}
}
80 changes: 80 additions & 0 deletions src/Captura.YouTube/YouTubeUploader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;

namespace Captura
{
// ReSharper disable once ClassNeverInstantiated.Global
public class YouTubeUploader
{
readonly IYouTubeApiKeys _apiKeys;
readonly ProxySettings _proxySettings;

YouTubeService _youtubeService;

public YouTubeUploader(IYouTubeApiKeys ApiKeys,
ProxySettings ProxySettings)
{
_apiKeys = ApiKeys;
_proxySettings = ProxySettings;
}

static string GetPrivacyStatus(YouTubePrivacyStatus PrivacyStatus)
{
return PrivacyStatus.ToString().ToLower();
}

public async Task<YouTubeUploadRequest> CreateUploadRequest(string FileName,
string Title,
string Description,
string[] Tags = null,
YouTubePrivacyStatus PrivacyStatus = YouTubePrivacyStatus.Unlisted)
{
if (_youtubeService == null)
await Init();

var video = new Video
{
Snippet = new VideoSnippet
{
Title = Title,
Description = Description,
Tags = Tags ?? new string[0],
CategoryId = "22"
},
Status = new VideoStatus { PrivacyStatus = GetPrivacyStatus(PrivacyStatus) }
};

return new YouTubeUploadRequest(FileName, _youtubeService, video);
}

async Task Init()
{
WebRequest.DefaultWebProxy = _proxySettings.GetWebProxy();

var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync
(
new ClientSecrets
{
ClientId = _apiKeys.YouTubeClientId,
ClientSecret = _apiKeys.YouTubeClientSecret
},
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);

_youtubeService = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = nameof(Captura)
});
}
}
}
Loading

0 comments on commit c2eed2f

Please sign in to comment.