├── screenshot.png ├── YoutubeBulkUploadUI ├── YoutubeBulkUploadUI │ ├── App.config │ ├── VideoVisibility.cs │ ├── obfuscar.xml │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── App.xaml │ ├── App.xaml.cs │ ├── packages.config │ ├── MainWindow.xaml │ ├── YoutubeBulkUploadUI.csproj │ └── MainWindow.xaml.cs └── YoutubeBulkUploadUI.sln ├── runbook.txt ├── todo.txt ├── .gitattributes ├── index.html ├── README.md ├── .gitignore └── categories.json /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staafl/youtube-bulk-upload-ui/HEAD/screenshot.png -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/VideoVisibility.cs: -------------------------------------------------------------------------------- 1 | namespace YoutubeBulkUploadUI 2 | { 3 | public enum VideoVisibility 4 | { 5 | Public, 6 | Unlisted, 7 | Private 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/obfuscar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /runbook.txt: -------------------------------------------------------------------------------- 1 | - Updating documentation 2 | 3 | sg-scp index.html youtube-bulk-upload index.html 4 | 5 | - Releasing new version 6 | 7 | Build, then copy over the exe from Obfuscator_Output into the output folder. Then zip up and upload. Don't forget to delete the xml files, the upload log and csv files and any previous zips. 8 | 9 | Also tag the current commit with the assembly version. 10 | 11 | - Google OAuth credentials 12 | %APPDATA%\YouTube.Auth.Store -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace YoutubeBulkUploadUI 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | SelfDeclaredMadeForKids vs MadeForKids? 2 | Age restrictions? 3 | Retrieve duration after upload? 4 | Config 5 | - country code (or detect) 6 | - fetch categories 7 | - defaults 8 | - open log on finished 9 | Logging 10 | Way to move video up or down on grid 11 | upload.csv - write video durations 12 | 13 | https://developers.google.com/youtube/terms/developer-policies 14 | https://support.google.com/youtube/contact/yt_api_form 15 | https://developers.google.com/youtube/v3/getting-started#quota -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26906.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YoutubeBulkUploadUI", "YoutubeBulkUploadUI\YoutubeBulkUploadUI.csproj", "{BA15CC42-AD77-4D9C-B857-4D0495F5067A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {BA15CC42-AD77-4D9C-B857-4D0495F5067A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {BA15CC42-AD77-4D9C-B857-4D0495F5067A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {BA15CC42-AD77-4D9C-B857-4D0495F5067A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {BA15CC42-AD77-4D9C-B857-4D0495F5067A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {CA72DB95-3EBC-42F9-93C8-3D5241CE45B7} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace YoutubeBulkUploadUI.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("YoutubeBulkUploadUI")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("YoutubeBulkUploadUI")] 15 | [assembly: AssemblyCopyright("Copyright © 2021")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.*")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | #* text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace YoutubeBulkUploadUI.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("YoutubeBulkUploadUI.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Copy new video settings from last video on grid? 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | YouTube Bulk Upload UI - Lazy Edition 4 | 5 | 6 |

YouTube Bulk Upload UI - Lazy Edition

7 | 8 |

A simple tool for uploading multiple videos to your YouTube channel.

9 | 10 |

Table of Contents

11 | 19 | 20 |

Introduction

21 | 22 |

I needed something to help me upload my vacation videos, so I wrote this app and now I'm sharing it with you.

23 |

I also had the bad experience of paying for a similar application, which the developers later broke, then demanded I pay for the new version to continue using. Hopefully this will help others avoid this kind of inconvenience.

24 | 25 | App screenshot 26 | 27 |

How to Use

28 |

Requires .NET 4.6.1. Download the latest release from here: https://github.com/staafl/youtube-bulk-upload-ui/releases/latest

29 |

No installation needed. Start the application and you'll be prompted to provide access to your YouTube account in order to upload videos.

30 |

Afterwards, you can drag videos files to the main grid of the application and edit their details. When you're finished, just hit "Upload" and the app will do its job.

31 |

To remove videos from the grid, select the lines on the grid and hit "Delete".

32 |

The title and description fields support the following placeholders: %f - file name, %i - order of file on the grid, %c - total number of files on the grid (so having five videos with title "%f - part %i/%c" will upload them with titles like "my video - part 2/5")

33 |

During upload, the application writes two files: upload-list.log, which lists the titles of the uploaded videos and their URLs, and upload.csv, which contains more details and can be opened in Excel. Copy these files if you want to keep them for reference. When uploading finishes, the application will automatically open upload-list.log. 34 |

This app should work on MacOS or Linux with Mono.

35 |

Planned Features

36 | 48 |

Privacy Policy

49 |

This app doesn't gather or publish ANY data from the user's machine other than to YouTube's servers for the purpose of uploading videos.

50 |

This app's use of information received from Google APIs will adhere to the Google API Services User Data Policy, including the Limited Use requirements.

51 |

Building

52 |

Download the code here: https://github.com/staafl/youtube-bulk-upload-ui

53 |

To build the project you'll need to provide your own client_secret.json from Google Cloud, since if I publish the client secret used in the binary, Google will likely revoke it. Protecting the client secret is also the reason why the published release is obfuscated. Just paste the JSON file in a static class ClientSecret with a string constant.

54 |

Donations

55 |

If this tool helps you, consider sending a small donation to a charity of your choice and dropping me a line. You'll totally make my day. You can also donate a few bucks through me, in which case 100% of your donation will be forwarded to a children or animal shelter.

56 |

Donate

57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube Bulk Upload UI - Lazy Edition 2 | 3 | ## IMPORTANT 4 | 5 | It seems the Google has removed the application's registration for their own inscrutable reasons. For the moment the app won't work as is and I don't have time to update it. Feel free to rebuild it with your own client-secret.json etc. 6 | 7 | 8 | 9 | A simple tool for uploading multiple videos to your YouTube channel. 10 | 11 | I needed something to help me upload my vacation videos and now I'm sharing it with you. 12 | 13 | I also had the bad experience of paying for a similar application, which the developers later broke, then demanded I pay for the new version to continue using. Hopefully this will help others avoid this kind of inconvenience. 14 | 15 | ![app screenshot](https://github.com/staafl/youtube-bulk-upload-ui/blob/main/screenshot.png?raw=true) 16 | 17 | ## How to Use 18 | 19 | Requires .NET 4.6.1. Download the latest release from here: https://github.com/staafl/youtube-bulk-upload-ui/releases/latest 20 | 21 | No installation needed. Start the application and you'll be prompted to provide access to your YouTube account in order to upload videos. The login data is stored in the %APPDATA%\YouTube.Auth.Store folder so you can delete it and run the app again to log in with another user. 22 | 23 | NB: As of 2021-07-22, Google Cloud API verification is still ongoing for the application and you'll see a "Google hasn't verified this app" screen on the consent page. You'll need to click on "Advanced" and click on the "Go to Youtube Bulk Upload UI (unsafe)" link if you want to use it. Sorry about that, but the validation process has a lot of details that need to be tweaked and a lot of back and forth over email, and I'm busy so the process is progressing slowly. You're welcome to wait a few weeks instead until verification is hopefully officially complete. 24 | 25 | After logging in, you can drag videos files to the main grid of the application and edit their details. When you're finished, just hit "Upload" and the app will do its job. 26 | 27 | To remove videos from the grid, select the lines on the grid and hit "Delete". 28 | 29 | The title and description fields support the following placeholders: %f - file name, %i - order of file on the grid, %c - total number of files on the grid (so having five videos with title "%f - part %i/%c" will upload them with titles like "my video - part 2/5") 30 | 31 | During upload, the application writes two files: upload-list.log, which lists the titles of the uploaded videos and their URLs, and upload.csv, which contains more details and can be opened in Excel. Copy these files if you want to keep them for reference. When uploading finishes, the application will automatically open upload-list.log. 32 | 33 | This app should work on MacOS or Linux with Mono. 34 | 35 | ## Planned Features 36 | 37 | - Reading video EXIF tags to populate title and description automatically or with a pattern supplied by the user. 38 | 39 | - Export / import lists of videos to upload in CSV file format (e.g. for compatibility with Excel). 40 | 41 | - Resuming failed uploads. 42 | 43 | - Notify user when upload is complete, e.g. via push notification or by running a shell task. 44 | 45 | - Integrate with ffmpeg for automatic stabilization etc. 46 | 47 | ## Privacy Policy 48 | 49 | This app doesn't gather or publish ANY data from the user's machine other than to YouTube's servers for the purpose of uploading videos. 50 | 51 | This app's use of information received from Google APIs will adhere to the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy#additional_requirements_for_specific_api_scopes), including the Limited Use requirements. 52 | 53 | ## Building 54 | 55 | Download the code here: [https://github.com/staafl/youtube-bulk-upload-ui](https://github.com/staafl/youtube-bulk-upload-ui) 56 | 57 | To build the project you'll need to provide your own client_secret.json from Google Cloud, since if I publish the client secret used in the binary, Google will likely revoke it. Protecting the client secret is also the reason why the published release is obfuscated. Just paste the JSON file in a static class ClientSecret with a string constant. 58 | 59 | ## Donations 60 | 61 | If this tool helps you, consider sending a small donation to a charity of your choice and dropping me a line. You'll totally make my day. You can also donate a few bucks through me, in which case 100% of your donation will be forwarded to a children or animal shelter. 62 | 63 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=F7GH776DZEFNU) 64 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/YoutubeBulkUploadUI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {BA15CC42-AD77-4D9C-B857-4D0495F5067A} 9 | WinExe 10 | YoutubeBulkUploadUI 11 | YoutubeBulkUploadUI 12 | v4.6.1 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | true 17 | 18 | 19 | 20 | 21 | AnyCPU 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | ..\packages\Google.Apis.1.51.0\lib\net45\Google.Apis.dll 42 | 43 | 44 | ..\packages\Google.Apis.Auth.1.51.0\lib\net45\Google.Apis.Auth.dll 45 | 46 | 47 | ..\packages\Google.Apis.Auth.1.51.0\lib\net45\Google.Apis.Auth.PlatformServices.dll 48 | 49 | 50 | ..\packages\Google.Apis.Core.1.51.0\lib\net45\Google.Apis.Core.dll 51 | 52 | 53 | ..\packages\Google.Apis.1.51.0\lib\net45\Google.Apis.PlatformServices.dll 54 | 55 | 56 | ..\packages\Google.Apis.YouTube.v3.1.51.0.2238\lib\net45\Google.Apis.YouTube.v3.dll 57 | 58 | 59 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 4.0 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | MSBuild:Compile 79 | Designer 80 | 81 | 82 | 83 | 84 | MSBuild:Compile 85 | Designer 86 | 87 | 88 | App.xaml 89 | Code 90 | 91 | 92 | MainWindow.xaml 93 | Code 94 | 95 | 96 | 97 | 98 | Code 99 | 100 | 101 | True 102 | True 103 | Resources.resx 104 | 105 | 106 | True 107 | Settings.settings 108 | True 109 | 110 | 111 | ResXFileCodeGenerator 112 | Resources.Designer.cs 113 | 114 | 115 | 116 | SettingsSingleFileGenerator 117 | Settings.Designer.cs 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | Always 126 | 127 | 128 | 129 | 130 | "$(Obfuscar)" $(ProjectDir)obfuscar.xml 131 | 132 | 133 | 134 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ClientSecret.cs 2 | client_secret*.json 3 | 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.rsuser 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Mono auto generated files 21 | mono_crash.* 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Dd]ebugPublic/ 26 | [Rr]elease/ 27 | [Rr]eleases/ 28 | x64/ 29 | x86/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Visual Studio code coverage results 145 | *.coverage 146 | *.coveragexml 147 | 148 | # NCrunch 149 | _NCrunch_* 150 | .*crunch*.local.xml 151 | nCrunchTemp_* 152 | 153 | # MightyMoose 154 | *.mm.* 155 | AutoTest.Net/ 156 | 157 | # Web workbench (sass) 158 | .sass-cache/ 159 | 160 | # Installshield output folder 161 | [Ee]xpress/ 162 | 163 | # DocProject is a documentation generator add-in 164 | DocProject/buildhelp/ 165 | DocProject/Help/*.HxT 166 | DocProject/Help/*.HxC 167 | DocProject/Help/*.hhc 168 | DocProject/Help/*.hhk 169 | DocProject/Help/*.hhp 170 | DocProject/Help/Html2 171 | DocProject/Help/html 172 | 173 | # Click-Once directory 174 | publish/ 175 | 176 | # Publish Web Output 177 | *.[Pp]ublish.xml 178 | *.azurePubxml 179 | # Note: Comment the next line if you want to checkin your web deploy settings, 180 | # but database connection strings (with potential passwords) will be unencrypted 181 | *.pubxml 182 | *.publishproj 183 | 184 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 185 | # checkin your Azure Web App publish settings, but sensitive information contained 186 | # in these scripts will be unencrypted 187 | PublishScripts/ 188 | 189 | # NuGet Packages 190 | *.nupkg 191 | # NuGet Symbol Packages 192 | *.snupkg 193 | # The packages folder can be ignored because of Package Restore 194 | **/[Pp]ackages/* 195 | # except build/, which is used as an MSBuild target. 196 | !**/[Pp]ackages/build/ 197 | # Uncomment if necessary however generally it will be regenerated when needed 198 | #!**/[Pp]ackages/repositories.config 199 | # NuGet v3's project.json files produces more ignorable files 200 | *.nuget.props 201 | *.nuget.targets 202 | 203 | # Microsoft Azure Build Output 204 | csx/ 205 | *.build.csdef 206 | 207 | # Microsoft Azure Emulator 208 | ecf/ 209 | rcf/ 210 | 211 | # Windows Store app package directories and files 212 | AppPackages/ 213 | BundleArtifacts/ 214 | Package.StoreAssociation.xml 215 | _pkginfo.txt 216 | *.appx 217 | *.appxbundle 218 | *.appxupload 219 | 220 | # Visual Studio cache files 221 | # files ending in .cache can be ignored 222 | *.[Cc]ache 223 | # but keep track of directories ending in .cache 224 | !?*.[Cc]ache/ 225 | 226 | # Others 227 | ClientBin/ 228 | ~$* 229 | *~ 230 | *.dbmdl 231 | *.dbproj.schemaview 232 | *.jfm 233 | *.pfx 234 | *.publishsettings 235 | orleans.codegen.cs 236 | 237 | # Including strong name files can present a security risk 238 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 239 | #*.snk 240 | 241 | # Since there are multiple workflows, uncomment next line to ignore bower_components 242 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 243 | #bower_components/ 244 | 245 | # RIA/Silverlight projects 246 | Generated_Code/ 247 | 248 | # Backup & report files from converting an old project file 249 | # to a newer Visual Studio version. Backup files are not needed, 250 | # because we have git ;-) 251 | _UpgradeReport_Files/ 252 | Backup*/ 253 | UpgradeLog*.XML 254 | UpgradeLog*.htm 255 | ServiceFabricBackup/ 256 | *.rptproj.bak 257 | 258 | # SQL Server files 259 | *.mdf 260 | *.ldf 261 | *.ndf 262 | 263 | # Business Intelligence projects 264 | *.rdl.data 265 | *.bim.layout 266 | *.bim_*.settings 267 | *.rptproj.rsuser 268 | *- [Bb]ackup.rdl 269 | *- [Bb]ackup ([0-9]).rdl 270 | *- [Bb]ackup ([0-9][0-9]).rdl 271 | 272 | # Microsoft Fakes 273 | FakesAssemblies/ 274 | 275 | # GhostDoc plugin setting file 276 | *.GhostDoc.xml 277 | 278 | # Node.js Tools for Visual Studio 279 | .ntvs_analysis.dat 280 | node_modules/ 281 | 282 | # Visual Studio 6 build log 283 | *.plg 284 | 285 | # Visual Studio 6 workspace options file 286 | *.opt 287 | 288 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 289 | *.vbw 290 | 291 | # Visual Studio LightSwitch build output 292 | **/*.HTMLClient/GeneratedArtifacts 293 | **/*.DesktopClient/GeneratedArtifacts 294 | **/*.DesktopClient/ModelManifest.xml 295 | **/*.Server/GeneratedArtifacts 296 | **/*.Server/ModelManifest.xml 297 | _Pvt_Extensions 298 | 299 | # Paket dependency manager 300 | .paket/paket.exe 301 | paket-files/ 302 | 303 | # FAKE - F# Make 304 | .fake/ 305 | 306 | # CodeRush personal settings 307 | .cr/personal 308 | 309 | # Python Tools for Visual Studio (PTVS) 310 | __pycache__/ 311 | *.pyc 312 | 313 | # Cake - Uncomment if you are using it 314 | # tools/** 315 | # !tools/packages.config 316 | 317 | # Tabs Studio 318 | *.tss 319 | 320 | # Telerik's JustMock configuration file 321 | *.jmconfig 322 | 323 | # BizTalk build output 324 | *.btp.cs 325 | *.btm.cs 326 | *.odx.cs 327 | *.xsd.cs 328 | 329 | # OpenCover UI analysis results 330 | OpenCover/ 331 | 332 | # Azure Stream Analytics local run output 333 | ASALocalRun/ 334 | 335 | # MSBuild Binary and Structured Log 336 | *.binlog 337 | 338 | # NVidia Nsight GPU debugger configuration file 339 | *.nvuser 340 | 341 | # MFractors (Xamarin productivity tool) working folder 342 | .mfractor/ 343 | 344 | # Local History for Visual Studio 345 | .localhistory/ 346 | 347 | # BeatPulse healthcheck temp database 348 | healthchecksdb 349 | 350 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 351 | MigrationBackup/ 352 | 353 | # Ionide (cross platform F# VS Code tools) working folder 354 | .ionide/ 355 | -------------------------------------------------------------------------------- /categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "youtube#videoCategoryListResponse", 3 | "etag": "QteLrrS_X7rM7rlcU_e7qa0embQ", 4 | "items": [ 5 | { 6 | "kind": "youtube#videoCategory", 7 | "etag": "grPOPYEUUZN3ltuDUGEWlrTR90U", 8 | "id": "1", 9 | "snippet": { 10 | "title": "Film & Animation", 11 | "assignable": true, 12 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 13 | } 14 | }, 15 | { 16 | "kind": "youtube#videoCategory", 17 | "etag": "Q0xgUf8BFM8rW3W0R9wNq809xyA", 18 | "id": "2", 19 | "snippet": { 20 | "title": "Autos & Vehicles", 21 | "assignable": true, 22 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 23 | } 24 | }, 25 | { 26 | "kind": "youtube#videoCategory", 27 | "etag": "qnpwjh5QlWM5hrnZCvHisquztC4", 28 | "id": "10", 29 | "snippet": { 30 | "title": "Music", 31 | "assignable": true, 32 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 33 | } 34 | }, 35 | { 36 | "kind": "youtube#videoCategory", 37 | "etag": "HyFIixS5BZaoBdkQdLzPdoXWipg", 38 | "id": "15", 39 | "snippet": { 40 | "title": "Pets & Animals", 41 | "assignable": true, 42 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 43 | } 44 | }, 45 | { 46 | "kind": "youtube#videoCategory", 47 | "etag": "PNU8SwXhjsF90fmkilVohofOi4I", 48 | "id": "17", 49 | "snippet": { 50 | "title": "Sports", 51 | "assignable": true, 52 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 53 | } 54 | }, 55 | { 56 | "kind": "youtube#videoCategory", 57 | "etag": "5kFljz9YJ4lEgSfVwHWi5kTAwAs", 58 | "id": "18", 59 | "snippet": { 60 | "title": "Short Movies", 61 | "assignable": false, 62 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 63 | } 64 | }, 65 | { 66 | "kind": "youtube#videoCategory", 67 | "etag": "ANnLQyzEA_9m3bMyJXMhKTCOiyg", 68 | "id": "19", 69 | "snippet": { 70 | "title": "Travel & Events", 71 | "assignable": true, 72 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 73 | } 74 | }, 75 | { 76 | "kind": "youtube#videoCategory", 77 | "etag": "0Hh6gbZ9zWjnV3sfdZjKB5LQr6E", 78 | "id": "20", 79 | "snippet": { 80 | "title": "Gaming", 81 | "assignable": true, 82 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 83 | } 84 | }, 85 | { 86 | "kind": "youtube#videoCategory", 87 | "etag": "q8Cp4pUfCD8Fuh8VJ_yl5cBCVNw", 88 | "id": "21", 89 | "snippet": { 90 | "title": "Videoblogging", 91 | "assignable": false, 92 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 93 | } 94 | }, 95 | { 96 | "kind": "youtube#videoCategory", 97 | "etag": "cHDaaqPDZsJT1FPr1-MwtyIhR28", 98 | "id": "22", 99 | "snippet": { 100 | "title": "People & Blogs", 101 | "assignable": true, 102 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 103 | } 104 | }, 105 | { 106 | "kind": "youtube#videoCategory", 107 | "etag": "3Uz364xBbKY50a2s0XQlv-gXJds", 108 | "id": "23", 109 | "snippet": { 110 | "title": "Comedy", 111 | "assignable": true, 112 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 113 | } 114 | }, 115 | { 116 | "kind": "youtube#videoCategory", 117 | "etag": "0srcLUqQzO7-NGLF7QnhdVzJQmY", 118 | "id": "24", 119 | "snippet": { 120 | "title": "Entertainment", 121 | "assignable": true, 122 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 123 | } 124 | }, 125 | { 126 | "kind": "youtube#videoCategory", 127 | "etag": "bQlQMjmYX7DyFkX4w3kT0osJyIc", 128 | "id": "25", 129 | "snippet": { 130 | "title": "News & Politics", 131 | "assignable": true, 132 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 133 | } 134 | }, 135 | { 136 | "kind": "youtube#videoCategory", 137 | "etag": "Y06N41HP_WlZmeREZvkGF0HW5pg", 138 | "id": "26", 139 | "snippet": { 140 | "title": "Howto & Style", 141 | "assignable": true, 142 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 143 | } 144 | }, 145 | { 146 | "kind": "youtube#videoCategory", 147 | "etag": "yBaNkLx4sX9NcDmFgAmxQcV4Y30", 148 | "id": "27", 149 | "snippet": { 150 | "title": "Education", 151 | "assignable": true, 152 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 153 | } 154 | }, 155 | { 156 | "kind": "youtube#videoCategory", 157 | "etag": "Mxy3A-SkmnR7MhJDZRS4DuAIbQA", 158 | "id": "28", 159 | "snippet": { 160 | "title": "Science & Technology", 161 | "assignable": true, 162 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 163 | } 164 | }, 165 | { 166 | "kind": "youtube#videoCategory", 167 | "etag": "p3lEirEJApyEkuWpaGEHoF-m-aA", 168 | "id": "29", 169 | "snippet": { 170 | "title": "Nonprofits & Activism", 171 | "assignable": true, 172 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 173 | } 174 | }, 175 | { 176 | "kind": "youtube#videoCategory", 177 | "etag": "4pIHL_AdN2kO7btAGAP1TvPucNk", 178 | "id": "30", 179 | "snippet": { 180 | "title": "Movies", 181 | "assignable": false, 182 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 183 | } 184 | }, 185 | { 186 | "kind": "youtube#videoCategory", 187 | "etag": "Iqol1myDwh2AuOnxjtn2AfYwJTU", 188 | "id": "31", 189 | "snippet": { 190 | "title": "Anime/Animation", 191 | "assignable": false, 192 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 193 | } 194 | }, 195 | { 196 | "kind": "youtube#videoCategory", 197 | "etag": "tzhBKCBcYWZLPai5INY4id91ss8", 198 | "id": "32", 199 | "snippet": { 200 | "title": "Action/Adventure", 201 | "assignable": false, 202 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 203 | } 204 | }, 205 | { 206 | "kind": "youtube#videoCategory", 207 | "etag": "ii8nBGYpKyl6FyzP3cmBCevdrbs", 208 | "id": "33", 209 | "snippet": { 210 | "title": "Classics", 211 | "assignable": false, 212 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 213 | } 214 | }, 215 | { 216 | "kind": "youtube#videoCategory", 217 | "etag": "Y0u9UAQCCGp60G11Arac5Mp46z4", 218 | "id": "34", 219 | "snippet": { 220 | "title": "Comedy", 221 | "assignable": false, 222 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 223 | } 224 | }, 225 | { 226 | "kind": "youtube#videoCategory", 227 | "etag": "_YDnyT205AMuX8etu8loOiQjbD4", 228 | "id": "35", 229 | "snippet": { 230 | "title": "Documentary", 231 | "assignable": false, 232 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 233 | } 234 | }, 235 | { 236 | "kind": "youtube#videoCategory", 237 | "etag": "eAl2b-uqIGRDgnlMa0EsGZjXmWg", 238 | "id": "36", 239 | "snippet": { 240 | "title": "Drama", 241 | "assignable": false, 242 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 243 | } 244 | }, 245 | { 246 | "kind": "youtube#videoCategory", 247 | "etag": "HDAW2HFOt3SqeDI00X-eL7OELfY", 248 | "id": "37", 249 | "snippet": { 250 | "title": "Family", 251 | "assignable": false, 252 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 253 | } 254 | }, 255 | { 256 | "kind": "youtube#videoCategory", 257 | "etag": "QHiWh3niw5hjDrim85M8IGF45eE", 258 | "id": "38", 259 | "snippet": { 260 | "title": "Foreign", 261 | "assignable": false, 262 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 263 | } 264 | }, 265 | { 266 | "kind": "youtube#videoCategory", 267 | "etag": "ztKcSS7GpH9uEyZk9nQCdNujvGg", 268 | "id": "39", 269 | "snippet": { 270 | "title": "Horror", 271 | "assignable": false, 272 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 273 | } 274 | }, 275 | { 276 | "kind": "youtube#videoCategory", 277 | "etag": "Ids1sm8QFeSo_cDlpcUNrnEBYWA", 278 | "id": "40", 279 | "snippet": { 280 | "title": "Sci-Fi/Fantasy", 281 | "assignable": false, 282 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 283 | } 284 | }, 285 | { 286 | "kind": "youtube#videoCategory", 287 | "etag": "qhfgS7MzzZHIy_UZ1dlawl1GbnY", 288 | "id": "41", 289 | "snippet": { 290 | "title": "Thriller", 291 | "assignable": false, 292 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 293 | } 294 | }, 295 | { 296 | "kind": "youtube#videoCategory", 297 | "etag": "TxVSfGoUyT7CJ7h7ebjg4vhIt6g", 298 | "id": "42", 299 | "snippet": { 300 | "title": "Shorts", 301 | "assignable": false, 302 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 303 | } 304 | }, 305 | { 306 | "kind": "youtube#videoCategory", 307 | "etag": "o9w6eNqzjHPnNbKDujnQd8pklXM", 308 | "id": "43", 309 | "snippet": { 310 | "title": "Shows", 311 | "assignable": false, 312 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 313 | } 314 | }, 315 | { 316 | "kind": "youtube#videoCategory", 317 | "etag": "mLdyKd0VgXKDI6GevTLBAcvRlIU", 318 | "id": "44", 319 | "snippet": { 320 | "title": "Trailers", 321 | "assignable": false, 322 | "channelId": "UCBR8-60-B28hp2BmDPdntcQ" 323 | } 324 | } 325 | ] 326 | } 327 | -------------------------------------------------------------------------------- /YoutubeBulkUploadUI/YoutubeBulkUploadUI/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Google.Apis.Auth.OAuth2; 2 | using Google.Apis.Services; 3 | using Google.Apis.Util.Store; 4 | using Google.Apis.YouTube.v3; 5 | using Google.Apis.YouTube.v3.Data; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Collections.ObjectModel; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Text; 13 | using System.Threading; 14 | using System.Windows; 15 | using System.Windows.Input; 16 | using Google.Apis.Upload; 17 | using System.ComponentModel; 18 | using System.Runtime.InteropServices; 19 | using System.Diagnostics; 20 | using Google; 21 | 22 | namespace YoutubeBulkUploadUI 23 | { 24 | /// 25 | /// Interaction logic for MainWindow.xaml 26 | /// 27 | public partial class MainWindow : Window 28 | { 29 | readonly ObservableCollection filesCollection = new ObservableCollection(); 30 | readonly ObservableCollection categoriesCollection = new ObservableCollection(); 31 | FileStream fileStream; 32 | FileModel currentUpload; 33 | YouTubeService youtubeService; 34 | string categories = @"1,Film & Animation 35 | 2,Autos & Vehicles 36 | 10,Music 37 | 15,Pets & Animals 38 | 17,Sports 39 | 18,Short Movies 40 | 19,Travel & Events 41 | 20,Gaming 42 | 21,Videoblogging 43 | 22,People & Blogs 44 | 23,Comedy 45 | 24,Entertainment 46 | 25,News & Politics 47 | 26,Howto & Style 48 | 27,Education 49 | 28,Science & Technology 50 | 30,Movies 51 | 31,Anime/Animation 52 | 32,Action/Adventure 53 | 33,Classics 54 | 34,Comedy 55 | 35,Documentary 56 | 36,Drama 57 | 37,Family 58 | 38,Foreign 59 | 39,Horror 60 | 40,Sci-Fi/Fantasy 61 | 41,Thriller 62 | 42,Shorts 63 | 43,Shows 64 | 44,Trailers 65 | "; 66 | 67 | class CategoryModel 68 | { 69 | public string Id { get; set; } 70 | public string Name { get; set; } 71 | } 72 | class FileModel : INotifyPropertyChanged 73 | { 74 | string status; 75 | string url; 76 | public string File { get; set; } 77 | public string Status { get { return status; } set { status = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs (nameof(Status))); } } 78 | public string Url { get { return url; } set { url = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs (nameof(Url))); } } 79 | public string Length { get; set; } 80 | public string Title { get; set; } 81 | // todo: get from EXIF tags 82 | public string Description { get; set; } 83 | public VideoVisibility Visibility { get; set; } 84 | public Video Video { get; set; } 85 | public bool MadeForKids { get; set; } 86 | public CategoryModel Category { get; set; } 87 | public string Tags { get; set; } 88 | 89 | public event PropertyChangedEventHandler PropertyChanged; 90 | } 91 | 92 | public MainWindow() 93 | { 94 | InitializeComponent(); 95 | this.DataContext = new 96 | { 97 | Files = filesCollection, 98 | Categories = categoriesCollection 99 | }; 100 | dgv.AllowDrop = true; 101 | dgv.Drop += Dgv_Drop; 102 | 103 | } 104 | 105 | protected override void OnKeyUp(KeyEventArgs e) 106 | { 107 | if (e.Key == Key.F1) 108 | { 109 | Process.Start("http://trustingwolves.com/youtube-bulk-upload/index.html#how-to-use"); 110 | } 111 | base.OnKeyUp(e); 112 | } 113 | 114 | protected async override void OnContentRendered(EventArgs e) 115 | { 116 | base.OnContentRendered(e); 117 | 118 | UserCredential credential; 119 | using (var stream = 120 | (ClientSecret.clientSecret != null ? new MemoryStream(Encoding.UTF8.GetBytes(ClientSecret.clientSecret)) : null) ?? 121 | Assembly.GetExecutingAssembly().GetManifestResourceStream("YoutubeBulkUploadUI.client_secret.json") 122 | ?? (Stream)new FileStream("client_secret.json", FileMode.Open, FileAccess.Read)) 123 | { 124 | credential = GoogleWebAuthorizationBroker.AuthorizeAsync( 125 | GoogleClientSecrets.Load(stream).Secrets, 126 | new[] { YouTubeService.Scope.Youtube, YouTubeService.Scope.YoutubeUpload }, 127 | "user", 128 | CancellationToken.None, 129 | new FileDataStore("YouTube.Auth.Store")).Result; 130 | } 131 | 132 | youtubeService = new YouTubeService( 133 | new BaseClientService.Initializer() 134 | { 135 | HttpClientInitializer = credential, 136 | ApplicationName = Assembly.GetExecutingAssembly().GetName().Name 137 | }); 138 | 139 | // https://developers.google.com/youtube/v3/docs/videoCategories/list?apix_params=%7B%22part%22%3A%5B%22snippet%22%5D%2C%22regionCode%22%3A%22us%22%7D 140 | categoriesCollection.Add(null); 141 | //if (File.Exists("categories.txt")) 142 | { 143 | foreach (var line in categories.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) //File.ReadAllLines("categories.txt")) 144 | { 145 | var split = line.Split(new[] { ',' }, 2); 146 | categoriesCollection.Add( 147 | new CategoryModel 148 | { 149 | Id = split[0], 150 | Name = split[1] 151 | }); 152 | } 153 | } 154 | /*else 155 | { 156 | string regionName; 157 | try 158 | { 159 | string info = new WebClient().DownloadString("http://ipinfo.io"); 160 | regionName = Regex.Match(info, "\"country\": *\"([^\"]+)\"").Groups[1].Value; 161 | } 162 | catch 163 | { 164 | regionName = "us"; 165 | } 166 | 167 | //var ipInfo = jsonObject.Deserialize(info); 168 | 169 | RegionInfo region = new RegionInfo(regionName); 170 | 171 | 172 | var categoriesRequest = youtubeService 173 | .VideoCategories 174 | .List("snippet"); 175 | categoriesRequest.RegionCode = region.Name.ToLower(); 176 | 177 | var categories = await categoriesRequest.ExecuteAsync(); 178 | foreach (var category in categories.Items) 179 | { 180 | categoriesCollection.Add( 181 | new CategoryModel 182 | { 183 | Id = category.Id, 184 | Name = category.Snippet.Title 185 | }); 186 | File.AppendAllLines("categories.txt", new[] { category.Id + "," + category.Snippet.Title }); 187 | } 188 | }*/ 189 | 190 | } 191 | 192 | public class IpInfo 193 | { 194 | //country 195 | public string Country { get; set; } 196 | } 197 | 198 | private void videosInsertRequest_ResponseReceived(Video obj) 199 | { 200 | var file = filesCollection.FirstOrDefault(x => x.Title == obj.Snippet.Title); 201 | string error = 202 | obj.ProcessingDetails?.ProcessingFailureReason ?? 203 | obj.Status?.RejectionReason ?? 204 | obj.Status.FailureReason; 205 | if (error != null) 206 | { 207 | file.Status = "Error"; 208 | file.Url = error; 209 | } 210 | else if (obj.Status?.UploadStatus != null) 211 | { 212 | file.Status = obj.Status.UploadStatus; 213 | if (obj.Id != null) 214 | { 215 | file.Url = "https://youtube.com/watch?v=" + obj.Id; 216 | } 217 | } 218 | } 219 | 220 | private void videosInsertRequest_ProgressChanged(IUploadProgress obj) 221 | { 222 | this.Dispatcher.BeginInvoke(new Action(() => 223 | { 224 | try 225 | { 226 | progress.Value = (int)(100 * (obj.BytesSent / (double)fileStream.Length)); 227 | } 228 | catch (ObjectDisposedException) 229 | { 230 | progress.Value = 100; 231 | } 232 | })); 233 | } 234 | 235 | [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] 236 | private static extern int StrCmpLogicalW(string psz1, string psz2); 237 | class StrCmpLogicalWComparer : IComparer 238 | { 239 | public int Compare(string x, string y) 240 | { 241 | return StrCmpLogicalW(x, y); 242 | } 243 | } 244 | 245 | private void Dgv_Drop(object sender, DragEventArgs e) 246 | { 247 | // Shell IDList Array;DragImageBits;DragContext;DragSourceHelperFlags;InShellDragLoop;FileDrop;FileNameW;FileName 248 | 249 | if (e.Data.GetDataPresent("FileDrop")) 250 | { 251 | var files = (e.Data.GetData("FileDrop") as string[]); 252 | Array.Sort(files, new StrCmpLogicalWComparer()); 253 | 254 | foreach (var file in files) 255 | { 256 | var model = new FileModel 257 | { 258 | File = file, 259 | Status = "Pending", 260 | Visibility = VideoVisibility.Unlisted, 261 | Title = "%f", //System.IO.Path.GetFileNameWithoutExtension(file), 262 | Category = categoriesCollection.First(), 263 | Description = "" 264 | }; 265 | if (copy.IsChecked == true && filesCollection.Any()) 266 | { 267 | var last = filesCollection.Last(); 268 | model.Visibility = last.Visibility; 269 | model.Title = last.Title; 270 | model.Description = last.Description; 271 | model.Category = last.Category; 272 | model.MadeForKids = last.MadeForKids; 273 | model.Tags = last.Tags; 274 | } 275 | filesCollection.Add(model); 276 | } 277 | } 278 | } 279 | 280 | private async void but_upload_Click(object sender, RoutedEventArgs e) 281 | { 282 | but_upload.IsEnabled = false; 283 | int ii = 0; 284 | foreach (var file in filesCollection) 285 | { 286 | currentUpload = file; 287 | file.Status = "Uploading..."; 288 | ii += 1; 289 | label.Content = "Uploading video " + ii + ": " + System.IO.Path.GetFileName(file.File); 290 | var filePath = file.File; 291 | var video = new Video(); 292 | video.Snippet = new VideoSnippet(); 293 | video.Snippet.Title = file.Title = ReplacePatterns(file, file.Title, ii); 294 | video.Snippet.Description = file.Description = ReplacePatterns(file, file.Description, ii); 295 | video.Snippet.Tags = file.Tags?.Split(new[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries); 296 | video.Snippet.CategoryId = file.Category?.Id; // See https://developers.google.com/youtube/v3/docs/videoCategories/list 297 | video.Status = new VideoStatus(); 298 | video.Status.PrivacyStatus = (file.Visibility + "").ToLower(); 299 | video.Status.MadeForKids = file.MadeForKids; 300 | file.Video = video; 301 | using (fileStream = new FileStream(filePath, FileMode.Open)) 302 | { 303 | var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*"); 304 | videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged; 305 | videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived; 306 | var uploadProgress = await videosInsertRequest.UploadAsync(); 307 | file.Status = uploadProgress.Status.ToString(); 308 | if (uploadProgress.Exception != null) 309 | { 310 | file.Url = (uploadProgress.Exception as GoogleApiException)?.Error?.Message ?? uploadProgress.Exception.Message; 311 | } 312 | //file.Status = "Uploaded"; 313 | // TODO: are we finished at this point? 314 | progress.Value = 100; 315 | } 316 | } 317 | label.Content = "Done!"; 318 | 319 | if (!File.Exists("upload.csv")) 320 | { 321 | File.WriteAllText("upload.csv", "File,Status,Result,Title,Description,Category,Tags,Made for Kids\n"); 322 | } 323 | using (var sw = new StreamWriter("upload.csv", append: true)) 324 | { 325 | foreach (var file in filesCollection) 326 | { 327 | var csvLine = new[]{ 328 | file.File, 329 | file.Status, 330 | file.Url, 331 | file.Title, 332 | file.Description, 333 | file.Category?.Name, 334 | string.Join(", ", file.Tags), 335 | file.MadeForKids + "" 336 | }.Select(x => (x + "").Replace("\"", "\"\"").Replace("\r", "").Replace("\n", " ")); 337 | sw.WriteLine("\"" + string.Join("\",\"", csvLine) + "\""); 338 | } 339 | } 340 | using (var sw = new StreamWriter("upload-list.log")) 341 | { 342 | foreach (var file in filesCollection) 343 | { 344 | sw.WriteLine(file.Title + ": " + file.Url); 345 | } 346 | } 347 | Process.Start("notepad.exe", "upload-list.log"); 348 | but_upload.IsEnabled = true; 349 | } 350 | 351 | private string ReplacePatterns(FileModel model, string what, int index) 352 | { 353 | return what 354 | .Replace("%i", index + "") 355 | .Replace("%c", filesCollection.Count + "") 356 | .Replace("%f", System.IO.Path.GetFileNameWithoutExtension(model.File)); 357 | } 358 | } 359 | } 360 | --------------------------------------------------------------------------------