diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..d447dc9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://paypal.me/mateuszskoczek'] \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e47a59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,389 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ +[Pp]roperties/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Nuget personal access tokens and Credentials +nuget.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +.idea/ +*.sln.iml \ No newline at end of file diff --git a/VDownload.sln b/VDownload.sln new file mode 100644 index 0000000..66d5080 --- /dev/null +++ b/VDownload.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VDownload", "VDownload\VDownload.csproj", "{324AB81A-F68D-424D-B90E-5402F5E44240}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A087406C-5459-423E-BB0B-84BC93E5B38F}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|ARM.ActiveCfg = Debug|ARM + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|ARM.Build.0 = Debug|ARM + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|ARM.Deploy.0 = Debug|ARM + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|ARM64.Build.0 = Debug|ARM64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|x64.ActiveCfg = Debug|x64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|x64.Build.0 = Debug|x64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|x64.Deploy.0 = Debug|x64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|x86.ActiveCfg = Debug|x86 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|x86.Build.0 = Debug|x86 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Debug|x86.Deploy.0 = Debug|x86 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|ARM.ActiveCfg = Release|ARM + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|ARM.Build.0 = Release|ARM + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|ARM.Deploy.0 = Release|ARM + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|ARM64.ActiveCfg = Release|ARM64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|ARM64.Build.0 = Release|ARM64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|ARM64.Deploy.0 = Release|ARM64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|x64.ActiveCfg = Release|x64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|x64.Build.0 = Release|x64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|x64.Deploy.0 = Release|x64 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|x86.ActiveCfg = Release|x86 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|x86.Build.0 = Release|x86 + {324AB81A-F68D-424D-B90E-5402F5E44240}.Release|x86.Deploy.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {878070E9-B074-4931-94AC-8BACD6906448} + EndGlobalSection +EndGlobal diff --git a/VDownload/App.xaml b/VDownload/App.xaml new file mode 100644 index 0000000..e1d3280 --- /dev/null +++ b/VDownload/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/VDownload/App.xaml.cs b/VDownload/App.xaml.cs new file mode 100644 index 0000000..8e53bc2 --- /dev/null +++ b/VDownload/App.xaml.cs @@ -0,0 +1,78 @@ +// Internal +using VDownload.Services; + +// System +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Storage; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; + +namespace VDownload +{ + sealed partial class App : Application + { + public App() + { + InitializeComponent(); + Suspending += OnSuspending; + } + + protected override async void OnLaunched(LaunchActivatedEventArgs e) + { + // Rebuild configuration file + Config.Rebuild(); + + // Delete temp on start + if (Config.GetValue("delete_temp_on_start") == "1") + { + IReadOnlyList tempItems = await ApplicationData.Current.TemporaryFolder.GetItemsAsync(); + List tasks = new List(); + foreach (IStorageItem item in tempItems) tasks.Add(item.DeleteAsync().AsTask()); + await Task.WhenAll(tasks); + } + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (!(Window.Current.Content is Frame rootFrame)) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + rootFrame.NavigationFailed += OnNavigationFailed; + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + if (e.PrelaunchActivated == false) + { + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(typeof(MainPage), e.Arguments); + } + + // Ensure the current window is active + Window.Current.Activate(); + } + } + + private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + //TODO: Save application state and stop any background activity + deferral.Complete(); + } + } +} diff --git a/VDownload/Assets/Icons/AddVideo/NotFound.png b/VDownload/Assets/Icons/AddVideo/NotFound.png new file mode 100644 index 0000000..38e14ac Binary files /dev/null and b/VDownload/Assets/Icons/AddVideo/NotFound.png differ diff --git a/VDownload/Assets/Icons/AddVideo/Start.png b/VDownload/Assets/Icons/AddVideo/Start.png new file mode 100644 index 0000000..8c94413 Binary files /dev/null and b/VDownload/Assets/Icons/AddVideo/Start.png differ diff --git a/VDownload/Assets/Icons/Sources/Twitch.png b/VDownload/Assets/Icons/Sources/Twitch.png new file mode 100644 index 0000000..547fa37 Binary files /dev/null and b/VDownload/Assets/Icons/Sources/Twitch.png differ diff --git a/VDownload/Assets/Icons/Sources/Unknown.png b/VDownload/Assets/Icons/Sources/Unknown.png new file mode 100644 index 0000000..2d965e4 Binary files /dev/null and b/VDownload/Assets/Icons/Sources/Unknown.png differ diff --git a/VDownload/Assets/Icons/Sources/Youtube.png b/VDownload/Assets/Icons/Sources/Youtube.png new file mode 100644 index 0000000..2c8282b Binary files /dev/null and b/VDownload/Assets/Icons/Sources/Youtube.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Author.png b/VDownload/Assets/Icons/Universal/Dark/Author.png new file mode 100644 index 0000000..1520596 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Author.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Cancelled.png b/VDownload/Assets/Icons/Universal/Dark/Cancelled.png new file mode 100644 index 0000000..94179cf Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Cancelled.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Date.png b/VDownload/Assets/Icons/Universal/Dark/Date.png new file mode 100644 index 0000000..ad98de8 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Date.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Done.png b/VDownload/Assets/Icons/Universal/Dark/Done.png new file mode 100644 index 0000000..7fde7fa Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Done.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Downloading.png b/VDownload/Assets/Icons/Universal/Dark/Downloading.png new file mode 100644 index 0000000..28a207a Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Downloading.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Duration.png b/VDownload/Assets/Icons/Universal/Dark/Duration.png new file mode 100644 index 0000000..1241f21 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Duration.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Error.png b/VDownload/Assets/Icons/Universal/Dark/Error.png new file mode 100644 index 0000000..731fa5e Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Error.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Finalizing.png b/VDownload/Assets/Icons/Universal/Dark/Finalizing.png new file mode 100644 index 0000000..f0f2e64 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Finalizing.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Idle.png b/VDownload/Assets/Icons/Universal/Dark/Idle.png new file mode 100644 index 0000000..f17c6cd Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Idle.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/MediaType.png b/VDownload/Assets/Icons/Universal/Dark/MediaType.png new file mode 100644 index 0000000..a6716de Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/MediaType.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Path.png b/VDownload/Assets/Icons/Universal/Dark/Path.png new file mode 100644 index 0000000..47832bc Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Path.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Quality.png b/VDownload/Assets/Icons/Universal/Dark/Quality.png new file mode 100644 index 0000000..272124f Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Quality.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Transcoding.png b/VDownload/Assets/Icons/Universal/Dark/Transcoding.png new file mode 100644 index 0000000..c73c67b Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Transcoding.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Trim.png b/VDownload/Assets/Icons/Universal/Dark/Trim.png new file mode 100644 index 0000000..9a8c828 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Trim.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Views.png b/VDownload/Assets/Icons/Universal/Dark/Views.png new file mode 100644 index 0000000..1541196 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Views.png differ diff --git a/VDownload/Assets/Icons/Universal/Dark/Waiting.png b/VDownload/Assets/Icons/Universal/Dark/Waiting.png new file mode 100644 index 0000000..91e00a7 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Dark/Waiting.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Author.png b/VDownload/Assets/Icons/Universal/Light/Author.png new file mode 100644 index 0000000..c0b94c4 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Author.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Cancelled.png b/VDownload/Assets/Icons/Universal/Light/Cancelled.png new file mode 100644 index 0000000..d12aaa4 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Cancelled.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Date.png b/VDownload/Assets/Icons/Universal/Light/Date.png new file mode 100644 index 0000000..4dd74af Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Date.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Done.png b/VDownload/Assets/Icons/Universal/Light/Done.png new file mode 100644 index 0000000..c6420c2 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Done.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Downloading.png b/VDownload/Assets/Icons/Universal/Light/Downloading.png new file mode 100644 index 0000000..a3cd4d4 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Downloading.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Duration.png b/VDownload/Assets/Icons/Universal/Light/Duration.png new file mode 100644 index 0000000..ba92311 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Duration.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Error.png b/VDownload/Assets/Icons/Universal/Light/Error.png new file mode 100644 index 0000000..4d7a52c Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Error.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Finalizing.png b/VDownload/Assets/Icons/Universal/Light/Finalizing.png new file mode 100644 index 0000000..72c0fb4 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Finalizing.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Idle.png b/VDownload/Assets/Icons/Universal/Light/Idle.png new file mode 100644 index 0000000..08a09c1 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Idle.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/MediaType.png b/VDownload/Assets/Icons/Universal/Light/MediaType.png new file mode 100644 index 0000000..74f446c Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/MediaType.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Path.png b/VDownload/Assets/Icons/Universal/Light/Path.png new file mode 100644 index 0000000..0b06bc3 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Path.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Quality.png b/VDownload/Assets/Icons/Universal/Light/Quality.png new file mode 100644 index 0000000..2caf368 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Quality.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Transcoding.png b/VDownload/Assets/Icons/Universal/Light/Transcoding.png new file mode 100644 index 0000000..c2ffd89 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Transcoding.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Trim.png b/VDownload/Assets/Icons/Universal/Light/Trim.png new file mode 100644 index 0000000..6692db7 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Trim.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Views.png b/VDownload/Assets/Icons/Universal/Light/Views.png new file mode 100644 index 0000000..625b748 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Views.png differ diff --git a/VDownload/Assets/Icons/Universal/Light/Waiting.png b/VDownload/Assets/Icons/Universal/Light/Waiting.png new file mode 100644 index 0000000..dce6a60 Binary files /dev/null and b/VDownload/Assets/Icons/Universal/Light/Waiting.png differ diff --git a/VDownload/Assets/Logo/LargeTile.scale-100.png b/VDownload/Assets/Logo/LargeTile.scale-100.png new file mode 100644 index 0000000..1caadc1 Binary files /dev/null and b/VDownload/Assets/Logo/LargeTile.scale-100.png differ diff --git a/VDownload/Assets/Logo/LargeTile.scale-125.png b/VDownload/Assets/Logo/LargeTile.scale-125.png new file mode 100644 index 0000000..33b0c9c Binary files /dev/null and b/VDownload/Assets/Logo/LargeTile.scale-125.png differ diff --git a/VDownload/Assets/Logo/LargeTile.scale-150.png b/VDownload/Assets/Logo/LargeTile.scale-150.png new file mode 100644 index 0000000..c60f389 Binary files /dev/null and b/VDownload/Assets/Logo/LargeTile.scale-150.png differ diff --git a/VDownload/Assets/Logo/LargeTile.scale-200.png b/VDownload/Assets/Logo/LargeTile.scale-200.png new file mode 100644 index 0000000..f39860b Binary files /dev/null and b/VDownload/Assets/Logo/LargeTile.scale-200.png differ diff --git a/VDownload/Assets/Logo/LargeTile.scale-400.png b/VDownload/Assets/Logo/LargeTile.scale-400.png new file mode 100644 index 0000000..8891077 Binary files /dev/null and b/VDownload/Assets/Logo/LargeTile.scale-400.png differ diff --git a/VDownload/Assets/Logo/Logo.png b/VDownload/Assets/Logo/Logo.png new file mode 100644 index 0000000..3083d5d Binary files /dev/null and b/VDownload/Assets/Logo/Logo.png differ diff --git a/VDownload/Assets/Logo/SmallTile.scale-100.png b/VDownload/Assets/Logo/SmallTile.scale-100.png new file mode 100644 index 0000000..85a7b76 Binary files /dev/null and b/VDownload/Assets/Logo/SmallTile.scale-100.png differ diff --git a/VDownload/Assets/Logo/SmallTile.scale-125.png b/VDownload/Assets/Logo/SmallTile.scale-125.png new file mode 100644 index 0000000..1252c6a Binary files /dev/null and b/VDownload/Assets/Logo/SmallTile.scale-125.png differ diff --git a/VDownload/Assets/Logo/SmallTile.scale-150.png b/VDownload/Assets/Logo/SmallTile.scale-150.png new file mode 100644 index 0000000..d0f17a4 Binary files /dev/null and b/VDownload/Assets/Logo/SmallTile.scale-150.png differ diff --git a/VDownload/Assets/Logo/SmallTile.scale-200.png b/VDownload/Assets/Logo/SmallTile.scale-200.png new file mode 100644 index 0000000..126c231 Binary files /dev/null and b/VDownload/Assets/Logo/SmallTile.scale-200.png differ diff --git a/VDownload/Assets/Logo/SmallTile.scale-400.png b/VDownload/Assets/Logo/SmallTile.scale-400.png new file mode 100644 index 0000000..a2a9a46 Binary files /dev/null and b/VDownload/Assets/Logo/SmallTile.scale-400.png differ diff --git a/VDownload/Assets/Logo/SplashScreen.scale-100.png b/VDownload/Assets/Logo/SplashScreen.scale-100.png new file mode 100644 index 0000000..085bfcc Binary files /dev/null and b/VDownload/Assets/Logo/SplashScreen.scale-100.png differ diff --git a/VDownload/Assets/Logo/SplashScreen.scale-125.png b/VDownload/Assets/Logo/SplashScreen.scale-125.png new file mode 100644 index 0000000..07dccc7 Binary files /dev/null and b/VDownload/Assets/Logo/SplashScreen.scale-125.png differ diff --git a/VDownload/Assets/Logo/SplashScreen.scale-150.png b/VDownload/Assets/Logo/SplashScreen.scale-150.png new file mode 100644 index 0000000..e987bce Binary files /dev/null and b/VDownload/Assets/Logo/SplashScreen.scale-150.png differ diff --git a/VDownload/Assets/Logo/SplashScreen.scale-200.png b/VDownload/Assets/Logo/SplashScreen.scale-200.png new file mode 100644 index 0000000..53dd18a Binary files /dev/null and b/VDownload/Assets/Logo/SplashScreen.scale-200.png differ diff --git a/VDownload/Assets/Logo/SplashScreen.scale-400.png b/VDownload/Assets/Logo/SplashScreen.scale-400.png new file mode 100644 index 0000000..94a0981 Binary files /dev/null and b/VDownload/Assets/Logo/SplashScreen.scale-400.png differ diff --git a/VDownload/Assets/Logo/Square150x150Logo.scale-100.png b/VDownload/Assets/Logo/Square150x150Logo.scale-100.png new file mode 100644 index 0000000..854bac9 Binary files /dev/null and b/VDownload/Assets/Logo/Square150x150Logo.scale-100.png differ diff --git a/VDownload/Assets/Logo/Square150x150Logo.scale-125.png b/VDownload/Assets/Logo/Square150x150Logo.scale-125.png new file mode 100644 index 0000000..c51bff5 Binary files /dev/null and b/VDownload/Assets/Logo/Square150x150Logo.scale-125.png differ diff --git a/VDownload/Assets/Logo/Square150x150Logo.scale-150.png b/VDownload/Assets/Logo/Square150x150Logo.scale-150.png new file mode 100644 index 0000000..bf26b9a Binary files /dev/null and b/VDownload/Assets/Logo/Square150x150Logo.scale-150.png differ diff --git a/VDownload/Assets/Logo/Square150x150Logo.scale-200.png b/VDownload/Assets/Logo/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..0a3dfd4 Binary files /dev/null and b/VDownload/Assets/Logo/Square150x150Logo.scale-200.png differ diff --git a/VDownload/Assets/Logo/Square150x150Logo.scale-400.png b/VDownload/Assets/Logo/Square150x150Logo.scale-400.png new file mode 100644 index 0000000..8f56e66 Binary files /dev/null and b/VDownload/Assets/Logo/Square150x150Logo.scale-400.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-16.png b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-16.png new file mode 100644 index 0000000..7357738 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-16.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-24.png b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-24.png new file mode 100644 index 0000000..82f04f0 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-24.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-256.png b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-256.png new file mode 100644 index 0000000..1fb0224 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-256.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-32.png b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-32.png new file mode 100644 index 0000000..ae6d7cb Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-32.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-48.png b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-48.png new file mode 100644 index 0000000..9e758c3 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-lightunplated_targetsize-48.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-16.png b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-16.png new file mode 100644 index 0000000..7357738 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-16.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-24.png b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-24.png new file mode 100644 index 0000000..82f04f0 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-24.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-256.png b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 0000000..1fb0224 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-256.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-32.png b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-32.png new file mode 100644 index 0000000..ae6d7cb Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-32.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-48.png b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-48.png new file mode 100644 index 0000000..9e758c3 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.altform-unplated_targetsize-48.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.scale-100.png b/VDownload/Assets/Logo/Square44x44Logo.scale-100.png new file mode 100644 index 0000000..b5d08fe Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.scale-100.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.scale-125.png b/VDownload/Assets/Logo/Square44x44Logo.scale-125.png new file mode 100644 index 0000000..fee188e Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.scale-125.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.scale-150.png b/VDownload/Assets/Logo/Square44x44Logo.scale-150.png new file mode 100644 index 0000000..c14ee37 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.scale-150.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.scale-200.png b/VDownload/Assets/Logo/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..93d26a6 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.scale-200.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.scale-400.png b/VDownload/Assets/Logo/Square44x44Logo.scale-400.png new file mode 100644 index 0000000..ed217a4 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.scale-400.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.targetsize-16.png b/VDownload/Assets/Logo/Square44x44Logo.targetsize-16.png new file mode 100644 index 0000000..541122a Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.targetsize-16.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.targetsize-24.png b/VDownload/Assets/Logo/Square44x44Logo.targetsize-24.png new file mode 100644 index 0000000..ae1c6bb Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.targetsize-24.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.targetsize-256.png b/VDownload/Assets/Logo/Square44x44Logo.targetsize-256.png new file mode 100644 index 0000000..ddc7b84 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.targetsize-256.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.targetsize-32.png b/VDownload/Assets/Logo/Square44x44Logo.targetsize-32.png new file mode 100644 index 0000000..fbea8b7 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.targetsize-32.png differ diff --git a/VDownload/Assets/Logo/Square44x44Logo.targetsize-48.png b/VDownload/Assets/Logo/Square44x44Logo.targetsize-48.png new file mode 100644 index 0000000..8357d90 Binary files /dev/null and b/VDownload/Assets/Logo/Square44x44Logo.targetsize-48.png differ diff --git a/VDownload/Assets/Logo/StoreLogo.scale-100.png b/VDownload/Assets/Logo/StoreLogo.scale-100.png new file mode 100644 index 0000000..db31a48 Binary files /dev/null and b/VDownload/Assets/Logo/StoreLogo.scale-100.png differ diff --git a/VDownload/Assets/Logo/StoreLogo.scale-125.png b/VDownload/Assets/Logo/StoreLogo.scale-125.png new file mode 100644 index 0000000..dcab8cd Binary files /dev/null and b/VDownload/Assets/Logo/StoreLogo.scale-125.png differ diff --git a/VDownload/Assets/Logo/StoreLogo.scale-150.png b/VDownload/Assets/Logo/StoreLogo.scale-150.png new file mode 100644 index 0000000..acf854c Binary files /dev/null and b/VDownload/Assets/Logo/StoreLogo.scale-150.png differ diff --git a/VDownload/Assets/Logo/StoreLogo.scale-200.png b/VDownload/Assets/Logo/StoreLogo.scale-200.png new file mode 100644 index 0000000..4c84405 Binary files /dev/null and b/VDownload/Assets/Logo/StoreLogo.scale-200.png differ diff --git a/VDownload/Assets/Logo/StoreLogo.scale-400.png b/VDownload/Assets/Logo/StoreLogo.scale-400.png new file mode 100644 index 0000000..75a7096 Binary files /dev/null and b/VDownload/Assets/Logo/StoreLogo.scale-400.png differ diff --git a/VDownload/Assets/Logo/Wide310x150Logo.scale-100.png b/VDownload/Assets/Logo/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000..83a567b Binary files /dev/null and b/VDownload/Assets/Logo/Wide310x150Logo.scale-100.png differ diff --git a/VDownload/Assets/Logo/Wide310x150Logo.scale-125.png b/VDownload/Assets/Logo/Wide310x150Logo.scale-125.png new file mode 100644 index 0000000..b490b49 Binary files /dev/null and b/VDownload/Assets/Logo/Wide310x150Logo.scale-125.png differ diff --git a/VDownload/Assets/Logo/Wide310x150Logo.scale-150.png b/VDownload/Assets/Logo/Wide310x150Logo.scale-150.png new file mode 100644 index 0000000..5da8ba7 Binary files /dev/null and b/VDownload/Assets/Logo/Wide310x150Logo.scale-150.png differ diff --git a/VDownload/Assets/Logo/Wide310x150Logo.scale-200.png b/VDownload/Assets/Logo/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..085bfcc Binary files /dev/null and b/VDownload/Assets/Logo/Wide310x150Logo.scale-200.png differ diff --git a/VDownload/Assets/Logo/Wide310x150Logo.scale-400.png b/VDownload/Assets/Logo/Wide310x150Logo.scale-400.png new file mode 100644 index 0000000..53dd18a Binary files /dev/null and b/VDownload/Assets/Logo/Wide310x150Logo.scale-400.png differ diff --git a/VDownload/Assets/Other/UnknownThumbnail.png b/VDownload/Assets/Other/UnknownThumbnail.png new file mode 100644 index 0000000..38ed3d5 Binary files /dev/null and b/VDownload/Assets/Other/UnknownThumbnail.png differ diff --git a/VDownload/Objects/Enums/VideoSource.cs b/VDownload/Objects/Enums/VideoSource.cs new file mode 100644 index 0000000..6732770 --- /dev/null +++ b/VDownload/Objects/Enums/VideoSource.cs @@ -0,0 +1,10 @@ +namespace VDownload.Objects.Enums +{ + public enum VideoSource + { + TwitchVod, + TwitchClip, + YoutubeVideo, + Null + } +} diff --git a/VDownload/Objects/Enums/VideoStatus.cs b/VDownload/Objects/Enums/VideoStatus.cs new file mode 100644 index 0000000..942cd83 --- /dev/null +++ b/VDownload/Objects/Enums/VideoStatus.cs @@ -0,0 +1,10 @@ +namespace VDownload.Objects.Enums +{ + public enum VideoStatus + { + Idle, + Waiting, + InProgress, + Removed + } +} diff --git a/VDownload/Package.appxmanifest b/VDownload/Package.appxmanifest new file mode 100644 index 0000000..0e7c734 --- /dev/null +++ b/VDownload/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + + + VDownload + Mateusz Skoczek + Assets\Logo\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VDownload/Services/Config.cs b/VDownload/Services/Config.cs new file mode 100644 index 0000000..52f96db --- /dev/null +++ b/VDownload/Services/Config.cs @@ -0,0 +1,101 @@ +// System +using System.Collections.Generic; +using Windows.Storage; + +namespace VDownload.Services +{ + internal class Config + { + #region CONSTANTS + + // DATA VALUES LISTS + public static readonly string[] DateFormatList = new string[] + { + "yyyy.MM.dd", + "yyyy.dd.MM", + "dd.MM.yyyy", + "MM.dd.yyyy", + }; + public static readonly string[] DefaultMediaTypeList = new string[] + { + "AV", + "A", + "V" + }; + public static readonly string[] DefaultVideoExtensionList = new string[] + { + "MP4", + "WMV", + "HEVC" + }; + public static readonly string[] DefaultAudioExtensionList = new string[] + { + "MP3", + "FLAC", + "WAV", + "M4A", + "ALAC", + "WMA" + }; + + // DEFAULT SETTINGS + private static readonly Dictionary DefaultSettings = new Dictionary() + { + { "max_video_tasks" , "5" }, + { "default_video_extension", DefaultVideoExtensionList[0] }, + { "default_audio_extension", DefaultAudioExtensionList[0] }, + { "date_format", DateFormatList[0] }, + { "default_media_type", DefaultMediaTypeList[0] }, + { "default_output_filename", "[%date_pub%] %title%" }, + { "use_mrfcrf444", "1" }, + { "use_hardware_acceleration", "1" }, + { "delete_temp_on_start", "1" }, + { "delete_video_temp_after_error", "1" } + }; + + // SETTINGS CONTAINER + private static readonly ApplicationDataContainer SettingsContainer = ApplicationData.Current.LocalSettings; + + #endregion + + + + #region MAIN + + // GET VALUE + public static string GetValue(string key) + { + return SettingsContainer.Values[key].ToString(); + } + + // SET VALUE + public static void SetValue(string key, string value) + { + SettingsContainer.Values[key] = value; + } + + // SET DEFAULT + public static void SetDefault() + { + foreach (KeyValuePair s in DefaultSettings) + { + SettingsContainer.Values[s.Key] = s.Value; + } + } + + + // REBUILD + public static void Rebuild() + { + foreach (KeyValuePair s in DefaultSettings) + { + if (!SettingsContainer.Values.ContainsKey(s.Key)) + { + SettingsContainer.Values[s.Key] = s.Value; + } + } + } + + #endregion + } +} diff --git a/VDownload/Services/Media.cs b/VDownload/Services/Media.cs new file mode 100644 index 0000000..c29e848 --- /dev/null +++ b/VDownload/Services/Media.cs @@ -0,0 +1,119 @@ +// System +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Windows.ApplicationModel.Resources; +using Windows.Foundation; +using Windows.Media.MediaProperties; +using Windows.Media.Transcoding; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace VDownload.Services +{ + internal class Media + { + #region VARIABLES + + // PROGRESS UI ELEMENTS + private TextBlock ProgressLabelTextblock; + private ProgressBar ProgressBar; + + #endregion + + + + #region MAIN + + // TRANSCODE MEDIA FILE + public async Task Transcode(StorageFile inputFile, StorageFile outputFile, string extension, string mediaType, TimeSpan duration, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken token, TextBlock progressLabelTextblock, ProgressBar progressBar) + { + // Set progress UI elements + ProgressLabelTextblock = progressLabelTextblock; + ProgressBar = progressBar; + + // Init transcoder + MediaTranscoder transcoder = new MediaTranscoder + { + HardwareAccelerationEnabled = Config.GetValue("use_hardware_acceleration") == "1" ? true : false, + VideoProcessingAlgorithm = Config.GetValue("use_mrfcrf444") == "1" ? MediaVideoProcessingAlgorithm.MrfCrf444 : MediaVideoProcessingAlgorithm.Default + }; + if (0 < trimStart.TotalMilliseconds && trimStart.TotalMilliseconds < duration.TotalMilliseconds) // Set trimming at start + { + transcoder.TrimStartTime = trimStart; + } + if (0 < trimEnd.TotalMilliseconds && trimEnd.TotalMilliseconds < duration.TotalMilliseconds) // Set trimming at end + { + transcoder.TrimStopTime = trimEnd; + } + + // Set video encoding profile + MediaEncodingProfile profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p); + switch (extension) + { + case "MP4": profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p); break; + case "WMV": profile = MediaEncodingProfile.CreateWmv(VideoEncodingQuality.HD1080p); break; + case "HEVC": profile = MediaEncodingProfile.CreateHevc(VideoEncodingQuality.HD1080p); break; + case "MP3": profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High); break; + case "FLAC": profile = MediaEncodingProfile.CreateFlac(AudioEncodingQuality.High); break; + case "WAV": profile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High); break; + case "M4A": profile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High); break; + case "ALAC": profile = MediaEncodingProfile.CreateAlac(AudioEncodingQuality.High); break; + case "WMA": profile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High); break; + } + var videoData = await inputFile.Properties.GetVideoPropertiesAsync(); + var audioData = await inputFile.Properties.GetMusicPropertiesAsync(); + if (mediaType != "A") + { + profile.Video.Height = videoData.Height; + profile.Video.Width = videoData.Width; + profile.Video.Bitrate = videoData.Bitrate - audioData.Bitrate; + } + if (mediaType != "V") + { + profile.Audio.Bitrate = audioData.Bitrate; + } + if (mediaType == "V") + { + var audioTracks = profile.GetAudioTracks(); + audioTracks.Clear(); + profile.SetAudioTracks(audioTracks.AsEnumerable()); + } + + // Start transcoding operation + using (IRandomAccessStream outputFileOpened = await outputFile.OpenAsync(FileAccessMode.ReadWrite)) + { + PrepareTranscodeResult transcodingPreparated = await transcoder.PrepareStreamTranscodeAsync(await inputFile.OpenAsync(FileAccessMode.Read), outputFileOpened, profile); + IAsyncActionWithProgress transcodingTask = transcodingPreparated.TranscodeAsync(); + try + { + await transcodingTask.AsTask(token, new Progress(OnProgress)); + await outputFileOpened.FlushAsync(); + } + catch (TaskCanceledException) { } + transcodingTask.Close(); + } + } + + #endregion + + + + #region EVENTS + + // ON PROGRESS + private void OnProgress(double percent) + { + // Set progress + ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelTranscoding")} ({Math.Floor(percent)}%)"; + ProgressBar.IsIndeterminate = false; + ProgressBar.Visibility = Visibility.Visible; + ProgressBar.Value = percent; + } + + #endregion + } +} diff --git a/VDownload/Services/Source.cs b/VDownload/Services/Source.cs new file mode 100644 index 0000000..0678f46 --- /dev/null +++ b/VDownload/Services/Source.cs @@ -0,0 +1,43 @@ +// Internal +using VDownload.Objects.Enums; + +// System +using System; +using System.Linq; + +namespace VDownload.Services +{ + internal class Source + { + public static (VideoSource, string) GetVideoSourceData(Uri url) + { + // Twitch VOD + if (url.Host == "www.twitch.tv" && url.Segments.Contains("videos/")) + { + return (VideoSource.TwitchVod, url.Segments[url.Segments.Length - 1].Replace("/", "")); + } + + // Twitch Clip + else if ((url.Host == "www.twitch.tv" && url.Segments.Contains("clip/")) || url.Host == "clips.twitch.tv") + { + return (VideoSource.TwitchClip, url.Segments[url.Segments.Length - 1].Replace("/", "")); + } + + // Youtube Video + else if (url.Host == "www.youtube.com" && url.Segments.Contains("watch")) + { + return (VideoSource.YoutubeVideo, url.Query.Replace("?", "").Split('&')[0].Replace("v=", "")); + } + else if (url.Host == "youtu.be") + { + return (VideoSource.YoutubeVideo, url.Segments[url.Segments.Length - 1]); + } + + // Unknown + else + { + return (VideoSource.Null, ""); + } + } + } +} diff --git a/VDownload/Services/Videos.cs b/VDownload/Services/Videos.cs new file mode 100644 index 0000000..992f29a --- /dev/null +++ b/VDownload/Services/Videos.cs @@ -0,0 +1,64 @@ +// Internal +using VDownload.Sources; + +// System +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; + +namespace VDownload.Services +{ + internal class Videos + { + #region MAIN + + // VIDEO OBJECTS LIST + public static List VideoObjectsList = new List(); + + // ACTIVE VIDEO TASKS LIST + public static List VideoTasksList = new List(); + + // WAIT FOR FREE SPACE IN ACTIVE VIDEO TASKS LIST + public static async Task WaitForFreeSpace(CancellationToken token) + { + await Task.Run(async () => + { + while (!(VideoTasksList.Count < int.Parse((string)Config.GetValue("max_video_tasks"))) && !token.IsCancellationRequested) + { + await Task.Delay(50); + } + }); + } + + #endregion + + + + #region GET UNIQUE ID + + // VARIABLES + private static readonly Random Random = new Random(); + private static readonly char[] CharsID = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); + private static readonly int LengthID = 10; + private static readonly List UsedID = new List(); + + // METHOD + public static string GetUniqueID() + { + string id; + do + { + id = ""; + while (id.Length < LengthID) + { + id += CharsID[Random.Next(0, CharsID.Length)]; + } + } while (UsedID.Contains(id)); + UsedID.Add(id); + return id; + } + + #endregion + } +} diff --git a/VDownload/Sources/Twitch/Vod.cs b/VDownload/Sources/Twitch/Vod.cs new file mode 100644 index 0000000..bedd5e2 --- /dev/null +++ b/VDownload/Sources/Twitch/Vod.cs @@ -0,0 +1,266 @@ +// Internal +using VDownload.Services; + +// External +using Newtonsoft.Json.Linq; + +// System +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Windows.ApplicationModel.Resources; +using Windows.Storage; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media.Imaging; + +namespace VDownload.Sources.Twitch +{ + internal class Vod + { + #region INIT + + // ID + private string ID { get; set; } + + // CONSTRUCTOR + public Vod(string id) + { + ID = id; + } + + #endregion + + + + #region MAIN + + // GET METADATA + public async Task> GetMetadata() + { + // Client settings + WebClient Client = new WebClient(); + Client.Headers.Add("Accept", "application/vnd.twitchtv.v5+json"); + Client.Headers.Add("Client-ID", "v8kfhyc2980it9e7t5hhc7baukzuj2"); + + // Send request and get response + Uri requestUrl = new Uri($"https://api.twitch.tv/kraken/videos/{ID}"); + JObject response = JObject.Parse(await Client.DownloadStringTaskAsync(requestUrl)); + + // Pack data into dictionary + Dictionary metadata = new Dictionary() + { + ["title"] = response["title"].ToString().Replace("\n", ""), + ["author"] = response["channel"]["display_name"].ToString(), + ["date"] = Convert.ToDateTime(response["created_at"].ToString()), + ["duration"] = TimeSpan.FromSeconds(int.Parse(response["length"].ToString())), + ["views"] = long.Parse(response["views"].ToString()), + ["url"] = new Uri(response["url"].ToString()) + }; + try + { metadata["thumbnail"] = new Uri(response["thumbnails"]["large"][0]["url"].ToString()); } + catch + { metadata["thumbnail"] = new Uri("Assets/Icons/Unknown/Thumbnail.png", UriKind.Relative); } + + // Return metadata + return metadata; + } + + // GET STREAMS + public async Task> GetStreams() + { + // Client settings + WebClient Client = new WebClient(); + Client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko"); + + // Get access token + JObject accessToken = JObject.Parse(await Client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"operationName\":\"PlaybackAccessToken_Template\",\"query\":\"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}\",\"variables\":{\"isLive\":false,\"login\":\"\",\"isVod\":true,\"vodID\":\"" + ID + "\",\"playerType\":\"embed\"}}")); + string tokenVal = accessToken["data"]["videoPlaybackAccessToken"]["value"].ToString(); + string tokenSig = accessToken["data"]["videoPlaybackAccessToken"]["signature"].ToString(); + + // Get streams + string[] response = Client.DownloadString($"http://usher.twitch.tv/vod/{ID}?nauth={tokenVal}&nauthsig={tokenSig}&allow_source=true&player=twitchweb").Split("\n"); + + // Pack data into dictionary + Dictionary streams = new Dictionary(); + for (int i = 2; i < response.Length; i += 3) + { + string key = response[i].Replace("#EXT-X-MEDIA:", "").Split(',')[2].Replace("NAME=", "").Replace("\"", ""); + Uri value = new Uri(response[i + 2]); + streams[key] = value; + } + + // Return streams + return streams; + } + + // DOWNLOAD VIDEO + private TextBlock ProgressLabelTextblock; + private ProgressBar ProgressBar; + private Image ProgressIcon; + public async Task Download(StorageFolder tempFolder, string quality, string extension, string mediaType, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration, CancellationTokenSource token, TextBlock progressLabelTextblock, ProgressBar progressBar, Image progressIcon) + { + // Set variables + ProgressLabelTextblock = progressLabelTextblock; + ProgressBar = progressBar; + ProgressIcon = progressIcon; + + // Set progress to downloading + if (!token.Token.IsCancellationRequested) + { + ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} (0%)"; + ProgressBar.IsIndeterminate = false; + ProgressBar.Visibility = Visibility.Visible; + ProgressBar.Value = 0; + ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Downloading.png") }; + } + + // Get chunks + Uri[] chunks = new Uri[] { }; + float chunksInTotal = 0; + float chunksDownloaded = 0; + if (!token.Token.IsCancellationRequested) + { + chunks = await ExtractChunksFromM3U8((await GetStreams())[quality]); + chunksInTotal = chunks.Length; + } + + // Download + StorageFile rawFile = null; + if (!token.Token.IsCancellationRequested) + { + try + { + rawFile = await tempFolder.CreateFileAsync("raw.ts"); + if (!token.Token.IsCancellationRequested) + { + Task writeTask = WriteChunkToFile(rawFile, await DownloadVodChunk(chunks[0])); + chunksDownloaded++; + ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} ({Math.Floor(chunksDownloaded / chunksInTotal * 100)}%)"; + ProgressBar.IsIndeterminate = false; + ProgressBar.Visibility = Visibility.Visible; + ProgressBar.Value = chunksDownloaded / chunksInTotal * 100; + Task downloadTask = DownloadVodChunk(chunks[1]); + for (int i = 2; i < chunks.Length; i++) + { + if (!token.Token.IsCancellationRequested) + { + await Task.WhenAll(downloadTask, writeTask); + chunksDownloaded++; + ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDownloading")} ({Math.Floor(chunksDownloaded / chunksInTotal * 100)}%)"; + ProgressBar.IsIndeterminate = false; + ProgressBar.Visibility = Visibility.Visible; + ProgressBar.Value = chunksDownloaded / chunksInTotal * 100; + writeTask = WriteChunkToFile(rawFile, downloadTask.Result); + downloadTask = DownloadVodChunk(chunks[i]); + } + } + } + } + catch (WebException) + { + throw new Exception(ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelErrorInternetConnection")); + } + } + + // Rendering + StorageFile outputFile = null; + if (!token.Token.IsCancellationRequested) + { + // Set progress to transcoding + ProgressLabelTextblock.Text = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelTranscoding")} (0%)"; + ProgressBar.IsIndeterminate = false; + ProgressBar.Visibility = Visibility.Visible; + ProgressBar.Value = 0; + ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Transcoding.png") }; + + // Create transcoding output file + outputFile = await tempFolder.CreateFileAsync($"processed.{extension.ToLower()}"); + + // Run processing + await new Media().Transcode(rawFile, outputFile, extension, mediaType, duration, trimStart, trimEnd, token.Token, ProgressLabelTextblock, ProgressBar); + } + + // Return output file + return outputFile; + } + + #endregion + + + + #region INTERNAL + + // EXTRACT CHUNKS FROM M3U8 PLAYLIST + private async Task ExtractChunksFromM3U8(Uri streamUrl) + { + // Client settings + WebClient Client = new WebClient(); + Client.Headers.Add("Client-ID", "kimne78kx3ncx6brgo4mv6wki5h1ko"); + + // Get playlist content + string request = await Client.DownloadStringTaskAsync(streamUrl); + + // Pack into list + List videos = new List(); + foreach (string l in request.Split("\n")) + { + if (l.Length > 0 && l[0] != '#') + { + string[] uriSegments = streamUrl.Segments; + string streamDir = ""; + for (int i = 0; i < uriSegments.Length - 1; i++) + { + streamDir += uriSegments[i]; + } + videos.Add(new Uri($@"{streamUrl.GetLeftPart(UriPartial.Scheme)}{streamUrl.Host}{streamDir}{l}")); + } + } + + // Return videos + return videos.ToArray(); + } + + // DOWNLOAD VOD CHUNK + private async Task DownloadVodChunk(Uri url) + { + // Download + int errorCount = 0; + bool done = false; + while (!done && errorCount < 10) + { + try + { + using (WebClient Client = new WebClient()) + { + return await Client.DownloadDataTaskAsync(url); + } + } + catch + { + errorCount++; + await Task.Delay(5000); + } + } + throw new WebException(); + } + + // WRITE CHUNK TO FILE + private Task WriteChunkToFile(StorageFile file, byte[] dataToWrite) + { + return Task.Factory.StartNew(() => + { + using (var stream = new FileStream(file.Path, FileMode.Append)) + { + stream.Write(dataToWrite, 0, dataToWrite.Length); + stream.Close(); + } + }); + } + + #endregion + } +} diff --git a/VDownload/Sources/VObject.cs b/VDownload/Sources/VObject.cs new file mode 100644 index 0000000..87874ee --- /dev/null +++ b/VDownload/Sources/VObject.cs @@ -0,0 +1,673 @@ +// Internal +using VDownload.Objects.Enums; +using VDownload.Services; + +// System +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Windows.ApplicationModel.ExtendedExecution; +using Windows.ApplicationModel.Resources; +using Windows.Storage; +using Windows.UI.Text; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; +using Windows.UI; +using System.Diagnostics; + +namespace VDownload.Sources +{ + public class VObject + { + #region INIT + + // VIDEO METADATA + private string UniqueID { get; set; } + public VideoSource SourceType { get; private set; } + public string ID { get; private set; } + public string Title { get; private set; } + public string Author { get; private set; } + public DateTime Date { get; private set; } + public long Views { get; private set; } + public TimeSpan Duration { get; private set; } + public Uri Url { get; private set; } + public Uri Thumbnail { get; private set; } + public Uri SourceIcon { get; private set; } + public Dictionary Streams { get; private set; } + + // FILE PROCESS DATA + public string SelectedQuality { get; set; } + public TimeSpan TrimStart { get; set; } + public TimeSpan TrimEnd { get; set; } + public string MediaType { get; set; } + public string Filename { get; set; } + public string Extension { get; set; } + public string FilePath { get; set; } + public StorageFolder CustomSaveLocation { get; set; } + + // VIDEO PANEL + private Grid VideoPanel { get; set; } + private StackPanel VideoPanelParent { get; set; } + private AppBarButton StartStopButton { get; set; } + private TextBlock ProgressLabelTextblock { get; set; } + private ProgressBar ProgressBar { get; set; } + private Image ProgressIcon { get; set; } + + // VIDEO TASK + private object VideoSourceHandler { get; set; } + private CancellationTokenSource VideoTaskCancellationToken { get; set; } + private Task VideoTask { get; set; } + public VideoStatus VideoStatus { get; private set; } + + // CONSTRUCTOR + public VObject(Uri url) + { + (SourceType, ID) = Source.GetVideoSourceData(url); + switch (SourceType) // (TODO) + { + case VideoSource.TwitchVod: + VideoSourceHandler = new Twitch.Vod(ID); + SourceIcon = new Uri("ms-appx:///Assets/Icons/Sources/Twitch.png"); + break; + } + UniqueID = Videos.GetUniqueID(); + } + + #endregion + + + + #region MAIN + + // GET METADATA AND SET DATA VARIABLES + public async Task GetMetadata() + { + // Get metadata and streams + Task> metadataTask; + Task> streamsTask; + switch (SourceType) // (TODO) + { + case VideoSource.TwitchVod: + metadataTask = ((Twitch.Vod)VideoSourceHandler).GetMetadata(); + streamsTask = ((Twitch.Vod)VideoSourceHandler).GetStreams(); + break; + default: + throw new Exception(message: "Unknown video source"); + } + await Task.WhenAll(metadataTask, streamsTask); + Dictionary metadata = metadataTask.Result; + Dictionary streams = streamsTask.Result; + + // Set metadata + Title = (string)metadata["title"]; + Author = (string)metadata["author"]; + Date = (DateTime)metadata["date"]; + Views = (long)metadata["views"]; + Duration = (TimeSpan)metadata["duration"]; + Thumbnail = (Uri)metadata["thumbnail"]; + Url = (Uri)metadata["url"]; + Streams = streams; + + // Set default media type + MediaType = Config.GetValue("default_media_type"); + + // Set default quality + SelectedQuality = Streams.Keys.ToArray()[0]; + + // Set trim timestamps + TrimStart = new TimeSpan(0); + TrimEnd = Duration; + + // Set defualt filename + Dictionary filenameTemplate = new Dictionary() + { + { "%title%", Title }, + { "%author%", Author }, + { "%date_pub%", Date.ToString(Config.GetValue("date_format")) }, + { "%date_now%", DateTime.Now.ToString(Config.GetValue("date_format")) }, + { "%views%", Views.ToString() }, + { "%id%", ID } + }; + string temporaryFilename = Config.GetValue("default_output_filename"); + foreach (KeyValuePair t in filenameTemplate) + { temporaryFilename = temporaryFilename.Replace(t.Key, t.Value); } + foreach (char c in Path.GetInvalidFileNameChars()) + { temporaryFilename = temporaryFilename.Replace(c, ' '); } + Filename = temporaryFilename; + + // Set extension + Extension = MediaType == "A" ? Config.GetValue("default_audio_extension") : Config.GetValue("default_video_extension"); + + // Set visible path + if (CustomSaveLocation != null) FilePath = $@"{CustomSaveLocation.Path}\{Filename}.{Extension.ToLower()}"; + else FilePath = $@"{UserDataPaths.GetDefault().Downloads}\VDownload\{Filename}.{Extension.ToLower()}"; + } + + // ADD VIDEO TO LIST + public void AddVideoToList(StackPanel parent) + { + // Set status to idle + VideoStatus = VideoStatus.Idle; + + // Video panel management + VideoPanel = CreateVideoPanel(); + VideoPanelParent = parent; + VideoPanelParent.Children.Add(VideoPanel); + + // Add VObject to listed videos list + Videos.VideoObjectsList.Add(this); + } + + // REMOVE VIDEO FROM LIST + public void RemoveVideoFromList() + { + // Remove video from video list + VideoPanelParent.Children.Remove(VideoPanel); + + // Remove VObject from listed videos list + Videos.VideoObjectsList.Remove(this); + + // Set status as removed + VideoStatus = VideoStatus.Removed; + } + + // START VIDEO TASK + public async Task Start() + { + // Change video status to idle + VideoStatus = VideoStatus.Waiting; + + // Set cancellation token + VideoTaskCancellationToken = new CancellationTokenSource(); + + // Set panel (start and waiting) + StartStopButton.Icon = new SymbolIcon(Symbol.Stop); + ProgressLabelTextblock.Text = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelWaiting"); + ProgressBar.IsIndeterminate = true; + ProgressBar.Visibility = Visibility.Visible; + ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Waiting.png") }; + + // Wait for free space in active video tasks list + await Videos.WaitForFreeSpace(VideoTaskCancellationToken.Token); + + // Set end message + string endMessage = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelCancelled"); + Uri endIconSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Cancelled.png"); + + // Start video downloading process + if (!VideoTaskCancellationToken.IsCancellationRequested) + { + // Init temp folder + StorageFolder tempFolder = await ApplicationData.Current.TemporaryFolder.CreateFolderAsync(UniqueID); + + // Set video task handler (TODO) + switch (SourceType) + { + case VideoSource.TwitchVod: + VideoTask = ((Twitch.Vod)VideoSourceHandler).Download(tempFolder, SelectedQuality, Extension, MediaType, TrimStart, TrimEnd, Duration, VideoTaskCancellationToken, ProgressLabelTextblock, ProgressBar, ProgressIcon); + break; + default: + throw new Exception(message: "Unknown video source"); + } + + // Add video task to active video tasks list + Videos.VideoTasksList.Add(VideoTask); + VideoStatus = VideoStatus.InProgress; + + // Start video task + StorageFile downloadedFile = null; + bool error = false; + string errorMessage = ""; + Stopwatch executeStopwatch = new Stopwatch(); + executeStopwatch.Start(); + try + { + // Request no suspendable session + ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified }; + await session.RequestExtensionAsync(); + + // Run task + downloadedFile = await VideoTask; + + // Dispose session + session.Dispose(); + } + catch (Exception ex) + { + // Set error info (error identifier and endMessage) + error = true; + errorMessage = ex.Message; + } + finally + { + // Set progress to finalizing + ProgressLabelTextblock.Text = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelFinalizing"); + ProgressBar.IsIndeterminate = true; + ProgressBar.Visibility = Visibility.Visible; + ProgressIcon.Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Finalizing.png") }; + + // Move to output destination + if (!VideoTaskCancellationToken.IsCancellationRequested && !error) + { + // Set output file + StorageFile outputFile; + if (CustomSaveLocation != null) outputFile = await CustomSaveLocation.CreateFileAsync($"{Filename}.{Extension.ToLower()}", CreationCollisionOption.GenerateUniqueName); + else outputFile = await DownloadsFolder.CreateFileAsync($"{Filename}.{Extension.ToLower()}", CreationCollisionOption.GenerateUniqueName); + + // Create and move file + await downloadedFile.MoveAndReplaceAsync(outputFile); + } + + // Delete temp + if (!error || Config.GetValue("delete_video_temp_after_error") == "1") + await tempFolder.DeleteAsync(); + + // Remove video task from active video tasks list + Videos.VideoTasksList.Remove(VideoTask); + VideoStatus = VideoStatus.Idle; + + // Stop stopwatch + executeStopwatch.Stop(); + + // Set end message + if (!VideoTaskCancellationToken.IsCancellationRequested) + { + if (error) + { + endMessage = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelError")} ({errorMessage})"; + endIconSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Error.png"); + } + else + { + endMessage = $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDone1")} {Math.Round(executeStopwatch.Elapsed.TotalSeconds, 0)} {ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelDone2")}"; + endIconSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Done.png"); + } + } + } + } + + // End message + ProgressLabelTextblock.Text = endMessage; + StartStopButton.Icon = new SymbolIcon(Symbol.Download); + ProgressBar.Visibility = Visibility.Collapsed; + ProgressIcon.Source = new BitmapImage { UriSource = endIconSource }; + } + + #endregion + + + + #region PANEL + + // CREATE VIDEO PANEL + private Grid CreateVideoPanel() + { + // Base grid + Grid baseGrid = new Grid + { + Background = new SolidColorBrush((Color)Application.Current.Resources["SystemChromeAltHighColor"]), + Margin = new Thickness(0, 5, 0, 5), + BorderThickness = new Thickness(20), + BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["SystemChromeAltHighColor"]), + CornerRadius = new CornerRadius(5), + }; + baseGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + baseGrid.ColumnDefinitions.Add(new ColumnDefinition()); + baseGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + + + + // Thumbnail image + Image thumbnailImage = new Image + { + Source = new BitmapImage { UriSource = Thumbnail }, + Height = 144, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + }; + Grid.SetColumn(thumbnailImage, 0); + baseGrid.Children.Add(thumbnailImage); + + + + // Data grid + Grid dataGrid = new Grid + { + Margin = new Thickness(20, 0, 20, 0), + }; + dataGrid.RowDefinitions.Add(new RowDefinition()); + dataGrid.RowDefinitions.Add(new RowDefinition()); + dataGrid.RowDefinitions.Add(new RowDefinition()); + dataGrid.RowDefinitions.Add(new RowDefinition()); + Grid.SetColumn(dataGrid, 1); + baseGrid.Children.Add(dataGrid); + + // Title textblock + TextBlock titleTextBlock = new TextBlock + { + Text = Title, + FontWeight = FontWeights.Bold, + FontSize = 16, + Margin = new Thickness(0, 0, 0, 8), + }; + Grid.SetRow(titleTextBlock, 0); + dataGrid.Children.Add(titleTextBlock); + + + // Metadata grid + Grid metadataGrid = new Grid(); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + metadataGrid.ColumnDefinitions.Add(new ColumnDefinition()); + metadataGrid.RowDefinitions.Add(new RowDefinition()); + metadataGrid.RowDefinitions.Add(new RowDefinition()); + metadataGrid.RowDefinitions.Add(new RowDefinition()); + metadataGrid.RowDefinitions.Add(new RowDefinition()); + Grid.SetRow(metadataGrid, 1); + dataGrid.Children.Add(metadataGrid); + double iconSize = 15; + double textSize = 11; + double iconMargin = 5; + + // Author icon + Image authorIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Author.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(authorIcon, 0); + Grid.SetRow(authorIcon, 0); + metadataGrid.Children.Add(authorIcon); + + // Author data textblock + TextBlock authorDataTextBlock = new TextBlock + { + Text = Author, + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(authorDataTextBlock, 1); + Grid.SetRow(authorDataTextBlock, 0); + metadataGrid.Children.Add(authorDataTextBlock); + + // Views icon + Image viewsIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Views.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(viewsIcon, 3); + Grid.SetRow(viewsIcon, 0); + metadataGrid.Children.Add(viewsIcon); + + // Views data textblock + TextBlock viewsDataTextBlock = new TextBlock + { + Text = Views.ToString(), + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(viewsDataTextBlock, 4); + Grid.SetRow(viewsDataTextBlock, 0); + metadataGrid.Children.Add(viewsDataTextBlock); + + // Date icon + Image dateIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Date.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(dateIcon, 6); + Grid.SetRow(dateIcon, 0); + metadataGrid.Children.Add(dateIcon); + + // Date data textblock + TextBlock dateDataTextBlock = new TextBlock + { + Text = Date.ToString((string)Config.GetValue("date_format")), + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(dateDataTextBlock, 7); + Grid.SetRow(dateDataTextBlock, 0); + metadataGrid.Children.Add(dateDataTextBlock); + + // Duration icon + Image durationIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Duration.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(durationIcon, 0); + Grid.SetRow(durationIcon, 1); + metadataGrid.Children.Add(durationIcon); + + // Duration data textblock + TextBlock durationDataTextBlock = new TextBlock + { + Text = Duration.ToString(), + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(durationDataTextBlock, 1); + Grid.SetRow(durationDataTextBlock, 1); + metadataGrid.Children.Add(durationDataTextBlock); + + // Media type & quality icon + Image qualityIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Quality.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(qualityIcon, 3); + Grid.SetRow(qualityIcon, 1); + metadataGrid.Children.Add(qualityIcon); + + // Media type & quality data textblock + string mediaTypeQualityData = MediaType == "A" ? "" : SelectedQuality; + switch (MediaType) + { + case "AV": mediaTypeQualityData += $" ({ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataAV")})"; break; + case "A": mediaTypeQualityData += $"{ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataA")}"; break; + case "V": mediaTypeQualityData += $" ({ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataV")})"; break; + default: mediaTypeQualityData += $" ({ResourceLoader.GetForCurrentView().GetString("VideoPanelMediaTypeDataAV")})"; break; + } + TextBlock qualityDataTextBlock = new TextBlock + { + Text = mediaTypeQualityData, + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(qualityDataTextBlock, 4); + Grid.SetRow(qualityDataTextBlock, 1); + metadataGrid.Children.Add(qualityDataTextBlock); + + // Trim icon + Image trimIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Trim.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(trimIcon, 6); + Grid.SetRow(trimIcon, 1); + metadataGrid.Children.Add(trimIcon); + + // Trim data textblock + TextBlock trimDataTextBlock = new TextBlock + { + Text = $"{TrimStart} - {TrimEnd}", + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(trimDataTextBlock, 7); + Grid.SetRow(trimDataTextBlock, 1); + metadataGrid.Children.Add(trimDataTextBlock); + + // Path icon + Image pathIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Path.png") }, + Margin = new Thickness(0, iconMargin, 0, iconMargin), + Width = iconSize, + }; + Grid.SetColumn(pathIcon, 0); + Grid.SetRow(pathIcon, 2); + metadataGrid.Children.Add(pathIcon); + + // File path data textblock + TextBlock filePathDataTextBlock = new TextBlock + { + Text = FilePath, + Margin = new Thickness(10, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize, + }; + Grid.SetColumn(filePathDataTextBlock, 1); + Grid.SetColumnSpan(filePathDataTextBlock, 7); + Grid.SetRow(filePathDataTextBlock, 2); + metadataGrid.Children.Add(filePathDataTextBlock); + + + // Progress grid + Grid progressGrid = new Grid + { + Margin = new Thickness(0, 10, 0, 0), + }; + progressGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + progressGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + progressGrid.ColumnDefinitions.Add(new ColumnDefinition()); + Grid.SetRow(progressGrid, 3); + Grid.SetColumnSpan(progressGrid, 8); + metadataGrid.Children.Add(progressGrid); + + // Progress icon + ProgressIcon = new Image + { + Source = new BitmapImage { UriSource = new Uri($"ms-appx:///Assets/Icons/Universal/{(Application.Current.RequestedTheme == ApplicationTheme.Dark ? "Dark" : "Light")}/Idle.png") }, + Width = iconSize, + }; + Grid.SetColumn(ProgressIcon, 0); + progressGrid.Children.Add(ProgressIcon); + + // Progress textblock + ProgressLabelTextblock = new TextBlock + { + Text = ResourceLoader.GetForCurrentView().GetString("VideoPanelProgressLabelIdle"), + Margin = new Thickness(10, 0, 20, 0), + VerticalAlignment = VerticalAlignment.Center, + FontSize = textSize + }; + Grid.SetColumn(ProgressLabelTextblock, 1); + progressGrid.Children.Add(ProgressLabelTextblock); + + // Progress bar + ProgressBar = new ProgressBar + { + Visibility = Visibility.Collapsed, + }; + Grid.SetColumn(ProgressBar, 2); + progressGrid.Children.Add(ProgressBar); + + // Buttons grid + Grid buttonsGrid = new Grid + { + VerticalAlignment = VerticalAlignment.Center, + }; + buttonsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + buttonsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + buttonsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + Grid.SetColumn(buttonsGrid, 2); + baseGrid.Children.Add(buttonsGrid); + + // Source icon + AppBarButton sourceButton = new AppBarButton + { + Icon = new BitmapIcon { UriSource = SourceIcon, ShowAsMonochrome = false }, + Width = 40, + Height = 48, + }; + sourceButton.Click += SourceButtonClicked; + Grid.SetRow(sourceButton, 0); + buttonsGrid.Children.Add(sourceButton); + + // Delete button + AppBarButton deleteButton = new AppBarButton + { + Icon = new SymbolIcon(Symbol.Clear), + Width = 40, + Height = 48, + }; + deleteButton.Click += DeleteButtonClicked; + Grid.SetRow(deleteButton, 1); + buttonsGrid.Children.Add(deleteButton); + + // Download/Stop button + StartStopButton = new AppBarButton + { + Icon = new SymbolIcon(Symbol.Download), + Width = 40, + Height = 48, + }; + StartStopButton.Click += DownloadStopButtonClicked; + Grid.SetRow(StartStopButton, 2); + buttonsGrid.Children.Add(StartStopButton); + + // Return panel + return baseGrid; + } + + // SOURCE BUTTON + private async void SourceButtonClicked(object sender, RoutedEventArgs e) + { + // Launch the website + await Windows.System.Launcher.LaunchUriAsync(Url); + } + + // DELETE BUTTON + private void DeleteButtonClicked(object sender, RoutedEventArgs e) + { + // Cancel if video downloading was started + if (VideoStatus == VideoStatus.InProgress || VideoStatus == VideoStatus.Waiting) + VideoTaskCancellationToken.Cancel(); + + // Remove video from the list + RemoveVideoFromList(); + } + + // DOWNLOAD STOP BUTTON + private async void DownloadStopButtonClicked(object sender, RoutedEventArgs e) + { + // Cancel if video downloading was started + if (VideoStatus == VideoStatus.InProgress || VideoStatus == VideoStatus.Waiting) + VideoTaskCancellationToken.Cancel(); + + // Start if video downloading wasn't started + else + await Start(); + } + + #endregion + } +} diff --git a/VDownload/Strings/en-US/Resources.resw b/VDownload/Strings/en-US/Resources.resw new file mode 100644 index 0000000..1bc6390 --- /dev/null +++ b/VDownload/Strings/en-US/Resources.resw @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Add + + + ADD VIDEO + + + Only audio + + + Normal + + + Only video + + + Media type + + + Quality + + + Trim + + + File + + + Location + + + Video not found. Try again. + + + Search + + + Paste URL and click "Search" button + + + Add video + + + 75 + + + Download All + + + 90 + + + Settings + + + 65 + + + Only audio + + + Audio & Video + + + Only video + + + Cancelled + + + Done in + + + seconds + + + Downloading + + + An error occured! + + + Internet connection error + + + Finalizing + + + Idle + + + Transcoding + + + Queued + + + Only audio + + \ No newline at end of file diff --git a/VDownload/VDownload.csproj b/VDownload/VDownload.csproj new file mode 100644 index 0000000..90e9baf --- /dev/null +++ b/VDownload/VDownload.csproj @@ -0,0 +1,305 @@ + + + + + Debug + x86 + {324AB81A-F68D-424D-B90E-5402F5E44240} + AppContainerExe + Properties + VDownload + VDownload + en-US + UAP + 10.0.19041.0 + 10.0.19041.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + false + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\ARM64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM64 + false + prompt + true + true + + + bin\ARM64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM64 + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + PackageReference + + + + App.xaml + + + + + + + + + + + AddVideoBase.xaml + + + AddVideoLoading.xaml + + + AddVideoMain.xaml + + + AddVideoNotFound.xaml + + + AddVideoStart.xaml + + + MainPage.xaml + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + 6.2.13 + + + 7.1.2 + + + 2.7.0 + + + 13.0.1 + + + + + + + 14.0 + + + + \ No newline at end of file diff --git a/VDownload/Views/AddVideo/AddVideoBase.xaml b/VDownload/Views/AddVideo/AddVideoBase.xaml new file mode 100644 index 0000000..9447737 --- /dev/null +++ b/VDownload/Views/AddVideo/AddVideoBase.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + +