1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.exe filter=lfs diff=lfs merge=lfs -text
|
||||||
BIN
.github/Images/Home.png
vendored
BIN
.github/Images/Home.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 94 KiB |
18
.github/config/gitversion.yml
vendored
Normal file
18
.github/config/gitversion.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
next-version: 1.0.0
|
||||||
|
assembly-versioning-scheme: MajorMinorPatch
|
||||||
|
assembly-file-versioning-scheme: MajorMinorPatch
|
||||||
|
|
||||||
|
branches:
|
||||||
|
master:
|
||||||
|
regex: ^master$
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
increment: Patch
|
||||||
|
tag: ''
|
||||||
|
is-release-branch: true
|
||||||
|
test:
|
||||||
|
regex: ^features
|
||||||
|
mode: ContinuousDelivery
|
||||||
|
increment: Patch
|
||||||
|
tag: ''
|
||||||
|
is-release-branch: true
|
||||||
|
source-branches: []
|
||||||
26
.github/workflows/pull_request_dev.yml
vendored
Normal file
26
.github/workflows/pull_request_dev.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build application on dev pull request
|
||||||
|
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "dev"
|
||||||
|
paths:
|
||||||
|
- "VDownload**"
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 8.0.x
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build
|
||||||
26
.github/workflows/pull_request_master.yml
vendored
Normal file
26
.github/workflows/pull_request_master.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build application on master pull request
|
||||||
|
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
paths:
|
||||||
|
- "VDownload**"
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 8.0.x
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build
|
||||||
26
.github/workflows/push_dev.yml
vendored
Normal file
26
.github/workflows/push_dev.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build application on dev push
|
||||||
|
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "dev"
|
||||||
|
paths:
|
||||||
|
- "VDownload**"
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 8.0.x
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build
|
||||||
66
.github/workflows/push_master.yml
vendored
Normal file
66
.github/workflows/push_master.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Build and publish application on master push
|
||||||
|
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
- "features/github_actions"
|
||||||
|
#paths:
|
||||||
|
#- "VDownload**"
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
MSBUILD_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\devenv.com
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup GitVersion
|
||||||
|
uses: gittools/actions/gitversion/setup@v0.9.7
|
||||||
|
with:
|
||||||
|
versionSpec: 5.x
|
||||||
|
- name: Determine Version
|
||||||
|
uses: gittools/actions/gitversion/execute@v0.9.7
|
||||||
|
id: gitversion
|
||||||
|
with:
|
||||||
|
useConfigFile: true
|
||||||
|
configFilePath: .github/config/gitversion.yml
|
||||||
|
- name: Set version in VDownload.csproj file
|
||||||
|
id: package_version
|
||||||
|
uses: KageKirin/set-csproj-version@v0
|
||||||
|
with:
|
||||||
|
file: VDownload/VDownload.csproj
|
||||||
|
version: ${{steps.gitversion.outputs.SemVer}}
|
||||||
|
- name: Set version in Package.appxmanifest file
|
||||||
|
uses: Nambers/ReplaceStringInFile@v1.3
|
||||||
|
with:
|
||||||
|
path: VDownload/Package.appxmanifest
|
||||||
|
oldString: 0\.0\.0\.0
|
||||||
|
newString: ${{steps.gitversion.outputs.SemVer}}.0
|
||||||
|
showFileContent: true
|
||||||
|
escapeBackslash: true
|
||||||
|
- name: Set version in app.manifest file
|
||||||
|
uses: Nambers/ReplaceStringInFile@v1.3
|
||||||
|
with:
|
||||||
|
path: VDownload/app.manifest
|
||||||
|
oldString: 0\.0\.0\.0
|
||||||
|
newString: ${{steps.gitversion.outputs.SemVer}}.0
|
||||||
|
showFileContent: true
|
||||||
|
escapeBackslash: true
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: 8.0.x
|
||||||
|
- name: Build application
|
||||||
|
run: dotnet build -o build
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: build
|
||||||
|
path: build
|
||||||
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## Changelog
|
||||||
|
|
||||||
|
* Migration to Windows App SDK and WinUI 3
|
||||||
|
* Code refactoring, MVVM pattern and Dependency Injection applied
|
||||||
|
* Authentication data encryption added
|
||||||
|
* Replacing native Windows media transcoding libraries with FFmpeg
|
||||||
|
* Home - Cancel all button added
|
||||||
|
* Subscriptions and Home - Error display improved
|
||||||
|
* Subscriptions - New videos counter removed
|
||||||
|
* Minor interface changes
|
||||||
30
README.md
30
README.md
@@ -10,8 +10,8 @@ VDownload is universal video downloader written in .NET/C# and Universal Windows
|
|||||||
|
|
||||||
|
|
||||||
## Download and installation
|
## Download and installation
|
||||||
- Microsoft Store
|
|
||||||
- GitHub Releases
|
- GitHub Releases
|
||||||
|
- Microsoft Store (in the future)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -19,26 +19,24 @@ VDownload is universal video downloader written in .NET/C# and Universal Windows
|
|||||||
|
|
||||||
#### Supported sources
|
#### Supported sources
|
||||||
- Twitch (VODs, clips and channels)
|
- Twitch (VODs, clips and channels)
|
||||||
- YouTube (Videos, playlists and channels)
|
|
||||||
|
|
||||||
|
|
||||||
#### Features
|
#### Features
|
||||||
|
|
||||||
- Intuitive and easy to use
|
- Intuitive and easy to use
|
||||||
- Modern GUI, consistent with Windows, touchscreen friendly - WinUI
|
- Modern GUI, consistent with Windows, touchscreen friendly
|
||||||
- Multiple video service support - one app for all
|
- Multiple video service support - one app for all
|
||||||
- Video downloading
|
- Video downloading
|
||||||
- Audio-only, video-only and audio-video downloading
|
- Audio-only, video-only and audio-video downloading
|
||||||
- Time trimming - download only the fragment you need
|
- Time trimming - download only the fragment you need
|
||||||
- Task scheduling - start downloading after a certain amount of time
|
|
||||||
- Playlist downloading
|
- Playlist downloading
|
||||||
- Playlist filtering - download only the videos you need
|
- Playlist filtering - download only the videos you need
|
||||||
- Playlist subscription - download new videos from saved playlists
|
- Playlist subscription - download new videos from saved playlists
|
||||||
- Downloading queue
|
- Downloading queue
|
||||||
- Use of native Windows media transcoding libraries - no FFmpeg included
|
- FFmpeg transcoding
|
||||||
- Highly configurable - from default video downloading parameters to transcoding algorithm
|
- Highly configurable - from default video downloading parameters to transcoding options
|
||||||
|
|
||||||
and everything for free with open source code
|
and all of this for free
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -46,26 +44,10 @@ and everything for free with open source code
|
|||||||
|
|
||||||
VDownload is a project, which I develop in my free time as a hobby and a form of learning.
|
VDownload is a project, which I develop in my free time as a hobby and a form of learning.
|
||||||
|
|
||||||
As the application is not 100% resistant to external factors (e.g. changes in APIs) and to be fair to creators and video services, it is available for free.
|
As the application is not 100% resistant to external factors (e.g. changes in services APIs) and to be fair to creators and video services, it is available for free with open source code.
|
||||||
|
|
||||||
If you want to support me and the development of the project you can:
|
If you want to support me and the development of the project you can:
|
||||||
- **Donate me** via Paypal (link in the "Sponsor this project" section in Github repository and in the app itself in the "About" page)
|
- **Donate me** via Paypal (link in the "Sponsor this project" section in Github repository and in the app itself in the "About" page)
|
||||||
- **Report bugs and errors** in the "Issues" section of Github repository
|
- **Report bugs and errors** in the "Issues" section of Github repository
|
||||||
- **Propose new features and support of video services** in the "Issues" section of Github repository
|
- **Propose new features and support of video services** in the "Issues" section of Github repository
|
||||||
- **Translate the application into a language you know**
|
- **Translate the application into a language you know**
|
||||||
|
|
||||||
|
|
||||||
#### App development status
|
|
||||||
|
|
||||||
At the moment, the application is in the **early stage of development**. Only source code is available to download. Check "App development plans" to learn about further project development plans.
|
|
||||||
|
|
||||||
|
|
||||||
#### App development plans
|
|
||||||
|
|
||||||
- **1.0-prerelease1** - with Twitch support only, all video and playlist download options and playlist subscription *[Q2 2022]*
|
|
||||||
- **1.0-prerelease2** - Youtube support and issues from 1.0-prelease1 closed *[Q3 2022]*
|
|
||||||
- **1.0** - issues from 1.0-prelease2 closed and support for Polish (and maybe German) language *[Q3 2022]*
|
|
||||||
- **1.?** - new video services and language support and closing issues from previous versions. *[?]*
|
|
||||||
- **2.0** - switch from Universal Windows Platform to Windows App SDK *[when Windows App SDK will be more developed and will offer elements which I use in UWP (e.g. Mica)]*
|
|
||||||
|
|
||||||
**dates are indicative only and may change*
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum AudioFileExtension
|
|
||||||
{
|
|
||||||
MP3 = 3,
|
|
||||||
FLAC = 4,
|
|
||||||
WAV = 5,
|
|
||||||
M4A = 6,
|
|
||||||
ALAC = 7,
|
|
||||||
WMA = 8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum DownloadTasksAddingRequestSource
|
|
||||||
{
|
|
||||||
Video,
|
|
||||||
Playlist,
|
|
||||||
Subscriptions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum MediaFileExtension
|
|
||||||
{
|
|
||||||
MP4 = VideoFileExtension.MP4,
|
|
||||||
WMV = VideoFileExtension.WMV,
|
|
||||||
HEVC = VideoFileExtension.HEVC,
|
|
||||||
MP3 = AudioFileExtension.MP3,
|
|
||||||
FLAC = AudioFileExtension.FLAC,
|
|
||||||
WAV = AudioFileExtension.WAV,
|
|
||||||
M4A = AudioFileExtension.M4A,
|
|
||||||
ALAC = AudioFileExtension.ALAC,
|
|
||||||
WMA = AudioFileExtension.WMA,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum MediaType
|
|
||||||
{
|
|
||||||
AudioVideo = 0,
|
|
||||||
OnlyAudio = 1,
|
|
||||||
OnlyVideo = 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum PlaylistSource
|
|
||||||
{
|
|
||||||
TwitchChannel,
|
|
||||||
Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum VideoFileExtension
|
|
||||||
{
|
|
||||||
MP4 = 0,
|
|
||||||
WMV = 1,
|
|
||||||
HEVC = 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace VDownload.Core.Enums
|
|
||||||
{
|
|
||||||
public enum VideoSource
|
|
||||||
{
|
|
||||||
TwitchVod,
|
|
||||||
TwitchClip,
|
|
||||||
Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class DownloadTaskStatusChangedEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, DateTime scheduledFor)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
ScheduledFor = scheduledFor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, double progress)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
if (Status == DownloadTaskStatus.Downloading)
|
|
||||||
{
|
|
||||||
DownloadingProgress = progress;
|
|
||||||
}
|
|
||||||
else if (Status == DownloadTaskStatus.Processing)
|
|
||||||
{
|
|
||||||
ProcessingProgress = progress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, TimeSpan elapsedTime)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
ElapsedTime = elapsedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadTaskStatusChangedEventArgs(DownloadTaskStatus status, Exception exception)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
Exception = exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public DownloadTaskStatus Status { get; private set; }
|
|
||||||
public DateTime ScheduledFor { get; private set; }
|
|
||||||
public double DownloadingProgress { get; private set; }
|
|
||||||
public double ProcessingProgress { get; private set; }
|
|
||||||
public TimeSpan ElapsedTime { get; private set; }
|
|
||||||
public Exception Exception { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class DownloadTasksAddingRequestedEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public DownloadTasksAddingRequestedEventArgs(DownloadTask[] downloadTasks, DownloadTasksAddingRequestSource requestSource)
|
|
||||||
{
|
|
||||||
DownloadTasks = downloadTasks;
|
|
||||||
RequestSource = requestSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public DownloadTask[] DownloadTasks { get; private set; }
|
|
||||||
public DownloadTasksAddingRequestSource RequestSource { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using VDownload.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class PlaylistSearchSuccessedEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public PlaylistSearchSuccessedEventArgs(IPlaylist playlist)
|
|
||||||
{
|
|
||||||
Playlist = playlist;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public IPlaylist Playlist { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class ProgressChangedEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public ProgressChangedEventArgs(double progress, bool isCompleted = false)
|
|
||||||
{
|
|
||||||
Progress = progress;
|
|
||||||
IsCompleted = isCompleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public double Progress { get; private set; }
|
|
||||||
public bool IsCompleted { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using VDownload.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class SubscriptionLoadSuccessedEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public SubscriptionLoadSuccessedEventArgs(IVideo[] videos)
|
|
||||||
{
|
|
||||||
Videos = videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public IVideo[] Videos { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using VDownload.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace VDownload.Core.EventArgs
|
|
||||||
{
|
|
||||||
public class VideoSearchSuccessedEventArgs : System.EventArgs
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public VideoSearchSuccessedEventArgs(IVideo video)
|
|
||||||
{
|
|
||||||
Video = video;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public IVideo Video { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Exceptions
|
|
||||||
{
|
|
||||||
public class MediaNotFoundException : Exception
|
|
||||||
{
|
|
||||||
public MediaNotFoundException() { }
|
|
||||||
public MediaNotFoundException(string message) : base(message) { }
|
|
||||||
public MediaNotFoundException(string message, Exception inner) : base(message, inner) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Exceptions
|
|
||||||
{
|
|
||||||
public class SubscriptionExistsException : Exception
|
|
||||||
{
|
|
||||||
public SubscriptionExistsException() { }
|
|
||||||
public SubscriptionExistsException(string message) : base(message) { }
|
|
||||||
public SubscriptionExistsException(string message, Exception inner) : base(message, inner) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Exceptions
|
|
||||||
{
|
|
||||||
public class TwitchAccessTokenNotFoundException : Exception
|
|
||||||
{
|
|
||||||
public TwitchAccessTokenNotFoundException() { }
|
|
||||||
public TwitchAccessTokenNotFoundException(string message) : base(message) { }
|
|
||||||
public TwitchAccessTokenNotFoundException(string message, Exception inner) :base(message, inner) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Exceptions
|
|
||||||
{
|
|
||||||
public class TwitchAccessTokenNotValidException : Exception
|
|
||||||
{
|
|
||||||
public TwitchAccessTokenNotValidException() { }
|
|
||||||
public TwitchAccessTokenNotValidException(string message) : base(message) { }
|
|
||||||
public TwitchAccessTokenNotValidException(string message, Exception inner) : base(message, inner) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Extensions
|
|
||||||
{
|
|
||||||
public static class TimeSpanExtension
|
|
||||||
{
|
|
||||||
// To string (TH:)MM:SS
|
|
||||||
public static string ToStringOptTHBaseMMSS(this TimeSpan timeSpan, params TimeSpan[] formatBase)
|
|
||||||
{
|
|
||||||
StringBuilder formattedTimeSpan = new StringBuilder();
|
|
||||||
|
|
||||||
int maxTHLength = 0;
|
|
||||||
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
|
|
||||||
{
|
|
||||||
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
|
|
||||||
if (THLength > maxTHLength)
|
|
||||||
{
|
|
||||||
maxTHLength = THLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maxTHLength > 0)
|
|
||||||
{
|
|
||||||
formattedTimeSpan.Append($"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:");
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedTimeSpan.Append(maxTHLength == 0 ? $"{timeSpan.Minutes}:" : $"{timeSpan.Minutes:00}:");
|
|
||||||
|
|
||||||
formattedTimeSpan.Append($"{timeSpan.Seconds:00}");
|
|
||||||
|
|
||||||
return formattedTimeSpan.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// To string ((TH:)MM:)SS
|
|
||||||
public static string ToStringOptTHMMBaseSS(this TimeSpan timeSpan, params TimeSpan[] formatBase)
|
|
||||||
{
|
|
||||||
StringBuilder formattedTimeSpan = new StringBuilder();
|
|
||||||
|
|
||||||
int maxTHLength = 0;
|
|
||||||
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
|
|
||||||
{
|
|
||||||
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
|
|
||||||
if (THLength > maxTHLength)
|
|
||||||
{
|
|
||||||
maxTHLength = THLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maxTHLength > 0)
|
|
||||||
{
|
|
||||||
formattedTimeSpan.Append($"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MM = false;
|
|
||||||
if (Math.Floor(timeSpan.TotalMinutes) > 0 || maxTHLength > 0)
|
|
||||||
{
|
|
||||||
formattedTimeSpan.Append(maxTHLength > 0 ? $"{timeSpan.Minutes:00}:" : $"{timeSpan.Minutes}:");
|
|
||||||
MM = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedTimeSpan.Append(MM ? $"{timeSpan.Seconds:00}" : $"{timeSpan.Seconds}");
|
|
||||||
|
|
||||||
return formattedTimeSpan.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Interfaces
|
|
||||||
{
|
|
||||||
public interface IPlaylist
|
|
||||||
{
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
PlaylistSource Source { get; }
|
|
||||||
string ID { get; }
|
|
||||||
Uri Url { get; }
|
|
||||||
string Name { get; }
|
|
||||||
IVideo[] Videos { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
Task GetVideosAsync(CancellationToken cancellationToken = default);
|
|
||||||
Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Interfaces
|
|
||||||
{
|
|
||||||
public interface IVideo
|
|
||||||
{
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
VideoSource Source { get; }
|
|
||||||
string ID { get; }
|
|
||||||
Uri Url { get; }
|
|
||||||
string Title { get; }
|
|
||||||
string Author { get; }
|
|
||||||
DateTime Date { get; }
|
|
||||||
TimeSpan Duration { get; }
|
|
||||||
long Views { get; }
|
|
||||||
Uri Thumbnail { get; }
|
|
||||||
BaseStream[] BaseStreams { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
Task GetStreamsAsync(CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region EVENTS
|
|
||||||
|
|
||||||
event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
|
||||||
event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using Windows.Media.Editing;
|
|
||||||
using Windows.Media.Transcoding;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
|
||||||
{
|
|
||||||
public static class Config
|
|
||||||
{
|
|
||||||
#region CONSTANTS
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, object> DefaultSettings = new Dictionary<string, object>()
|
|
||||||
{
|
|
||||||
{ "delete_temp_on_start", true },
|
|
||||||
{ "twitch_vod_passive_trim", true },
|
|
||||||
{ "twitch_vod_downloading_chunk_retry_after_error", false },
|
|
||||||
{ "twitch_vod_downloading_chunk_max_retries", 10 },
|
|
||||||
{ "twitch_vod_downloading_chunk_retries_delay", 5000 },
|
|
||||||
{ "media_transcoding_use_hardware_acceleration", true },
|
|
||||||
{ "media_transcoding_algorithm", (int)MediaVideoProcessingAlgorithm.MrfCrf444 },
|
|
||||||
{ "media_editing_algorithm", (int)MediaTrimmingPreference.Fast },
|
|
||||||
{ "default_max_playlist_videos", 0 },
|
|
||||||
{ "default_media_type", (int)MediaType.AudioVideo },
|
|
||||||
{ "default_filename", "[<date_pub:yyyy.MM.dd>] <title>" },
|
|
||||||
{ "default_video_extension", (int)VideoFileExtension.MP4 },
|
|
||||||
{ "default_audio_extension", (int)AudioFileExtension.MP3 },
|
|
||||||
{ "custom_media_location", false },
|
|
||||||
{ "max_active_video_task", 5 },
|
|
||||||
{ "replace_output_file_if_exists", false },
|
|
||||||
{ "remove_task_when_successfully_ended", false },
|
|
||||||
{ "delete_task_temp_when_ended_with_error", false },
|
|
||||||
{ "show_notification_when_task_ended_successfully", false },
|
|
||||||
{ "show_notification_when_task_ended_unsuccessfully", false },
|
|
||||||
{ "show_warning_when_task_starts_on_metered_network", true },
|
|
||||||
{ "delay_task_when_queued_task_starts_on_metered_network", true }
|
|
||||||
};
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public static object GetValue(string key)
|
|
||||||
{
|
|
||||||
return ApplicationData.Current.LocalSettings.Values[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetValue(string key, object value)
|
|
||||||
{
|
|
||||||
ApplicationData.Current.LocalSettings.Values[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDefault()
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<string, object> s in DefaultSettings)
|
|
||||||
{
|
|
||||||
ApplicationData.Current.LocalSettings.Values[s.Key] = s.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Rebuild()
|
|
||||||
{
|
|
||||||
foreach (KeyValuePair<string, object> s in DefaultSettings)
|
|
||||||
{
|
|
||||||
if (!ApplicationData.Current.LocalSettings.Values.ContainsKey(s.Key))
|
|
||||||
{
|
|
||||||
ApplicationData.Current.LocalSettings.Values[s.Key] = s.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.EventArgs;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
using VDownload.Core.Services;
|
|
||||||
using Windows.ApplicationModel.ExtendedExecution;
|
|
||||||
using Windows.Storage;
|
|
||||||
using Windows.Storage.AccessCache;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Structs
|
|
||||||
{
|
|
||||||
public class DownloadTask
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public DownloadTask(string id, IVideo video, MediaType mediaType, BaseStream selectedStream, TrimData trim, OutputFile file, double schedule)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
Video = video;
|
|
||||||
MediaType = mediaType;
|
|
||||||
SelectedStream = selectedStream;
|
|
||||||
Trim = trim;
|
|
||||||
File = file;
|
|
||||||
Schedule = schedule;
|
|
||||||
|
|
||||||
Status = DownloadTaskStatus.Idle;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status);
|
|
||||||
CancellationTokenSource = new CancellationTokenSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
|
||||||
public IVideo Video { get; set; }
|
|
||||||
public MediaType MediaType { get; set; }
|
|
||||||
public BaseStream SelectedStream { get; set; }
|
|
||||||
public TrimData Trim { get; set; }
|
|
||||||
public OutputFile File { get; set; }
|
|
||||||
public double Schedule { get; set; }
|
|
||||||
public DownloadTaskStatus Status { get; private set; }
|
|
||||||
public DownloadTaskStatusChangedEventArgs LastStatusChangedEventArgs { get; private set; }
|
|
||||||
public CancellationTokenSource CancellationTokenSource { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
public async Task Run(bool delayWhenOnMeteredConnection)
|
|
||||||
{
|
|
||||||
StatusChanged.Invoke(this, new DownloadTaskStatusChangedEventArgs(Status));
|
|
||||||
|
|
||||||
CancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
if (Schedule > 0)
|
|
||||||
{
|
|
||||||
DateTime scheduleFor = DateTime.Now.AddMinutes(Schedule);
|
|
||||||
Status = DownloadTaskStatus.Scheduled;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, scheduleFor);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
while (DateTime.Now < scheduleFor && !CancellationTokenSource.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Status = DownloadTaskStatus.Queued;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
await DownloadTasksCollectionManagement.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
|
|
||||||
|
|
||||||
if (!CancellationTokenSource.Token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Status = DownloadTaskStatus.Downloading;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, 0);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
|
|
||||||
StorageFolder tempFolder;
|
|
||||||
if (StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_temp_location"))
|
|
||||||
{
|
|
||||||
tempFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("custom_temp_location");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tempFolder = ApplicationData.Current.TemporaryFolder;
|
|
||||||
}
|
|
||||||
tempFolder = await tempFolder.CreateFolderAsync(Id);
|
|
||||||
|
|
||||||
bool endedWithError = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
Stopwatch taskStopwatch = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified };
|
|
||||||
await session.RequestExtensionAsync();
|
|
||||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
Video.DownloadingProgressChanged += DownloadingProgressChanged;
|
|
||||||
Video.ProcessingProgressChanged += ProcessingProgressChanged;
|
|
||||||
StorageFile tempOutputFile = await Video.DownloadAndTranscodeAsync(tempFolder, SelectedStream, File.Extension, MediaType, Trim, CancellationTokenSource.Token);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
|
|
||||||
Status = DownloadTaskStatus.Finalizing;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
|
|
||||||
StorageFile outputFile = await File.Create();
|
|
||||||
|
|
||||||
CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
|
||||||
await tempOutputFile.MoveAndReplaceAsync(outputFile);
|
|
||||||
|
|
||||||
taskStopwatch.Stop();
|
|
||||||
|
|
||||||
Status = DownloadTaskStatus.EndedSuccessfully;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, taskStopwatch.Elapsed);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
endedWithError = true;
|
|
||||||
Status = DownloadTaskStatus.EndedUnsuccessfully;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, ex);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!endedWithError || (bool)Config.GetValue("delete_task_temp_when_ended_with_error"))
|
|
||||||
{
|
|
||||||
// Delete temporary files
|
|
||||||
await tempFolder.DeleteAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Status = DownloadTaskStatus.EndedUnsuccessfully;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, new OperationCanceledException(CancellationTokenSource.Token));
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DownloadingProgressChanged(object sender, ProgressChangedEventArgs e)
|
|
||||||
{
|
|
||||||
Status = DownloadTaskStatus.Downloading;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, e.Progress);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessingProgressChanged(object sender, ProgressChangedEventArgs e)
|
|
||||||
{
|
|
||||||
Status = DownloadTaskStatus.Processing;
|
|
||||||
LastStatusChangedEventArgs = new DownloadTaskStatusChangedEventArgs(Status, e.Progress);
|
|
||||||
StatusChanged.Invoke(this, LastStatusChangedEventArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region EVENT
|
|
||||||
|
|
||||||
public event EventHandler<DownloadTaskStatusChangedEventArgs> StatusChanged;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
using Microsoft.Toolkit.Uwp.Connectivity;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
|
||||||
{
|
|
||||||
public static class DownloadTasksCollectionManagement
|
|
||||||
{
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, DownloadTask> ChangeableDownloadTasksCollection = new Dictionary<string, DownloadTask>();
|
|
||||||
public static readonly ReadOnlyDictionary<string, DownloadTask> DownloadTasksCollection = new ReadOnlyDictionary<string, DownloadTask>(ChangeableDownloadTasksCollection);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public static string GenerateID()
|
|
||||||
{
|
|
||||||
char[] idChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
|
|
||||||
int idLength = 10;
|
|
||||||
|
|
||||||
string id;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
id = "";
|
|
||||||
while (id.Length < idLength)
|
|
||||||
{
|
|
||||||
id += idChars[new Random().Next(0, idChars.Length)];
|
|
||||||
}
|
|
||||||
} while (DownloadTasksCollection.Keys.Contains(id));
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Add(DownloadTask downloadTask, string id)
|
|
||||||
{
|
|
||||||
ChangeableDownloadTasksCollection[id] = downloadTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Remove(string id)
|
|
||||||
{
|
|
||||||
ChangeableDownloadTasksCollection.Remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
while ((ChangeableDownloadTasksCollection.Values.Where((DownloadTask task) => task.Status == Enums.DownloadTaskStatus.Downloading || task.Status == Enums.DownloadTaskStatus.Processing || task.Status == Enums.DownloadTaskStatus.Finalizing).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
using Windows.Foundation;
|
|
||||||
using Windows.Media.Editing;
|
|
||||||
using Windows.Media.MediaProperties;
|
|
||||||
using Windows.Media.Transcoding;
|
|
||||||
using Windows.Storage;
|
|
||||||
using Windows.Storage.Streams;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
|
||||||
{
|
|
||||||
public class MediaProcessor
|
|
||||||
{
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public async Task Run(StorageFile mediaFile, MediaFileExtension extension, MediaType mediaType, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
|
||||||
|
|
||||||
MediaTranscoder mediaTranscoder = new MediaTranscoder
|
|
||||||
{
|
|
||||||
HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"),
|
|
||||||
VideoProcessingAlgorithm = (MediaVideoProcessingAlgorithm)Config.GetValue("media_transcoding_algorithm"),
|
|
||||||
};
|
|
||||||
if (trim.Start != null) mediaTranscoder.TrimStartTime = trim.Start;
|
|
||||||
if (trim.End != null) mediaTranscoder.TrimStopTime = trim.End;
|
|
||||||
|
|
||||||
using (IRandomAccessStream openedOutputFile = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
|
|
||||||
{
|
|
||||||
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await mediaFile.OpenAsync(FileAccessMode.Read), openedOutputFile, await GetMediaEncodingProfile(mediaFile, extension, mediaType));
|
|
||||||
|
|
||||||
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
|
|
||||||
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(percent)); }));
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
await openedOutputFile.FlushAsync();
|
|
||||||
transcodingTask.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
|
||||||
}
|
|
||||||
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
|
||||||
|
|
||||||
MediaComposition mediaEditor = new MediaComposition();
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
Task<MediaClip> getVideoFileTask = MediaClip.CreateFromFileAsync(videoFile).AsTask();
|
|
||||||
Task<BackgroundAudioTrack> getAudioFileTask = BackgroundAudioTrack.CreateFromFileAsync(audioFile).AsTask();
|
|
||||||
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
|
|
||||||
|
|
||||||
MediaClip videoElement = getVideoFileTask.Result;
|
|
||||||
if (trim.Start != null) videoElement.TrimTimeFromStart = trim.Start;
|
|
||||||
if (trim.End != null) videoElement.TrimTimeFromEnd = trim.End;
|
|
||||||
BackgroundAudioTrack audioElement = getAudioFileTask.Result;
|
|
||||||
if (trim.Start != null) audioElement.TrimTimeFromStart = trim.Start;
|
|
||||||
if (trim.End != null) audioElement.TrimTimeFromEnd = trim.End;
|
|
||||||
|
|
||||||
mediaEditor.Clips.Add(videoElement);
|
|
||||||
mediaEditor.BackgroundAudioTracks.Add(audioElement);
|
|
||||||
|
|
||||||
var renderOperation = mediaEditor.RenderToFileAsync(outputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
|
|
||||||
renderOperation.Progress += (info, progress) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(progress)); };
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
await renderOperation.AsTask(cancellationToken);
|
|
||||||
|
|
||||||
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
|
||||||
}
|
|
||||||
public async Task Run(StorageFile audioFile, AudioFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, outputFile, trim, cancellationToken);
|
|
||||||
}
|
|
||||||
public async Task Run(StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, outputFile, trim, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PRIVATE METHODS
|
|
||||||
|
|
||||||
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
|
|
||||||
{
|
|
||||||
MediaEncodingProfile profile;
|
|
||||||
|
|
||||||
switch (extension)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
case MediaFileExtension.MP4: profile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p); break;
|
|
||||||
case MediaFileExtension.WMV: profile = MediaEncodingProfile.CreateWmv(VideoEncodingQuality.HD1080p); break;
|
|
||||||
case MediaFileExtension.HEVC: profile = MediaEncodingProfile.CreateHevc(VideoEncodingQuality.HD1080p); break;
|
|
||||||
case MediaFileExtension.MP3: profile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High); break;
|
|
||||||
case MediaFileExtension.FLAC: profile = MediaEncodingProfile.CreateFlac(AudioEncodingQuality.High); break;
|
|
||||||
case MediaFileExtension.WAV: profile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High); break;
|
|
||||||
case MediaFileExtension.M4A: profile = MediaEncodingProfile.CreateM4a(AudioEncodingQuality.High); break;
|
|
||||||
case MediaFileExtension.ALAC: profile = MediaEncodingProfile.CreateAlac(AudioEncodingQuality.High); break;
|
|
||||||
case MediaFileExtension.WMA: profile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaType != MediaType.OnlyAudio)
|
|
||||||
{
|
|
||||||
var videoData = await videoFile.Properties.GetVideoPropertiesAsync();
|
|
||||||
profile.Video.Height = videoData.Height;
|
|
||||||
profile.Video.Width = videoData.Width;
|
|
||||||
profile.Video.Bitrate = videoData.Bitrate;
|
|
||||||
}
|
|
||||||
if (mediaType != MediaType.OnlyVideo)
|
|
||||||
{
|
|
||||||
var audioData = await audioFile.Properties.GetMusicPropertiesAsync();
|
|
||||||
profile.Audio.Bitrate = audioData.Bitrate;
|
|
||||||
if (mediaType == MediaType.AudioVideo) profile.Video.Bitrate -= audioData.Bitrate;
|
|
||||||
}
|
|
||||||
if (mediaType == MediaType.OnlyVideo)
|
|
||||||
{
|
|
||||||
var audioTracks = profile.GetAudioTracks();
|
|
||||||
audioTracks.Clear();
|
|
||||||
profile.SetAudioTracks(audioTracks.AsEnumerable());
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType)
|
|
||||||
{
|
|
||||||
return await GetMediaEncodingProfile(audioVideoFile, audioVideoFile, extension, mediaType);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region EVENTS
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProgressChanged;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
|
||||||
{
|
|
||||||
public class OutputFile
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public OutputFile(string name, MediaFileExtension extension, StorageFolder location)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Extension = extension;
|
|
||||||
Location = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputFile(string name, MediaFileExtension extension)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Extension = extension;
|
|
||||||
Location = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public string Name { get; private set; }
|
|
||||||
public MediaFileExtension Extension { get; private set; }
|
|
||||||
public StorageFolder Location { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public async Task<StorageFile> Create()
|
|
||||||
{
|
|
||||||
string filename = $"{Name}.{Extension.ToString().ToLower()}";
|
|
||||||
CreationCollisionOption collisionOption = (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName;
|
|
||||||
return await(!(Location is null) ? Location.CreateFileAsync(filename, collisionOption) : DownloadsFolder.CreateFileAsync(filename, collisionOption));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetPath() => $@"{(Location != null ? Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Name}.{Extension.ToString().ToLower()}";
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public class Subscription
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public Subscription(IPlaylist playlist)
|
|
||||||
{
|
|
||||||
Playlist = playlist;
|
|
||||||
SavedVideos = Playlist.Videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public IPlaylist Playlist { get; private set; }
|
|
||||||
public IVideo[] SavedVideos { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public async Task<IVideo[]> GetNewVideosAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Playlist.GetVideosAsync(cancellationToken);
|
|
||||||
return GetUnsavedVideos();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IVideo[]> GetNewVideosAndUpdateAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await Playlist.GetVideosAsync(cancellationToken);
|
|
||||||
IVideo[] newVideos = GetUnsavedVideos();
|
|
||||||
SavedVideos = Playlist.Videos;
|
|
||||||
return newVideos;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PRIVATE METHODS
|
|
||||||
|
|
||||||
private IVideo[] GetUnsavedVideos()
|
|
||||||
{
|
|
||||||
List<IVideo> newVideos = Playlist.Videos.ToList();
|
|
||||||
foreach (IVideo savedVideo in SavedVideos)
|
|
||||||
{
|
|
||||||
newVideos.RemoveAll((v) => v.Source == savedVideo.Source && v.ID == savedVideo.ID);
|
|
||||||
}
|
|
||||||
return newVideos.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Exceptions;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services
|
|
||||||
{
|
|
||||||
public static class SubscriptionsCollectionManagement
|
|
||||||
{
|
|
||||||
#region CONSTANTS
|
|
||||||
|
|
||||||
private static readonly StorageFolder SubscriptionFolderLocation = ApplicationData.Current.LocalFolder;
|
|
||||||
private static readonly string SubscriptionsFolderName = "Subscriptions";
|
|
||||||
private static readonly string SubscriptionFileExtension = "vsub";
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public static async Task<(Subscription Subscription, StorageFile SubscriptionFile)[]> GetSubscriptionsAsync()
|
|
||||||
{
|
|
||||||
List<(Subscription Subscription, StorageFile SubscriptionFile)> subscriptions = new List<(Subscription Subscription,StorageFile SubscriptionFile)> ();
|
|
||||||
StorageFolder subscriptionsFolder = await SubscriptionFolderLocation.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
|
|
||||||
BinaryFormatter formatter = new BinaryFormatter();
|
|
||||||
foreach (StorageFile file in await subscriptionsFolder.GetFilesAsync())
|
|
||||||
{
|
|
||||||
if (file.Name.EndsWith(SubscriptionFileExtension))
|
|
||||||
{
|
|
||||||
Stream fileStream = await file.OpenStreamForReadAsync();
|
|
||||||
Subscription subscription = (Subscription)formatter.Deserialize(fileStream);
|
|
||||||
subscriptions.Add((subscription, file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return subscriptions.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<StorageFile> CreateSubscriptionFileAsync(Subscription subscription)
|
|
||||||
{
|
|
||||||
StorageFolder subscriptionsFolder = await SubscriptionFolderLocation.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
StorageFile subscriptionFile = await subscriptionsFolder.CreateFileAsync($"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}.{SubscriptionFileExtension}", CreationCollisionOption.FailIfExists);
|
|
||||||
BinaryFormatter formatter = new BinaryFormatter();
|
|
||||||
Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync();
|
|
||||||
formatter.Serialize(subscriptionFileStream, subscription);
|
|
||||||
return subscriptionFile;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if ((uint)ex.HResult == 0x800700B7)
|
|
||||||
{
|
|
||||||
throw new SubscriptionExistsException($"Subscription with id \"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}\" already exists");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task UpdateSubscriptionFileAsync(Subscription subscription, StorageFile subscriptionFile)
|
|
||||||
{
|
|
||||||
BinaryFormatter formatter = new BinaryFormatter();
|
|
||||||
Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync();
|
|
||||||
formatter.Serialize(subscriptionFileStream, subscription);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task DeleteSubscriptionFileAsync(StorageFile subscriptionFile) => await subscriptionFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Sources
|
|
||||||
{
|
|
||||||
internal static class AuthorizationData
|
|
||||||
{
|
|
||||||
internal static StorageFolder FolderLocation = ApplicationData.Current.LocalCacheFolder;
|
|
||||||
internal static string FolderName = "AuthData";
|
|
||||||
internal static string FilesExtension = "auth";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources
|
|
||||||
{
|
|
||||||
public static class Source
|
|
||||||
{
|
|
||||||
#region CONSTANTS
|
|
||||||
|
|
||||||
// VIDEO SOURCES REGULAR EXPRESSIONS
|
|
||||||
private static readonly (Regex Regex, VideoSource Type)[] VideoSources = new (Regex Regex, VideoSource Type)[]
|
|
||||||
{
|
|
||||||
(new Regex(@"^https://www.twitch.tv/videos/(?<id>\d+)"), VideoSource.TwitchVod),
|
|
||||||
(new Regex(@"^https://www.twitch.tv/\S+/clip/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
|
||||||
(new Regex(@"^https://clips.twitch.tv/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
|
||||||
};
|
|
||||||
|
|
||||||
// PLAYLIST SOURCES REGULAR EXPRESSIONS
|
|
||||||
private static readonly (Regex Regex, PlaylistSource Type)[] PlaylistSources = new (Regex Regex, PlaylistSource Type)[]
|
|
||||||
{
|
|
||||||
(new Regex(@"^https://www.twitch.tv/(?<id>[^?/]+)"), PlaylistSource.TwitchChannel),
|
|
||||||
};
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
// GET VIDEO SOURCE
|
|
||||||
public static IVideo GetVideo(string url)
|
|
||||||
{
|
|
||||||
VideoSource source = VideoSource.Null;
|
|
||||||
string id = string.Empty;
|
|
||||||
foreach ((Regex Regex, VideoSource Type) Source in VideoSources)
|
|
||||||
{
|
|
||||||
Match sourceMatch = Source.Regex.Match(url);
|
|
||||||
if (sourceMatch.Success)
|
|
||||||
{
|
|
||||||
source = Source.Type;
|
|
||||||
id = sourceMatch.Groups["id"].Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GetVideo(source, id);
|
|
||||||
}
|
|
||||||
public static IVideo GetVideo(VideoSource source, string id)
|
|
||||||
{
|
|
||||||
IVideo videoService = null;
|
|
||||||
switch (source)
|
|
||||||
{
|
|
||||||
case VideoSource.TwitchVod: videoService = new Twitch.Vod(id); break;
|
|
||||||
case VideoSource.TwitchClip: videoService = new Twitch.Clip(id); break;
|
|
||||||
}
|
|
||||||
return videoService;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET PLAYLIST SOURCE
|
|
||||||
public static IPlaylist GetPlaylist(string url)
|
|
||||||
{
|
|
||||||
PlaylistSource source = PlaylistSource.Null;
|
|
||||||
string id = string.Empty;
|
|
||||||
foreach ((Regex Regex, PlaylistSource Type) Source in PlaylistSources)
|
|
||||||
{
|
|
||||||
Match sourceMatch = Source.Regex.Match(url);
|
|
||||||
if (sourceMatch.Success)
|
|
||||||
{
|
|
||||||
source = Source.Type;
|
|
||||||
id = sourceMatch.Groups["id"].Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GetPlaylist(source, id);
|
|
||||||
}
|
|
||||||
public static IPlaylist GetPlaylist(PlaylistSource source, string id)
|
|
||||||
{
|
|
||||||
IPlaylist playlistService = null;
|
|
||||||
switch (source)
|
|
||||||
{
|
|
||||||
case PlaylistSource.TwitchChannel: playlistService = new Twitch.Channel(id); break;
|
|
||||||
}
|
|
||||||
return playlistService;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Exceptions;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public class Channel : IPlaylist
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public Channel(string id)
|
|
||||||
{
|
|
||||||
Source = PlaylistSource.TwitchChannel;
|
|
||||||
ID = id;
|
|
||||||
Url = new Uri($"https://twitch.tv/{ID}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public PlaylistSource Source { get; private set; }
|
|
||||||
public string ID { get; private set; }
|
|
||||||
private string UniqueID { get; set; }
|
|
||||||
public Uri Url { get; private set; }
|
|
||||||
public string Name { get; private set; }
|
|
||||||
public IVideo[] Videos { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
JToken response = null;
|
|
||||||
using (WebClient client = await Client.Helix())
|
|
||||||
{
|
|
||||||
client.QueryString.Add("login", ID);
|
|
||||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/users"))["data"];
|
|
||||||
if (((JArray)response).Count > 0) response = response[0];
|
|
||||||
else throw new MediaNotFoundException($"Twitch Channel (ID: {ID}) was not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
UniqueID = (string)response["id"];
|
|
||||||
Name = (string)response["display_name"];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GetVideosAsync(CancellationToken cancellationToken = default) => await GetVideosAsync(0, cancellationToken);
|
|
||||||
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
string pagination = "";
|
|
||||||
|
|
||||||
List<Vod> videos = new List<Vod>();
|
|
||||||
|
|
||||||
bool getAll = numberOfVideos == 0;
|
|
||||||
int count;
|
|
||||||
JToken[] videosData;
|
|
||||||
List<Task> getStreamsTasks = new List<Task>();
|
|
||||||
do
|
|
||||||
{
|
|
||||||
count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100;
|
|
||||||
|
|
||||||
JToken response = null;
|
|
||||||
using (WebClient client = await Client.Helix())
|
|
||||||
{
|
|
||||||
client.QueryString.Add("user_id", UniqueID);
|
|
||||||
client.QueryString.Add("first", count.ToString());
|
|
||||||
client.QueryString.Add("after", pagination);
|
|
||||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
|
|
||||||
}
|
|
||||||
|
|
||||||
pagination = (string)response["pagination"]["cursor"];
|
|
||||||
|
|
||||||
videosData = response["data"].ToArray();
|
|
||||||
foreach (JToken videoData in videosData)
|
|
||||||
{
|
|
||||||
Vod video = new Vod((string)videoData["id"]);
|
|
||||||
video.GetMetadataAsync(videoData);
|
|
||||||
getStreamsTasks.Add(video.GetStreamsAsync());
|
|
||||||
videos.Add(video);
|
|
||||||
|
|
||||||
numberOfVideos--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while ((getAll || numberOfVideos > 0) && count == videosData.Length);
|
|
||||||
|
|
||||||
await Task.WhenAll(getStreamsTasks);
|
|
||||||
|
|
||||||
Videos = videos.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Exceptions;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public class Clip : IVideo
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public Clip(string id)
|
|
||||||
{
|
|
||||||
Source = VideoSource.TwitchClip;
|
|
||||||
ID = id;
|
|
||||||
Url = new Uri($"https://clips.twitch.tv/{ID}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public VideoSource Source { get; private set; }
|
|
||||||
public string ID { get; private set; }
|
|
||||||
public Uri Url { get; private set; }
|
|
||||||
public string Title { get; private set; }
|
|
||||||
public string Author { get; private set; }
|
|
||||||
public DateTime Date { get; private set; }
|
|
||||||
public TimeSpan Duration { get; private set; }
|
|
||||||
public long Views { get; private set; }
|
|
||||||
public Uri Thumbnail { get; private set; }
|
|
||||||
public BaseStream[] BaseStreams { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
JToken response = null;
|
|
||||||
using (WebClient client = await Client.Helix())
|
|
||||||
{
|
|
||||||
client.QueryString.Add("id", ID);
|
|
||||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/clips")).GetValue("data");
|
|
||||||
if (((JArray)response).Count > 0) response = response[0];
|
|
||||||
else throw new MediaNotFoundException($"Twitch Clip (ID: {ID}) was not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
Title = (string)response["title"];
|
|
||||||
Author = (string)response["broadcaster_name"];
|
|
||||||
Date = Convert.ToDateTime(response["created_at"]);
|
|
||||||
Duration = TimeSpan.FromSeconds((double)response["duration"]);
|
|
||||||
Views = (long)response["view_count"];
|
|
||||||
Thumbnail = new Uri((string)response["thumbnail_url"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
JToken[] response;
|
|
||||||
using (WebClient client = Client.GQL())
|
|
||||||
{
|
|
||||||
response = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["videoQualities"].ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BaseStream> streams = new List<BaseStream>();
|
|
||||||
|
|
||||||
foreach (JToken streamData in response)
|
|
||||||
{
|
|
||||||
BaseStream stream = new BaseStream()
|
|
||||||
{
|
|
||||||
Url = new Uri((string)streamData["sourceURL"]),
|
|
||||||
Height = int.Parse((string)streamData["quality"]),
|
|
||||||
FrameRate = (int)streamData["frameRate"],
|
|
||||||
};
|
|
||||||
|
|
||||||
streams.Add(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseStreams = streams.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
|
|
||||||
|
|
||||||
JToken videoAccessToken = null;
|
|
||||||
using (WebClient client = Client.GQL())
|
|
||||||
{
|
|
||||||
videoAccessToken = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["playbackAccessToken"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.mp4");
|
|
||||||
using (WebClient client = new WebClient())
|
|
||||||
{
|
|
||||||
client.DownloadProgressChanged += (s, a) => { DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(a.ProgressPercentage)); };
|
|
||||||
client.QueryString.Add("sig", (string)videoAccessToken["signature"]);
|
|
||||||
client.QueryString.Add("token", HttpUtility.UrlEncode((string)videoAccessToken["value"]));
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
using (cancellationToken.Register(client.CancelAsync))
|
|
||||||
{
|
|
||||||
await client.DownloadFileTaskAsync(baseStream.Url, rawFile.Path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
|
||||||
|
|
||||||
// Processing
|
|
||||||
StorageFile outputFile = rawFile;
|
|
||||||
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trim.Start != null || trim.End != null)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
|
||||||
|
|
||||||
MediaProcessor mediaProcessor = new MediaProcessor();
|
|
||||||
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
|
|
||||||
|
|
||||||
await mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trim, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region EVENTS
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
|
||||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Sources;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch.Helpers
|
|
||||||
{
|
|
||||||
public static class Authorization
|
|
||||||
{
|
|
||||||
#region CONSTANTS
|
|
||||||
|
|
||||||
public readonly static string ClientID = "yukkqkwp61wsv3u1pya17crpyaa98y";
|
|
||||||
public readonly static string GQLApiClientID = "kimne78kx3ncx6brgo4mv6wki5h1ko";
|
|
||||||
public readonly static Uri RedirectUrl = new Uri("https://www.vd.com");
|
|
||||||
|
|
||||||
private readonly static string ResponseType = "token";
|
|
||||||
private readonly static string[] Scopes = new[]
|
|
||||||
{
|
|
||||||
"user:read:subscriptions",
|
|
||||||
};
|
|
||||||
public readonly static Uri AuthorizationUrl = new Uri($"https://id.twitch.tv/oauth2/authorize?client_id={ClientID}&redirect_uri={RedirectUrl.OriginalString}&response_type={ResponseType}&scope={string.Join(" ", Scopes)}");
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
public static async Task<string> ReadAccessTokenAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.GetFolderAsync(AuthorizationData.FolderName);
|
|
||||||
StorageFile authDataFile = await authDataFolder.GetFileAsync($"Twitch.{AuthorizationData.FilesExtension}");
|
|
||||||
|
|
||||||
return await FileIO.ReadTextAsync(authDataFile);
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task SaveAccessTokenAsync(string accessToken)
|
|
||||||
{
|
|
||||||
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.CreateFolderAsync(AuthorizationData.FolderName, CreationCollisionOption.OpenIfExists);
|
|
||||||
StorageFile authDataFile = await authDataFolder.CreateFileAsync($"Twitch.{AuthorizationData.FilesExtension}", CreationCollisionOption.ReplaceExisting);
|
|
||||||
|
|
||||||
await FileIO.WriteTextAsync(authDataFile, accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task DeleteAccessTokenAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.GetFolderAsync(AuthorizationData.FolderName);
|
|
||||||
StorageFile authDataFile = await authDataFolder.GetFileAsync($"Twitch.{AuthorizationData.FilesExtension}");
|
|
||||||
|
|
||||||
await authDataFile.DeleteAsync();
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<TwitchAccessTokenValidationData> ValidateAccessTokenAsync(string accessToken)
|
|
||||||
{
|
|
||||||
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JObject response = JObject.Parse(await client.DownloadStringTaskAsync("https://id.twitch.tv/oauth2/validate"));
|
|
||||||
|
|
||||||
string login = response["login"].ToString();
|
|
||||||
DateTime? expirationDate = DateTime.Now.AddSeconds(long.Parse(response["expires_in"].ToString()));
|
|
||||||
|
|
||||||
return new TwitchAccessTokenValidationData(accessToken, true, login, expirationDate);
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
if (ex.Response is null)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JObject exInfo = JObject.Parse(new StreamReader(ex.Response.GetResponseStream()).ReadToEnd());
|
|
||||||
if ((int)exInfo["status"] == 401) return new TwitchAccessTokenValidationData(accessToken, false, null, null);
|
|
||||||
else throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task RevokeAccessTokenAsync(string accessToken)
|
|
||||||
{
|
|
||||||
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
|
|
||||||
|
|
||||||
await client.UploadStringTaskAsync(new Uri("https://id.twitch.tv/oauth2/revoke"), $"client_id={ClientID}&token={accessToken}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Exceptions;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch.Helpers
|
|
||||||
{
|
|
||||||
internal static class Client
|
|
||||||
{
|
|
||||||
internal static async Task<WebClient> Helix()
|
|
||||||
{
|
|
||||||
string accessToken = await Authorization.ReadAccessTokenAsync();
|
|
||||||
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
|
|
||||||
|
|
||||||
var twitchAccessTokenValidation = await Authorization.ValidateAccessTokenAsync(accessToken);
|
|
||||||
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
|
|
||||||
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {accessToken}");
|
|
||||||
client.Headers.Add("Client-Id", Authorization.ClientID);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static WebClient GQL()
|
|
||||||
{
|
|
||||||
WebClient client = new WebClient();
|
|
||||||
client.Headers.Add("Client-Id", Authorization.GQLApiClientID);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VDownload.Core.Enums;
|
|
||||||
using VDownload.Core.Exceptions;
|
|
||||||
using VDownload.Core.Interfaces;
|
|
||||||
using VDownload.Core.Services.Sources.Twitch.Helpers;
|
|
||||||
using VDownload.Core.Structs;
|
|
||||||
using Windows.Storage;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Services.Sources.Twitch
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public class Vod : IVideo
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public Vod(string id)
|
|
||||||
{
|
|
||||||
Source = VideoSource.TwitchVod;
|
|
||||||
ID = id;
|
|
||||||
Url = new Uri($"https://www.twitch.tv/videos/{ID}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public VideoSource Source { get; private set; }
|
|
||||||
public string ID { get; private set; }
|
|
||||||
public Uri Url { get; private set; }
|
|
||||||
public string Title { get; private set; }
|
|
||||||
public string Author { get; private set; }
|
|
||||||
public DateTime Date { get; private set; }
|
|
||||||
public TimeSpan Duration { get; private set; }
|
|
||||||
public long Views { get; private set; }
|
|
||||||
public Uri Thumbnail { get; private set; }
|
|
||||||
public BaseStream[] BaseStreams { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
|
||||||
|
|
||||||
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
JToken response = null;
|
|
||||||
using (WebClient client = await Client.Helix())
|
|
||||||
{
|
|
||||||
client.QueryString.Add("id", ID);
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos")).GetValue("data")[0];
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
if (ex.Response != null && new StreamReader(ex.Response.GetResponseStream()).ReadToEnd().Contains("Not Found")) throw new MediaNotFoundException($"Twitch VOD (ID: {ID}) was not found");
|
|
||||||
else if (ex.Response != null && new StreamReader(ex.Response.GetResponseStream()).ReadToEnd() == string.Empty && ex.Message.Contains("400")) throw new MediaNotFoundException($"Twitch VOD (ID: {ID}) was not found");
|
|
||||||
else throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GetMetadataAsync(response);
|
|
||||||
}
|
|
||||||
internal void GetMetadataAsync(JToken response)
|
|
||||||
{
|
|
||||||
Title = ((string)response["title"]).Replace("\n", "");
|
|
||||||
Author = (string)response["user_name"];
|
|
||||||
Date = Convert.ToDateTime(response["created_at"]);
|
|
||||||
Duration = ParseDuration((string)response["duration"]);
|
|
||||||
Views = (long)response["view_count"];
|
|
||||||
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
string[] response = null;
|
|
||||||
using (WebClient client = Client.GQL())
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
JToken videoAccessToken = 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\"}}"))["data"]["videoPlaybackAccessToken"];
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<BaseStream> streams = new List<BaseStream>();
|
|
||||||
|
|
||||||
Regex streamDataL2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""\S+,\S+"",RESOLUTION=\d+x(?<height>\d+),VIDEO=""\w+""(,FRAME-RATE=(?<frame_rate>\d+.\d+))?");
|
|
||||||
|
|
||||||
for (int i = 2; i < response.Length; i += 3)
|
|
||||||
{
|
|
||||||
Match line2 = streamDataL2Regex.Match(response[i + 1]);
|
|
||||||
|
|
||||||
BaseStream stream = new BaseStream()
|
|
||||||
{
|
|
||||||
Url = new Uri(response[i + 2]),
|
|
||||||
Height = int.Parse(line2.Groups["height"].Value),
|
|
||||||
FrameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
streams.Add(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseStreams = streams.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
DownloadingProgressChanged.Invoke(this, new EventArgs.ProgressChangedEventArgs(0));
|
|
||||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
|
|
||||||
|
|
||||||
TimeSpan duration = Duration;
|
|
||||||
|
|
||||||
// Passive trim
|
|
||||||
if ((bool)Config.GetValue("twitch_vod_passive_trim") && trim.Start != TimeSpan.Zero && trim.End != duration) (trim, duration) = PassiveVideoTrim(chunksList, trim, Duration);
|
|
||||||
|
|
||||||
// Download
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
StorageFile rawFile = await downloadingFolder.CreateFileAsync("raw.ts");
|
|
||||||
|
|
||||||
double chunksDownloaded = 0;
|
|
||||||
|
|
||||||
Task<byte[]> downloadTask;
|
|
||||||
Task writeTask;
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
downloadTask = DownloadChunkAsync(chunksList[0].ChunkUrl);
|
|
||||||
await downloadTask;
|
|
||||||
for (int i = 1; i < chunksList.Count; i++)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
writeTask = WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
|
||||||
downloadTask = DownloadChunkAsync(chunksList[i].ChunkUrl);
|
|
||||||
await Task.WhenAll(writeTask, downloadTask);
|
|
||||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(++chunksDownloaded * 100 / chunksList.Count));
|
|
||||||
}
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
await WriteChunkToFileAsync(rawFile, downloadTask.Result);
|
|
||||||
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
|
|
||||||
|
|
||||||
// Processing
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
StorageFile outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
|
|
||||||
|
|
||||||
MediaProcessor mediaProcessor = new MediaProcessor();
|
|
||||||
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
|
|
||||||
|
|
||||||
await mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trim, cancellationToken);
|
|
||||||
|
|
||||||
// Return output file
|
|
||||||
return outputFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PRIVATE METHODS
|
|
||||||
|
|
||||||
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
string response = null;
|
|
||||||
using (WebClient client = Client.GQL())
|
|
||||||
{
|
|
||||||
response = await client.DownloadStringTaskAsync(streamUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
|
|
||||||
|
|
||||||
Regex chunkDataRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
|
|
||||||
|
|
||||||
string chunkLocationPath = streamUrl.AbsoluteUri.Replace(Path.GetFileName(streamUrl.AbsoluteUri), "");
|
|
||||||
|
|
||||||
foreach (Match chunk in chunkDataRegex.Matches(response))
|
|
||||||
{
|
|
||||||
Uri chunkUrl = new Uri($"{chunkLocationPath}{chunk.Groups["filename"].Value}");
|
|
||||||
TimeSpan chunkDuration = TimeSpan.FromSeconds(double.Parse(chunk.Groups["duration"].Value));
|
|
||||||
chunks.Add((chunkUrl, chunkDuration));
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (TrimData Trim, TimeSpan NewDuration) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TrimData trim, TimeSpan duration)
|
|
||||||
{
|
|
||||||
TimeSpan newDuration = duration;
|
|
||||||
|
|
||||||
while (chunksList[0].ChunkDuration <= trim.Start)
|
|
||||||
{
|
|
||||||
trim.Start = trim.Start.Subtract(chunksList[0].ChunkDuration);
|
|
||||||
trim.End = trim.End.Subtract(chunksList[0].ChunkDuration);
|
|
||||||
newDuration = newDuration.Subtract(chunksList[0].ChunkDuration);
|
|
||||||
chunksList.RemoveAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (chunksList.Last().ChunkDuration <= newDuration.Subtract(trim.End))
|
|
||||||
{
|
|
||||||
newDuration = newDuration.Subtract(chunksList.Last().ChunkDuration);
|
|
||||||
chunksList.RemoveAt(chunksList.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (trim, newDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task<byte[]> DownloadChunkAsync(Uri chunkUrl, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
int retriesCount = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (WebClient client = new WebClient())
|
|
||||||
{
|
|
||||||
return await client.DownloadDataTaskAsync(chunkUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (WebException wex)
|
|
||||||
{
|
|
||||||
if ((bool)Config.GetValue("twitch_vod_downloading_chunk_retry_after_error") && retriesCount < (int)Config.GetValue("twitch_vod_downloading_chunk_max_retries"))
|
|
||||||
{
|
|
||||||
retriesCount++;
|
|
||||||
await Task.Delay((int)Config.GetValue("twitch_vod_downloading_chunk_retries_delay"));
|
|
||||||
}
|
|
||||||
else throw wex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Task WriteChunkToFileAsync(StorageFile file, byte[] chunk)
|
|
||||||
{
|
|
||||||
return Task.Factory.StartNew(() =>
|
|
||||||
{
|
|
||||||
using (var stream = new FileStream(file.Path, FileMode.Append))
|
|
||||||
{
|
|
||||||
stream.Write(chunk, 0, chunk.Length);
|
|
||||||
stream.Close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TimeSpan ParseDuration(string duration)
|
|
||||||
{
|
|
||||||
char[] separators = { 'h', 'm', 's' };
|
|
||||||
string[] durationParts = duration.Split(separators, StringSplitOptions.RemoveEmptyEntries).Reverse().ToArray();
|
|
||||||
|
|
||||||
TimeSpan timeSpan = new TimeSpan(
|
|
||||||
durationParts.Count() > 2 ? int.Parse(durationParts[2]) : 0,
|
|
||||||
durationParts.Count() > 1 ? int.Parse(durationParts[1]) : 0,
|
|
||||||
int.Parse(durationParts[0]));
|
|
||||||
|
|
||||||
return timeSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region EVENTS
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
|
||||||
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Structs
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public struct BaseStream
|
|
||||||
{
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public Uri Url { get; set; }
|
|
||||||
public int Height { get; set; }
|
|
||||||
public int FrameRate { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
|
||||||
|
|
||||||
public override string ToString() => $"{Height}p{(FrameRate > 0 ? FrameRate.ToString() : "N/A")}";
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace VDownload.Core.Structs
|
|
||||||
{
|
|
||||||
public struct TwitchAccessTokenValidationData
|
|
||||||
{
|
|
||||||
#region CONSTRUCTORS
|
|
||||||
|
|
||||||
public TwitchAccessTokenValidationData(string accessToken, bool isValid, string login, DateTime? expirationDate)
|
|
||||||
{
|
|
||||||
AccessToken = accessToken;
|
|
||||||
IsValid = isValid;
|
|
||||||
Login = login;
|
|
||||||
ExpirationDate = expirationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TwitchAccessTokenValidationData Null = new TwitchAccessTokenValidationData(string.Empty, false, string.Empty, null);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PROPERTIES
|
|
||||||
|
|
||||||
public string AccessToken { get; private set; }
|
|
||||||
public bool IsValid { get; private set; }
|
|
||||||
public string Login { get; private set; }
|
|
||||||
public DateTime? ExpirationDate { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
VDownload.Core/VDownload.Core.Strings/StringResource.cs
Normal file
41
VDownload.Core/VDownload.Core.Strings/StringResource.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using Windows.ApplicationModel.Resources;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Strings
|
||||||
|
{
|
||||||
|
public class StringResource
|
||||||
|
{
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected ResourceLoader _resourceLoader;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
internal StringResource(ResourceLoader resourceLoader)
|
||||||
|
{
|
||||||
|
_resourceLoader = resourceLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public string Get(string key)
|
||||||
|
{
|
||||||
|
return _resourceLoader.GetString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Windows.ApplicationModel.Resources;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Strings
|
||||||
|
{
|
||||||
|
public static class StringResourcesManager
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public static StringResource BaseView { get; } = BuildResource("BaseViewResources");
|
||||||
|
public static StringResource HomeView { get; } = BuildResource("HomeViewResources");
|
||||||
|
public static StringResource HomeVideoView { get; } = BuildResource("HomeVideoViewResources");
|
||||||
|
public static StringResource HomeVideoCollectionView { get; } = BuildResource("HomeVideoCollectionViewResources");
|
||||||
|
public static StringResource HomeDownloadsView { get; } = BuildResource("HomeDownloadsViewResources");
|
||||||
|
public static StringResource AuthenticationView { get; } = BuildResource("AuthenticationViewResources");
|
||||||
|
public static StringResource Notifications { get; } = BuildResource("NotificationsResources");
|
||||||
|
public static StringResource Search { get; } = BuildResource("SearchResources");
|
||||||
|
public static StringResource Common { get; } = BuildResource("CommonResources");
|
||||||
|
public static StringResource DialogButtons { get; } = BuildResource("DialogButtonsResources");
|
||||||
|
public static StringResource SettingsView { get; } = BuildResource("SettingsViewResources");
|
||||||
|
public static StringResource FilenameTemplate { get; } = BuildResource("FilenameTemplateResources");
|
||||||
|
public static StringResource AboutView { get; } = BuildResource("AboutViewResources");
|
||||||
|
public static StringResource SubscriptionsView { get; } = BuildResource("SubscriptionsViewResources");
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private static StringResource BuildResource(string resourceName)
|
||||||
|
{
|
||||||
|
File.AppendAllText("C:\\Users\\mateusz\\Desktop\\test.txt", $"teststring {resourceName}\n");
|
||||||
|
ResourceLoader loader;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loader = new ResourceLoader($"VDownload.Core.Strings/{resourceName}");
|
||||||
|
File.AppendAllText("C:\\Users\\mateusz\\Desktop\\test.txt", $"afterteststring {resourceName}\n");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
|
||||||
|
File.AppendAllText("C:\\Users\\mateusz\\Desktop\\test.txt", $"teststringerror {e.Message}\n");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return new StringResource(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Developers.Text" xml:space="preserve">
|
||||||
|
<value>Developers</value>
|
||||||
|
</data>
|
||||||
|
<data name="Donation.Text" xml:space="preserve">
|
||||||
|
<value>Donation</value>
|
||||||
|
</data>
|
||||||
|
<data name="More.Text" xml:space="preserve">
|
||||||
|
<value>More</value>
|
||||||
|
</data>
|
||||||
|
<data name="Repository.Text" xml:space="preserve">
|
||||||
|
<value>Repository</value>
|
||||||
|
</data>
|
||||||
|
<data name="SelfbuiltVersion" xml:space="preserve">
|
||||||
|
<value>Self-built version</value>
|
||||||
|
</data>
|
||||||
|
<data name="Translation.Text" xml:space="preserve">
|
||||||
|
<value>Translation (English (US))</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AuthenticationButtonSignIn.Text" xml:space="preserve">
|
||||||
|
<value>Sign in</value>
|
||||||
|
</data>
|
||||||
|
<data name="AuthenticationButtonSignOut.Text" xml:space="preserve">
|
||||||
|
<value>Sign out</value>
|
||||||
|
</data>
|
||||||
|
<data name="AuthenticationDescriptionLoading.Value" xml:space="preserve">
|
||||||
|
<value>Loading...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Header.Text" xml:space="preserve">
|
||||||
|
<value>Authentication</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDescriptionAuthenticated" xml:space="preserve">
|
||||||
|
<value>Signed in as {0}. Expiration date: {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDescriptionAuthenticationInvalid" xml:space="preserve">
|
||||||
|
<value>Token expired or is invalid. Please log in again</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDescriptionCannotValidate" xml:space="preserve">
|
||||||
|
<value>Cannot validate token. Check your internet connection and try again later.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDescriptionNotAuthenticated" xml:space="preserve">
|
||||||
|
<value>You are not authenticated. Please sign in</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDescriptionNotAuthenticatedNoInternetConnection" xml:space="preserve">
|
||||||
|
<value>You are not authenticated and there is no internet connection. Check your internet connection and try again later.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDialogMessage" xml:space="preserve">
|
||||||
|
<value>An unknown error occured during Twitch login</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationDialogTitle" xml:space="preserve">
|
||||||
|
<value>Twitch authentication error</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchAuthenticationWindowTitle" xml:space="preserve">
|
||||||
|
<value>Sign in to Twitch</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="AboutNavigationViewItem" xml:space="preserve">
|
||||||
|
<value>About</value>
|
||||||
|
</data>
|
||||||
|
<data name="AuthenticationNavigationViewItem" xml:space="preserve">
|
||||||
|
<value>Authentication</value>
|
||||||
|
</data>
|
||||||
|
<data name="HomeNavigationViewItem" xml:space="preserve">
|
||||||
|
<value>Home</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionsNavigationViewItem" xml:space="preserve">
|
||||||
|
<value>Subscriptions</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="MediaTypeOnlyAudio.Text" xml:space="preserve">
|
||||||
|
<value>Only audio</value>
|
||||||
|
</data>
|
||||||
|
<data name="MediaTypeOnlyVideo.Text" xml:space="preserve">
|
||||||
|
<value>Only video</value>
|
||||||
|
</data>
|
||||||
|
<data name="MediaTypeOriginal.Text" xml:space="preserve">
|
||||||
|
<value>Original</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedFast.Text" xml:space="preserve">
|
||||||
|
<value>Fast</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedFaster.Text" xml:space="preserve">
|
||||||
|
<value>Faster</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedMedium.Text" xml:space="preserve">
|
||||||
|
<value>Medium</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedSlow.Text" xml:space="preserve">
|
||||||
|
<value>Slow</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedSlower.Text" xml:space="preserve">
|
||||||
|
<value>Slower</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedSuperFast.Text" xml:space="preserve">
|
||||||
|
<value>Super fast</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedUltraFast.Text" xml:space="preserve">
|
||||||
|
<value>Ultra fast</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedVeryFast.Text" xml:space="preserve">
|
||||||
|
<value>Very fast</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProcessingSpeedVerySlow.Text" xml:space="preserve">
|
||||||
|
<value>Very slow</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartAtMeteredConnectionDialogMessage" xml:space="preserve">
|
||||||
|
<value>You are trying to download a video using a metered connection. Do you want to continue?</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartAtMeteredConnectionDialogTitle" xml:space="preserve">
|
||||||
|
<value>Warning</value>
|
||||||
|
</data>
|
||||||
|
<data name="SubscriptionVideoListName" xml:space="preserve">
|
||||||
|
<value>Subscriptions</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Cancel" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="Close" xml:space="preserve">
|
||||||
|
<value>Close</value>
|
||||||
|
</data>
|
||||||
|
<data name="No" xml:space="preserve">
|
||||||
|
<value>No</value>
|
||||||
|
</data>
|
||||||
|
<data name="OK" xml:space="preserve">
|
||||||
|
<value>OK</value>
|
||||||
|
</data>
|
||||||
|
<data name="Yes" xml:space="preserve">
|
||||||
|
<value>Yes</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="author" xml:space="preserve">
|
||||||
|
<value>{author} = author</value>
|
||||||
|
</data>
|
||||||
|
<data name="date" xml:space="preserve">
|
||||||
|
<value>{date:<format>} = publish date in specified format</value>
|
||||||
|
</data>
|
||||||
|
<data name="duration" xml:space="preserve">
|
||||||
|
<value>{duration:<format>} = duration in specified format</value>
|
||||||
|
</data>
|
||||||
|
<data name="id" xml:space="preserve">
|
||||||
|
<value>{id} = id (from url)</value>
|
||||||
|
</data>
|
||||||
|
<data name="source" xml:space="preserve">
|
||||||
|
<value>{source} = source</value>
|
||||||
|
</data>
|
||||||
|
<data name="title" xml:space="preserve">
|
||||||
|
<value>{title} = title</value>
|
||||||
|
</data>
|
||||||
|
<data name="views" xml:space="preserve">
|
||||||
|
<value>{views} = views</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="DialogErrorMessageNoInternetConnection" xml:space="preserve">
|
||||||
|
<value>No internet connection. Try again later.</value>
|
||||||
|
</data>
|
||||||
|
<data name="DialogErrorTitle" xml:space="preserve">
|
||||||
|
<value>Error</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorDownloadingTimeout" xml:space="preserve">
|
||||||
|
<value>Downloading timeout. Check your internet connection and try again later.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorFFmpeg" xml:space="preserve">
|
||||||
|
<value>FFmpeg exited with error.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorFFmpegPath" xml:space="preserve">
|
||||||
|
<value>No FFmpeg executables found. Check path in settings and try again.</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusCancelled.Text" xml:space="preserve">
|
||||||
|
<value>Cancelled</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusDone.Text" xml:space="preserve">
|
||||||
|
<value>Done</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusDownloading.Text" xml:space="preserve">
|
||||||
|
<value>Downloading</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusError.Text" xml:space="preserve">
|
||||||
|
<value>Error</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusFinalizing.Text" xml:space="preserve">
|
||||||
|
<value>Finalizing</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusIdle.Text" xml:space="preserve">
|
||||||
|
<value>Idle</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusInitializing.Text" xml:space="preserve">
|
||||||
|
<value>Initializing</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusProcessing.Text" xml:space="preserve">
|
||||||
|
<value>Processing</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatusQueued.Text" xml:space="preserve">
|
||||||
|
<value>Queued</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="ApplyDirectoryButton.ToolTipService.ToolTip" xml:space="preserve">
|
||||||
|
<value>Selected directory will be applied to all videos in playlist</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateAndStartButton.Content" xml:space="preserve">
|
||||||
|
<value>Create download tasks and start</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateButton.Content" xml:space="preserve">
|
||||||
|
<value>Create download tasks</value>
|
||||||
|
</data>
|
||||||
|
<data name="DirectorySettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Directory</value>
|
||||||
|
</data>
|
||||||
|
<data name="DirectorySettingsCardButton.Content" xml:space="preserve">
|
||||||
|
<value>Browse</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilenameSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Filename</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileOptionsHeader.Text" xml:space="preserve">
|
||||||
|
<value>File options</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileTypeSettingsCard.Description" xml:space="preserve">
|
||||||
|
<value>If original video is not in selected type, it will be converted</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileTypeSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>File type</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterAuthorTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Author</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterAuthorTextBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Enter regular expression</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterDateTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Date</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterDurationTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Duration</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterRemovedButton.Content" xml:space="preserve">
|
||||||
|
<value>Restore</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterRemovedTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Removed</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterTitleTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Title</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterTitleTextBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Enter regular expression</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterViewsTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Views</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterWindow.Title" xml:space="preserve">
|
||||||
|
<value>Filter</value>
|
||||||
|
</data>
|
||||||
|
<data name="HiddenTextBlock.Text" xml:space="preserve">
|
||||||
|
<value>Hidden: </value>
|
||||||
|
</data>
|
||||||
|
<data name="MediaOptionsHeader.Text" xml:space="preserve">
|
||||||
|
<value>Media options</value>
|
||||||
|
</data>
|
||||||
|
<data name="MediaTypeSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Media type</value>
|
||||||
|
</data>
|
||||||
|
<data name="QualitySettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Quality</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrimEndSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>End at</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrimSettingsGroup.Header" xml:space="preserve">
|
||||||
|
<value>Trim</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrimStartSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Start at</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="CreateAndStartButton.Content" xml:space="preserve">
|
||||||
|
<value>Create download task and start</value>
|
||||||
|
</data>
|
||||||
|
<data name="CreateButton.Content" xml:space="preserve">
|
||||||
|
<value>Create download task</value>
|
||||||
|
</data>
|
||||||
|
<data name="DirectorySettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Directory</value>
|
||||||
|
</data>
|
||||||
|
<data name="DirectorySettingsCardButton.Content" xml:space="preserve">
|
||||||
|
<value>Browse</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilenameSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Filename</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileOptionsHeader.Text" xml:space="preserve">
|
||||||
|
<value>File options</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileTypeSettingsCard.Description" xml:space="preserve">
|
||||||
|
<value>If original video is not in selected type, it will be converted</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileTypeSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>File type</value>
|
||||||
|
</data>
|
||||||
|
<data name="MediaOptionsHeader.Text" xml:space="preserve">
|
||||||
|
<value>Media options</value>
|
||||||
|
</data>
|
||||||
|
<data name="MediaTypeSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Media type</value>
|
||||||
|
</data>
|
||||||
|
<data name="QualitySettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Quality</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrimEndSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>End at</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrimSettingsGroup.Header" xml:space="preserve">
|
||||||
|
<value>Trim</value>
|
||||||
|
</data>
|
||||||
|
<data name="TrimStartSettingsCard.Header" xml:space="preserve">
|
||||||
|
<value>Start at</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="ErrorInfoBar.Title" xml:space="preserve">
|
||||||
|
<value>Error</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorInfoBarNoInternetConnection" xml:space="preserve">
|
||||||
|
<value>No internet connection</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarCancelAll.Label" xml:space="preserve">
|
||||||
|
<value>Cancel all</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarCancelAll.Width" xml:space="preserve">
|
||||||
|
<value>80</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarDownloadAll.Label" xml:space="preserve">
|
||||||
|
<value>Download all</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarDownloadAll.Width" xml:space="preserve">
|
||||||
|
<value>100</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarLoadSubscription.Label" xml:space="preserve">
|
||||||
|
<value>Load from subscriptions</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarLoadSubscription.Width" xml:space="preserve">
|
||||||
|
<value>150</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarMessageLoading" xml:space="preserve">
|
||||||
|
<value>Loading...</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarMessageVideosFound" xml:space="preserve">
|
||||||
|
<value>New videos:</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarMessageVideosNotFound" xml:space="preserve">
|
||||||
|
<value>No new videos</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarPlaylistSearch.Label" xml:space="preserve">
|
||||||
|
<value>Playlist search</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarPlaylistSearch.Width" xml:space="preserve">
|
||||||
|
<value>100</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarPlaylistSearchContentNumberBox.ToolTipService.ToolTip" xml:space="preserve">
|
||||||
|
<value>Number of videos to get from playlist
|
||||||
|
0 = Get all</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarPlaylistSearchContentTextBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Playlist URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarSearchButton.Content" xml:space="preserve">
|
||||||
|
<value>Search</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarVideoSearch.Label" xml:space="preserve">
|
||||||
|
<value>Video search</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarVideoSearch.Width" xml:space="preserve">
|
||||||
|
<value>100</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptionBarVideoSearchContentTextBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Video URL</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="Author" xml:space="preserve">
|
||||||
|
<value>Author</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message" xml:space="preserve">
|
||||||
|
<value>Message</value>
|
||||||
|
</data>
|
||||||
|
<data name="OnSuccessfulTitle" xml:space="preserve">
|
||||||
|
<value>Task ended successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="OnUnsuccessfulTitle" xml:space="preserve">
|
||||||
|
<value>Task ended unsuccessfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Title" xml:space="preserve">
|
||||||
|
<value>Title</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="EmptyPlaylist" xml:space="preserve">
|
||||||
|
<value>Playlist is empty</value>
|
||||||
|
</data>
|
||||||
|
<data name="EmptyUrl" xml:space="preserve">
|
||||||
|
<value>Invalid url. Url cannot be empty.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SearchTimeout" xml:space="preserve">
|
||||||
|
<value>Search timeout. Check your internet connection.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SourceNotSupported" xml:space="preserve">
|
||||||
|
<value>Invalid url. Url does not match any of supported source.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchChannelNotFound" xml:space="preserve">
|
||||||
|
<value>Invalid url. Twitch channel not found.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchClipNotFound" xml:space="preserve">
|
||||||
|
<value>Invalid url. Twitch clip not found.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchNotAuthenticated" xml:space="preserve">
|
||||||
|
<value>Twitch authentication error. Not authenticated to Twitch.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchTokenValidationUnsuccessful" xml:space="preserve">
|
||||||
|
<value>Twitch authentication error. Authentication token is invalid.</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodNotFound" xml:space="preserve">
|
||||||
|
<value>Invalid url. Twitch VOD not found.</value>
|
||||||
|
</data>
|
||||||
|
<data name="UnknownMediaType" xml:space="preserve">
|
||||||
|
<value>Invalid url. Url does not match any of supported source's media types.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -117,100 +117,124 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Base_CancelButtonText" xml:space="preserve">
|
<data name="Header.Text" xml:space="preserve">
|
||||||
<value>Cancel</value>
|
<value>Settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Base_CloseButtonText" xml:space="preserve">
|
<data name="NotificationsHeader.Text" xml:space="preserve">
|
||||||
<value>OK</value>
|
<value>Notifications</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadAll_MeteredConnection_Content" xml:space="preserve">
|
<data name="NotificationsOnSuccessful.Header" xml:space="preserve">
|
||||||
<value>You are on the metered connection now. You can delay tasks until the network changes. Do you want to start the tasks?</value>
|
<value>Show notifications when task ended successfully</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadAll_MeteredConnection_StartWithDelayButtonText" xml:space="preserve">
|
<data name="NotificationsOnUnsuccessful.Header" xml:space="preserve">
|
||||||
<value>Yes (With delay)</value>
|
<value>Show notifications when task ended unsuccessfully</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadAll_MeteredConnection_StartWithoutDelayButtonText" xml:space="preserve">
|
<data name="ProcessingFFmpegLocation.Header" xml:space="preserve">
|
||||||
<value>Yes (Without delay)</value>
|
<value>FFmpeg location</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadAll_MeteredConnection_Title" xml:space="preserve">
|
<data name="ProcessingFFmpegLocationButton.Content" xml:space="preserve">
|
||||||
<value>Metered connection detected</value>
|
<value>Browse</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadTaskControl_Start_MeteredConnection_Content" xml:space="preserve">
|
<data name="ProcessingHeader.Text" xml:space="preserve">
|
||||||
<value>You are on the metered connection now. You can delay task until the network changes. Do you want to start the task?</value>
|
<value>Processing</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadTaskControl_Start_MeteredConnection_StartWithDelayButtonText" xml:space="preserve">
|
<data name="ProcessingSpeed.Header" xml:space="preserve">
|
||||||
<value>Yes (With delay)</value>
|
<value>Speed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadTaskControl_Start_MeteredConnection_StartWithoutDelayButtonText1" xml:space="preserve">
|
<data name="ProcessingUseHardwareAcceleration.Header" xml:space="preserve">
|
||||||
<value>Yes (Without delay)</value>
|
<value>Use hardware acceleration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_DownloadTaskControl_Start_MeteredConnection_Title" xml:space="preserve">
|
<data name="ProcessingUseMultithreading.Header" xml:space="preserve">
|
||||||
<value>Metered connection detected</value>
|
<value>Use multithreading</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_PlaylistSearchControl_Base_Title" xml:space="preserve">
|
<data name="RestoreToDefaultButton.Content" xml:space="preserve">
|
||||||
<value>Playlist search error</value>
|
<value>Restore settings to default</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_PlaylistSearchControl_InternetNotAvailable_Content" xml:space="preserve">
|
<data name="SearchingHeader.Text" xml:space="preserve">
|
||||||
<value>Unable to connect to servers. Check your internet connection.</value>
|
<value>Searching</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_PlaylistSearchControl_TwitchAccessTokenNotFound_Content" xml:space="preserve">
|
<data name="SearchingPlaylistCount.Description" xml:space="preserve">
|
||||||
<value>To get information about Twitch playlists (Channels), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
|
<value>0 = Get all</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_PlaylistSearchControl_TwitchAccessTokenNotValid_Content" xml:space="preserve">
|
<data name="SearchingPlaylistCount.Header" xml:space="preserve">
|
||||||
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
|
<value>Default number of videos fetched from playlist</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_SubscriptionsLoadControl_Base_Title" xml:space="preserve">
|
<data name="TasksAudioExtension.Header" xml:space="preserve">
|
||||||
<value>Subscriptions loading error</value>
|
<value>Audio extension</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_SubscriptionsLoadControl_InternetNotAvailable_Content" xml:space="preserve">
|
<data name="TasksDefaultMediaOptions.Header" xml:space="preserve">
|
||||||
<value>Unable to connect to servers. Check your internet connection.</value>
|
<value>Default media options</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_SubscriptionsLoadControl_MediaNotFound_Content" xml:space="preserve">
|
<data name="TasksDefaultOutputDirectory.Header" xml:space="preserve">
|
||||||
<value>Subscribed playlist not found. Name:</value>
|
<value>Default output directory</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_SubscriptionsLoadControl_TwitchAccessTokenNotFound_Content" xml:space="preserve">
|
<data name="TasksDefaultOutputDirectoryButton.Content" xml:space="preserve">
|
||||||
<value>To get information about Twitch videos and playlists, you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
|
<value>Browse</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_SubscriptionsLoadControl_TwitchAccessTokenNotValid_Content" xml:space="preserve">
|
<data name="TasksFilenameTemplate.Header" xml:space="preserve">
|
||||||
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
|
<value>Filename template</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_VideoSearchControl_Base_Title" xml:space="preserve">
|
<data name="TasksHeader.Text" xml:space="preserve">
|
||||||
<value>Video search error</value>
|
<value>Tasks</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_VideoSearchControl_InternetNotAvailable_Content" xml:space="preserve">
|
<data name="TasksMediaType.Header" xml:space="preserve">
|
||||||
<value>Unable to connect to servers. Check your internet connection.</value>
|
<value>Media type</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_VideoSearchControl_TwitchAccessTokenNotFound_Content" xml:space="preserve">
|
<data name="TasksMeteredConnectionWarning.Header" xml:space="preserve">
|
||||||
<value>To get information about Twitch videos (VODs and Clips), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
|
<value>Show warning before downloading on a metered connection</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Home_OptionsBar_VideoSearchControl_TwitchAccessTokenNotValid_Content" xml:space="preserve">
|
<data name="TasksReplaceOutputFile.Description" xml:space="preserve">
|
||||||
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
|
<value>If file with same name as output file, already exists, it will be replaced. Otherwise new file with unique name will be created.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Sources_TwitchLogin_Base_Title" xml:space="preserve">
|
<data name="TasksReplaceOutputFile.Header" xml:space="preserve">
|
||||||
<value>Login to Twitch failed</value>
|
<value>Replace output file if exists</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Sources_TwitchLogin_InternetNotAvailable_Content" xml:space="preserve">
|
<data name="TasksRunningTasks.Header" xml:space="preserve">
|
||||||
<value>Unable to connect to servers. Check your internet connection.</value>
|
<value>Maximum number of tasks running in parallel</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Sources_TwitchLogin_Unknown_Content" xml:space="preserve">
|
<data name="TasksSaveLastOutputDirectory.Description" xml:space="preserve">
|
||||||
<value>Unknown error. </value>
|
<value>Otherwise, default directory defined below (after expanding) will be used</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Subscription_Adding_Base_Title" xml:space="preserve">
|
<data name="TasksSaveLastOutputDirectory.Header" xml:space="preserve">
|
||||||
<value>Playlist adding error</value>
|
<value>Save last output directory</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Subscription_Adding_InternetNotAvailable_Content" xml:space="preserve">
|
<data name="TasksVideoExtension.Header" xml:space="preserve">
|
||||||
<value>Unable to connect to servers. Check your internet connection.</value>
|
<value>Video extension</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Subscription_Adding_PlaylistNotFound_Content" xml:space="preserve">
|
<data name="TempDeleteOnFail.Header" xml:space="preserve">
|
||||||
<value>Playlist not found. Check the URL.</value>
|
<value>Delete task's temporary files when an error occurs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Subscription_Adding_SubscriptionExists_Content" xml:space="preserve">
|
<data name="TempDirectory.Header" xml:space="preserve">
|
||||||
<value>This playlist has been already subscribed</value>
|
<value>Temporary files location</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Subscription_Adding_TwitchAccessTokenNotFound_Content" xml:space="preserve">
|
<data name="TempDirectoryButton.Content" xml:space="preserve">
|
||||||
<value>To be able to subscribe Twitch playlists (Channels), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
|
<value>Browse</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Subscription_Adding_TwitchAccessTokenNotValid_Content" xml:space="preserve">
|
<data name="TempHeader.Text" xml:space="preserve">
|
||||||
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
|
<value>Temporary files</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchHeader.Text" xml:space="preserve">
|
||||||
|
<value>Twitch</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodChunkDownloadingErrorRetry.Header" xml:space="preserve">
|
||||||
|
<value>Retry when downloading single VOD chunk fails</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodChunkDownloadingErrorRetryCount.Header" xml:space="preserve">
|
||||||
|
<value>Number of retries</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodChunkDownloadingErrorRetryDelay.Header" xml:space="preserve">
|
||||||
|
<value>Time between fail and retry (in miliseconds)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodParallelDownloads.Description" xml:space="preserve">
|
||||||
|
<value>WARNING: Too many chunks downloaded at once may cause performance problems</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodParallelDownloads.Header" xml:space="preserve">
|
||||||
|
<value>Maximum number of VOD chunks downloaded in parallel</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodPassiveTrimming.Description" xml:space="preserve">
|
||||||
|
<value>Only chunks from trim start to trim end range will be downloaded</value>
|
||||||
|
</data>
|
||||||
|
<data name="TwitchVodPassiveTrimming.Header" xml:space="preserve">
|
||||||
|
<value>VOD passive trimming</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<data name="DuplicateError" xml:space="preserve">
|
||||||
|
<value>Playlist has been already added</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrorInfoBar.Title" xml:space="preserve">
|
||||||
|
<value>Error</value>
|
||||||
|
</data>
|
||||||
|
<data name="Header.Text" xml:space="preserve">
|
||||||
|
<value>Subscriptions</value>
|
||||||
|
</data>
|
||||||
|
<data name="NoInternetConnectionError" xml:space="preserve">
|
||||||
|
<value>No internet connection</value>
|
||||||
|
</data>
|
||||||
|
<data name="PlaylistSearchButton.Content" xml:space="preserve">
|
||||||
|
<value>Add</value>
|
||||||
|
</data>
|
||||||
|
<data name="PlaylistUrlTextBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Playlist URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="RemovePlaylistButton.Content" xml:space="preserve">
|
||||||
|
<value>Remove</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>VDownload.Core.Strings</RootNamespace>
|
||||||
|
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<UseRidGraph>true</UseRidGraph>
|
||||||
|
<EnableCoreMrtTooling Condition=" '$(BuildingInsideVisualStudio)' != 'true' ">false</EnableCoreMrtTooling>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PRIResource Update="Strings\en-US\AboutViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\AuthenticationViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\BaseViewResources.resw">
|
||||||
|
<Generator></Generator>
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\CommonResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\DialogButtonsResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\FilenameTemplateResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\HomeDownloadsViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\HomeVideoCollectionViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\HomeVideoViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\HomeViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\NotificationsResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\SearchResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\SettingsViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
<PRIResource Update="Strings\en-US\SubscriptionsViewResources.resw">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</PRIResource>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
292
VDownload.Core/VDownload.Core.Tasks/DownloadTask.cs
Normal file
292
VDownload.Core/VDownload.Core.Tasks/DownloadTask.cs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Utility.FFmpeg;
|
||||||
|
using VDownload.Services.Data.Configuration;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using Windows.System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Windows.Storage;
|
||||||
|
using System.IO;
|
||||||
|
using VDownload.Services.UI.Notifications;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Instances.Exceptions;
|
||||||
|
using FFMpegCore.Exceptions;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Tasks
|
||||||
|
{
|
||||||
|
public partial class DownloadTask : ObservableObject
|
||||||
|
{
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
private enum TaskResult
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
Cancellation,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IConfigurationService _configurationService;
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly IFFmpegService _ffmpegService;
|
||||||
|
protected readonly INotificationsService _notificationsService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||||
|
|
||||||
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
|
||||||
|
private Task _downloadTask;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Guid _id;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Video _video;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected VideoDownloadOptions _downloadOptions;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected DownloadTaskStatus _status;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected DateTime _createDate;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected double _progress;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string? _error;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
internal DownloadTask(Video video, VideoDownloadOptions downloadOptions, IConfigurationService configurationService, ISettingsService settingsService, IFFmpegService ffmpegService, INotificationsService notificationsService)
|
||||||
|
{
|
||||||
|
_configurationService = configurationService;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_ffmpegService = ffmpegService;
|
||||||
|
_notificationsService = notificationsService;
|
||||||
|
|
||||||
|
_video = video;
|
||||||
|
_downloadOptions = downloadOptions;
|
||||||
|
_id = Guid.NewGuid();
|
||||||
|
_status = DownloadTaskStatus.Idle;
|
||||||
|
_createDate = DateTime.Now;
|
||||||
|
_progress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public void Enqueue()
|
||||||
|
{
|
||||||
|
DownloadTaskStatus[] statuses =
|
||||||
|
[
|
||||||
|
DownloadTaskStatus.Idle,
|
||||||
|
DownloadTaskStatus.EndedUnsuccessfully,
|
||||||
|
DownloadTaskStatus.EndedSuccessfully,
|
||||||
|
DownloadTaskStatus.EndedCancelled,
|
||||||
|
];
|
||||||
|
if (statuses.Contains(Status))
|
||||||
|
{
|
||||||
|
Status = DownloadTaskStatus.Queued;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Start()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.Initializing);
|
||||||
|
|
||||||
|
_downloadTask = Download();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Cancel()
|
||||||
|
{
|
||||||
|
if (_cancellationTokenSource is not null)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
await _downloadTask;
|
||||||
|
_cancellationTokenSource.Dispose();
|
||||||
|
_cancellationTokenSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private async Task Download()
|
||||||
|
{
|
||||||
|
CancellationToken token = _cancellationTokenSource.Token;
|
||||||
|
|
||||||
|
await _settingsService.Load();
|
||||||
|
|
||||||
|
string tempDirectory = $"{_settingsService.Data.Common.Temp.Directory}\\{_configurationService.Common.Path.Temp.TasksDirectory}\\{Guid.NewGuid()}";
|
||||||
|
if (Directory.Exists(tempDirectory))
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDirectory, true);
|
||||||
|
}
|
||||||
|
Directory.CreateDirectory(tempDirectory);
|
||||||
|
|
||||||
|
List<string> content = new List<string>()
|
||||||
|
{
|
||||||
|
$"{StringResourcesManager.Notifications.Get("Title")}: {Video.Title}",
|
||||||
|
$"{StringResourcesManager.Notifications.Get("Author")}: {Video.Author}"
|
||||||
|
};
|
||||||
|
|
||||||
|
string errorMessage = null;
|
||||||
|
TaskResult? endingStatus = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IProgress<double> onProgressDownloading = new Progress<double>((value) =>
|
||||||
|
{
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.Downloading);
|
||||||
|
UpdateProgressWithDispatcher(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
VideoStreamDownloadResult downloadResult = await DownloadOptions.SelectedStream.Download(tempDirectory, onProgressDownloading, token, Video.Duration, DownloadOptions.TrimStart, DownloadOptions.TrimEnd);
|
||||||
|
|
||||||
|
Action<double> onProgressProcessing = (value) =>
|
||||||
|
{
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.Processing);
|
||||||
|
UpdateProgressWithDispatcher(value);
|
||||||
|
};
|
||||||
|
string outputPath = $"{tempDirectory}\\processed.{DownloadOptions.Extension}";
|
||||||
|
|
||||||
|
FFmpegBuilder ffmpegBuilder = _ffmpegService.CreateBuilder();
|
||||||
|
if (downloadResult.NewTrimStart != TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
ffmpegBuilder.TrimStart(downloadResult.NewTrimStart);
|
||||||
|
}
|
||||||
|
if (downloadResult.NewTrimEnd != downloadResult.NewDuration)
|
||||||
|
{
|
||||||
|
ffmpegBuilder.TrimEnd(downloadResult.NewTrimEnd);
|
||||||
|
}
|
||||||
|
ffmpegBuilder.SetMediaType(DownloadOptions.MediaType)
|
||||||
|
.JoinCancellationToken(token)
|
||||||
|
.JoinProgressReporter(onProgressProcessing, downloadResult.NewDuration);
|
||||||
|
await ffmpegBuilder.RunAsync(downloadResult.File, outputPath);
|
||||||
|
|
||||||
|
StorageFile outputFile = await StorageFile.GetFileFromPathAsync(outputPath);
|
||||||
|
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.Finalizing);
|
||||||
|
|
||||||
|
await Finalizing(outputFile);
|
||||||
|
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedSuccessfully);
|
||||||
|
endingStatus = TaskResult.Success;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedCancelled);
|
||||||
|
endingStatus = TaskResult.Cancellation;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string message;
|
||||||
|
|
||||||
|
if (ex is TaskCanceledException || ex is HttpRequestException)
|
||||||
|
{
|
||||||
|
message = StringResourcesManager.HomeDownloadsView.Get("ErrorDownloadingTimeout");
|
||||||
|
}
|
||||||
|
else if (ex is InstanceFileNotFoundException)
|
||||||
|
{
|
||||||
|
message = StringResourcesManager.HomeDownloadsView.Get("ErrorFFmpegPath");
|
||||||
|
}
|
||||||
|
else if (ex is FFMpegException)
|
||||||
|
{
|
||||||
|
message = StringResourcesManager.HomeDownloadsView.Get("ErrorFFmpeg");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateErrorWithDispatcher(message);
|
||||||
|
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedUnsuccessfully);
|
||||||
|
endingStatus = TaskResult.Error;
|
||||||
|
errorMessage = message;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
switch (endingStatus)
|
||||||
|
{
|
||||||
|
case TaskResult.Error:
|
||||||
|
if (_settingsService.Data.Common.Notifications.OnUnsuccessful)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.Notifications.Get("OnUnsuccessfulTitle");
|
||||||
|
content.Add($"{StringResourcesManager.Notifications.Get("Message")}: {errorMessage}");
|
||||||
|
_notificationsService.SendNotification(title, content);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TaskResult.Success:
|
||||||
|
if (_settingsService.Data.Common.Notifications.OnSuccessful)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.Notifications.Get("OnSuccessfulTitle");
|
||||||
|
_notificationsService.SendNotification(title, content);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Status != DownloadTaskStatus.EndedUnsuccessfully || _settingsService.Data.Common.Temp.DeleteOnError)
|
||||||
|
{
|
||||||
|
Directory.Delete(tempDirectory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task Finalizing(StorageFile outputFile)
|
||||||
|
{
|
||||||
|
StorageFolder destination = await StorageFolder.GetFolderFromPathAsync(DownloadOptions.Directory);
|
||||||
|
|
||||||
|
string filename = $"{DownloadOptions.Filename}.{DownloadOptions.Extension}";
|
||||||
|
|
||||||
|
NameCollisionOption nameCollisionOption = _settingsService.Data.Common.Tasks.ReplaceOutputFileIfExists
|
||||||
|
? NameCollisionOption.ReplaceExisting
|
||||||
|
: NameCollisionOption.GenerateUniqueName;
|
||||||
|
|
||||||
|
await outputFile.CopyAsync(destination, filename, nameCollisionOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateStatusWithDispatcher(DownloadTaskStatus status) => _dispatcherQueue.TryEnqueue(() => Status = status);
|
||||||
|
|
||||||
|
protected void UpdateProgressWithDispatcher(double progress) => _dispatcherQueue.TryEnqueue(() => Progress = progress);
|
||||||
|
|
||||||
|
protected void UpdateErrorWithDispatcher(string message) => _dispatcherQueue.TryEnqueue(() => Error = message);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Configuration;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.UI.Notifications;
|
||||||
|
using VDownload.Services.Utility.FFmpeg;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Tasks
|
||||||
|
{
|
||||||
|
public interface IDownloadTaskFactoryService
|
||||||
|
{
|
||||||
|
DownloadTask Create(Video video, VideoDownloadOptions downloadOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class DownloadTaskFactoryService : IDownloadTaskFactoryService
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IConfigurationService _configurationService;
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly IFFmpegService _ffmpegService;
|
||||||
|
protected readonly INotificationsService _notificationsService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public DownloadTaskFactoryService(IConfigurationService configurationService, ISettingsService settingsService, IFFmpegService ffmpegService, INotificationsService notificationsService)
|
||||||
|
{
|
||||||
|
_configurationService = configurationService;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_ffmpegService = ffmpegService;
|
||||||
|
_notificationsService = notificationsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public DownloadTask Create(Video video, VideoDownloadOptions downloadOptions)
|
||||||
|
{
|
||||||
|
return new DownloadTask(video, downloadOptions, _configurationService, _settingsService, _ffmpegService, _notificationsService);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
167
VDownload.Core/VDownload.Core.Tasks/DownloadTaskManager.cs
Normal file
167
VDownload.Core/VDownload.Core.Tasks/DownloadTaskManager.cs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Tasks
|
||||||
|
{
|
||||||
|
public interface IDownloadTaskManager
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
ReadOnlyObservableCollection<DownloadTask> Tasks { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENTS
|
||||||
|
|
||||||
|
event NotifyCollectionChangedEventHandler TaskCollectionChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
DownloadTask AddTask(Video video, VideoDownloadOptions downloadOptions);
|
||||||
|
void RemoveTask(DownloadTask task);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class DownloadTaskManager : IDownloadTaskManager
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IDownloadTaskFactoryService _downloadTaskFactoryService;
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private readonly Thread _taskMonitorThread;
|
||||||
|
|
||||||
|
private readonly ObservableCollection<DownloadTask> _tasksMain;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<DownloadTask> Tasks { get; protected set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENTS
|
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler TaskCollectionChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public DownloadTaskManager(IDownloadTaskFactoryService downloadTaskFactoryService, ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
_downloadTaskFactoryService = downloadTaskFactoryService;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
|
||||||
|
_tasksMain = new ObservableCollection<DownloadTask>();
|
||||||
|
_tasksMain.CollectionChanged += Tasks_CollectionChanged;
|
||||||
|
Tasks = new ReadOnlyObservableCollection<DownloadTask>(_tasksMain);
|
||||||
|
|
||||||
|
_taskMonitorThread = new Thread(TaskMonitor)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
_taskMonitorThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public DownloadTask AddTask(Video video, VideoDownloadOptions downloadOptions)
|
||||||
|
{
|
||||||
|
DownloadTask task = _downloadTaskFactoryService.Create(video, downloadOptions);
|
||||||
|
_tasksMain.Add(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveTask(DownloadTask task)
|
||||||
|
{
|
||||||
|
_tasksMain.Remove(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private async void TaskMonitor()
|
||||||
|
{
|
||||||
|
await _settingsService.Load();
|
||||||
|
|
||||||
|
DownloadTaskStatus[] pendingStatuses =
|
||||||
|
[
|
||||||
|
DownloadTaskStatus.Initializing,
|
||||||
|
DownloadTaskStatus.Downloading,
|
||||||
|
DownloadTaskStatus.Processing,
|
||||||
|
DownloadTaskStatus.Finalizing
|
||||||
|
];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IEnumerable<DownloadTask> pendingTasks = Tasks.Where(x => pendingStatuses.Contains(x.Status));
|
||||||
|
int freeSlots = _settingsService.Data.Common.Tasks.MaxNumberOfRunningTasks - pendingTasks.Count();
|
||||||
|
if (freeSlots > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<DownloadTask> queuedTasks = Tasks.Where(x => x.Status == DownloadTaskStatus.Queued).OrderBy(x => x.CreateDate).Take(freeSlots);
|
||||||
|
foreach (DownloadTask queuedTask in queuedTasks)
|
||||||
|
{
|
||||||
|
queuedTask.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
Console.WriteLine("TaskMonitor: Collection locked - skipping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
|
private void Tasks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TaskCollectionChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Tasks
|
||||||
|
{
|
||||||
|
public class DownloadTaskNotInitializedException : Exception
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public DownloadTaskNotInitializedException() : base() { }
|
||||||
|
|
||||||
|
public DownloadTaskNotInitializedException(string message) : base(message) { }
|
||||||
|
|
||||||
|
public DownloadTaskNotInitializedException(string message, Exception inner) : base(message, inner) { }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
namespace VDownload.Core.Enums
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Tasks
|
||||||
{
|
{
|
||||||
public enum DownloadTaskStatus
|
public enum DownloadTaskStatus
|
||||||
{
|
{
|
||||||
Idle,
|
Idle,
|
||||||
Scheduled,
|
|
||||||
Queued,
|
|
||||||
Downloading,
|
|
||||||
Processing,
|
|
||||||
Finalizing,
|
|
||||||
EndedSuccessfully,
|
EndedSuccessfully,
|
||||||
EndedUnsuccessfully,
|
EndedUnsuccessfully,
|
||||||
|
EndedCancelled,
|
||||||
|
Queued,
|
||||||
|
Initializing,
|
||||||
|
Finalizing,
|
||||||
|
Processing,
|
||||||
|
Downloading,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>VDownload.Core.Tasks</RootNamespace>
|
||||||
|
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<UseRidGraph>true</UseRidGraph>
|
||||||
|
<EnableCoreMrtTooling Condition=" '$(BuildingInsideVisualStudio)' != 'true' ">false</EnableCoreMrtTooling>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\VDownload.Models\VDownload.Models.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Configuration\VDownload.Services.Data.Configuration.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Settings\VDownload.Services.Data.Settings.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.Notifications\VDownload.Services.UI.Notifications.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.FFmpeg\VDownload.Services.Utility.FFmpeg.csproj" />
|
||||||
|
<ProjectReference Include="..\VDownload.Core.Strings\VDownload.Core.Strings.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Core.ViewModels.About.Helpers;
|
||||||
|
using VDownload.Services.Data.Configuration;
|
||||||
|
using VDownload.Services.Data.Configuration.Models;
|
||||||
|
using Windows.System.UserProfile;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.About
|
||||||
|
{
|
||||||
|
public partial class AboutViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IConfigurationService _configurationService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _version;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected ObservableCollection<PersonViewModel> _developers;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected ObservableCollection<PersonViewModel> _translators;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Uri _repositoryUrl;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Uri _donationUrl;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public AboutViewModel(IConfigurationService configurationService)
|
||||||
|
{
|
||||||
|
_configurationService = configurationService;
|
||||||
|
|
||||||
|
string version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
if (version == "0.0.0")
|
||||||
|
{
|
||||||
|
version = StringResourcesManager.AboutView.Get("SelfbuiltVersion");
|
||||||
|
}
|
||||||
|
_version = version;
|
||||||
|
|
||||||
|
_developers = new ObservableCollection<PersonViewModel>(_configurationService.Common.About.Developers.Select(x => new PersonViewModel(x.Name, x.Url)));
|
||||||
|
|
||||||
|
_repositoryUrl = new Uri(_configurationService.Common.About.RepositoryUrl);
|
||||||
|
_donationUrl = new Uri(_configurationService.Common.About.DonationUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region COMMANDS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void Navigation()
|
||||||
|
{
|
||||||
|
string languageCode = "en-US";
|
||||||
|
Language language = _configurationService.Common.About.Translation.FirstOrDefault(x => x.Code == languageCode);
|
||||||
|
Translators = new ObservableCollection<PersonViewModel>(language.Translators.Select(x => new PersonViewModel(x.Name, x.Url)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.About.Helpers
|
||||||
|
{
|
||||||
|
public partial class PersonViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _name;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Uri _url;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public PersonViewModel(string name, string url)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_url = new Uri(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Services.Data.Configuration;
|
||||||
|
using VDownload.Services.UI.Dialogs;
|
||||||
|
using VDownload.Services.UI.WebView;
|
||||||
|
using VDownload.Sources.Twitch.Authentication;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Authentication
|
||||||
|
{
|
||||||
|
public partial class AuthenticationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
public enum AuthenticationButton
|
||||||
|
{
|
||||||
|
SignIn,
|
||||||
|
SignOut,
|
||||||
|
Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IDialogsService _dialogsService;
|
||||||
|
protected readonly IWebViewService _webViewService;
|
||||||
|
protected readonly IConfigurationService _configurationService;
|
||||||
|
protected readonly ITwitchAuthenticationService _twitchAuthenticationService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected AuthenticationButton _twitchButtonState = AuthenticationButton.Loading;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected bool _twitchButtonEnable = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _twitchDescription;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public AuthenticationViewModel(IDialogsService dialogsService, IWebViewService webViewService, IConfigurationService configurationService, ITwitchAuthenticationService twitchAuthenticationService)
|
||||||
|
{
|
||||||
|
_dialogsService = dialogsService;
|
||||||
|
_webViewService = webViewService;
|
||||||
|
_configurationService = configurationService;
|
||||||
|
_twitchAuthenticationService = twitchAuthenticationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Navigation()
|
||||||
|
{
|
||||||
|
List<Task> refreshTasks = new List<Task>
|
||||||
|
{
|
||||||
|
TwitchAuthenticationRefresh()
|
||||||
|
};
|
||||||
|
await Task.WhenAll(refreshTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task TwitchAuthentication()
|
||||||
|
{
|
||||||
|
AuthenticationButton state = TwitchButtonState;
|
||||||
|
TwitchButtonState = AuthenticationButton.Loading;
|
||||||
|
|
||||||
|
if (state == AuthenticationButton.SignOut)
|
||||||
|
{
|
||||||
|
await _twitchAuthenticationService.DeleteToken();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Sources.Twitch.Configuration.Models.Authentication auth = _configurationService.Twitch.Authentication;
|
||||||
|
string authUrl = string.Format(auth.Url, auth.ClientId, auth.RedirectUrl, auth.ResponseType, string.Join(' ', auth.Scopes));
|
||||||
|
|
||||||
|
string url = await _webViewService.Show(new Uri(authUrl), (url) => url.StartsWith(auth.RedirectUrl), StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationWindowTitle"));
|
||||||
|
|
||||||
|
Regex regex = new Regex(auth.RedirectUrlRegex);
|
||||||
|
Match match = regex.Match(url);
|
||||||
|
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
string token = match.Groups[1].Value;
|
||||||
|
await _twitchAuthenticationService.SetToken(Encoding.UTF8.GetBytes(token));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDialogTitle");
|
||||||
|
string message = StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDialogMessage");
|
||||||
|
await _dialogsService.ShowOk(title, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await TwitchAuthenticationRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private async Task TwitchAuthenticationRefresh()
|
||||||
|
{
|
||||||
|
TwitchButtonState = AuthenticationButton.Loading;
|
||||||
|
TwitchButtonEnable = true;
|
||||||
|
|
||||||
|
byte[]? token = await _twitchAuthenticationService.GetToken();
|
||||||
|
|
||||||
|
if (token is null)
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
TwitchButtonEnable = false;
|
||||||
|
TwitchDescription = StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDescriptionNotAuthenticatedNoInternetConnection");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TwitchDescription = StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDescriptionNotAuthenticated");
|
||||||
|
}
|
||||||
|
TwitchButtonState = AuthenticationButton.SignIn;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TwitchValidationResult validationResult;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
validationResult = await _twitchAuthenticationService.ValidateToken(token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is TaskCanceledException || ex is HttpRequestException)
|
||||||
|
{
|
||||||
|
TwitchDescription = StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDescriptionCannotValidate");
|
||||||
|
TwitchButtonState = AuthenticationButton.SignIn;
|
||||||
|
TwitchButtonEnable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationResult.Success)
|
||||||
|
{
|
||||||
|
TwitchDescription = string.Format(StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDescriptionAuthenticated"), validationResult.TokenData.Login, validationResult.TokenData.ExpirationDate);
|
||||||
|
TwitchButtonState = AuthenticationButton.SignOut;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _twitchAuthenticationService.DeleteToken();
|
||||||
|
TwitchDescription = StringResourcesManager.AuthenticationView.Get("TwitchAuthenticationDescriptionAuthenticationInvalid");
|
||||||
|
TwitchButtonState = AuthenticationButton.SignIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
122
VDownload.Core/VDownload.Core.ViewModels/BaseViewModel.cs
Normal file
122
VDownload.Core/VDownload.Core.ViewModels/BaseViewModel.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.ViewModels.Authentication;
|
||||||
|
using VDownload.Core.ViewModels.Home;
|
||||||
|
using VDownload.Core.ViewModels.Settings;
|
||||||
|
using VDownload.Services.UI.DictionaryResources;
|
||||||
|
using SimpleToolkit.UI.Models;
|
||||||
|
using VDownload.Core.ViewModels.About;
|
||||||
|
using VDownload.Core.ViewModels.Subscriptions;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels
|
||||||
|
{
|
||||||
|
public partial class BaseViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IDictionaryResourcesService _dictionaryResourcesService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected readonly Type _settingsViewModel = typeof(SettingsViewModel);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Type _currentViewModel;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private NavigationViewItem _selectedItem;
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<NavigationViewItem> Items { get; protected set; }
|
||||||
|
public ReadOnlyObservableCollection<NavigationViewItem> FooterItems { get; protected set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public BaseViewModel(IDictionaryResourcesService dictionaryResourcesService)
|
||||||
|
{
|
||||||
|
_dictionaryResourcesService = dictionaryResourcesService;
|
||||||
|
|
||||||
|
Items = new ReadOnlyObservableCollection<NavigationViewItem>
|
||||||
|
(
|
||||||
|
new ObservableCollection<NavigationViewItem>
|
||||||
|
{
|
||||||
|
new NavigationViewItem()
|
||||||
|
{
|
||||||
|
Name = StringResourcesManager.BaseView.Get("HomeNavigationViewItem"),
|
||||||
|
IconSource = _dictionaryResourcesService.Get<string>("ImageBaseViewHome"),
|
||||||
|
ViewModel = typeof(HomeViewModel),
|
||||||
|
},
|
||||||
|
new NavigationViewItem()
|
||||||
|
{
|
||||||
|
Name = StringResourcesManager.BaseView.Get("SubscriptionsNavigationViewItem"),
|
||||||
|
IconSource = _dictionaryResourcesService.Get<string>("ImageBaseViewSubscriptions"),
|
||||||
|
ViewModel = typeof(SubscriptionsViewModel),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
FooterItems = new ReadOnlyObservableCollection<NavigationViewItem>
|
||||||
|
(
|
||||||
|
new ObservableCollection<NavigationViewItem>
|
||||||
|
{
|
||||||
|
new NavigationViewItem()
|
||||||
|
{
|
||||||
|
Name = StringResourcesManager.BaseView.Get("AboutNavigationViewItem"),
|
||||||
|
IconSource = _dictionaryResourcesService.Get<string>("ImageBaseViewAbout"),
|
||||||
|
ViewModel = typeof(AboutViewModel),
|
||||||
|
},
|
||||||
|
new NavigationViewItem()
|
||||||
|
{
|
||||||
|
Name = StringResourcesManager.BaseView.Get("AuthenticationNavigationViewItem"),
|
||||||
|
IconSource = _dictionaryResourcesService.Get<string>("ImageBaseViewAuthentication"),
|
||||||
|
ViewModel = typeof(AuthenticationViewModel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
SelectedItem = Items.First();
|
||||||
|
CurrentViewModel = SelectedItem.ViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void Navigate(Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.IsSettingsInvoked)
|
||||||
|
{
|
||||||
|
CurrentViewModel = _settingsViewModel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NavigationViewItem item = e.InvokedItemContainer.DataContext as NavigationViewItem;
|
||||||
|
CurrentViewModel = item.ViewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Application;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.UI.StoragePicker;
|
||||||
|
using VDownload.Services.Utility.Filename;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Home.Helpers
|
||||||
|
{
|
||||||
|
public partial class VideoViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly IStoragePickerService _storagePickerService;
|
||||||
|
protected readonly IFilenameService _filenameService;
|
||||||
|
protected readonly IApplicationDataService _applicationDataService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Uri _thumbnailUrl;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _title;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _author;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected DateTime _publishDate;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _duration;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected long _views;
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected ObservableCollection<VideoStream> _streams;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected VideoStream _selectedStream;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected MediaType _mediaType;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _trimStart;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _trimEnd;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _directoryPath;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _filename;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected VideoExtension _videoExtension;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected AudioExtension _audioExtension;
|
||||||
|
|
||||||
|
public Video Video { get; protected set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public VideoViewModel(Video video, ISettingsService settingsService, IStoragePickerService storagePickerService, IFilenameService filenameService, IApplicationDataService applicationDataService)
|
||||||
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_storagePickerService = storagePickerService;
|
||||||
|
_filenameService = filenameService;
|
||||||
|
_applicationDataService = applicationDataService;
|
||||||
|
|
||||||
|
Video = video;
|
||||||
|
|
||||||
|
ThumbnailUrl = video.ThumbnailUrl;
|
||||||
|
Title = video.Title;
|
||||||
|
Author = video.Author;
|
||||||
|
PublishDate = video.PublishDate;
|
||||||
|
Duration = video.Duration;
|
||||||
|
Views = video.Views;
|
||||||
|
|
||||||
|
Streams = [.. video.Streams];
|
||||||
|
SelectedStream = Streams[0];
|
||||||
|
MediaType = _settingsService.Data.Common.Tasks.DefaultMediaType;
|
||||||
|
TrimStart = TimeSpan.Zero;
|
||||||
|
TrimEnd = Duration;
|
||||||
|
Filename = _filenameService.CreateFilename(_settingsService.Data.Common.Tasks.FilenameTemplate, video);
|
||||||
|
VideoExtension = _settingsService.Data.Common.Tasks.DefaultVideoExtension;
|
||||||
|
AudioExtension = _settingsService.Data.Common.Tasks.DefaultAudioExtension;
|
||||||
|
|
||||||
|
if (_settingsService.Data.Common.Tasks.SaveLastOutputDirectory && !string.IsNullOrWhiteSpace(_applicationDataService.Data.Common.LastOutputDirectory))
|
||||||
|
{
|
||||||
|
DirectoryPath = _applicationDataService.Data.Common.LastOutputDirectory;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DirectoryPath = _settingsService.Data.Common.Tasks.DefaultOutputDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public VideoDownloadOptions BuildDownloadOptions()
|
||||||
|
{
|
||||||
|
return new VideoDownloadOptions(Duration)
|
||||||
|
{
|
||||||
|
MediaType = this.MediaType,
|
||||||
|
SelectedStream = this.SelectedStream,
|
||||||
|
TrimStart = this.TrimStart,
|
||||||
|
TrimEnd = this.TrimEnd,
|
||||||
|
Directory = this.DirectoryPath,
|
||||||
|
Filename = _filenameService.SanitizeFilename(this.Filename),
|
||||||
|
Extension = (this.MediaType == MediaType.OnlyAudio ? this.AudioExtension.ToString() : this.VideoExtension.ToString()).ToLower(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region COMMANDS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Browse()
|
||||||
|
{
|
||||||
|
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||||
|
if (newDirectory is not null)
|
||||||
|
{
|
||||||
|
this.DirectoryPath = newDirectory;
|
||||||
|
|
||||||
|
_applicationDataService.Data.Common.LastOutputDirectory = newDirectory;
|
||||||
|
await _applicationDataService.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Core.Tasks;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.UI.Dialogs;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Home
|
||||||
|
{
|
||||||
|
public partial class HomeDownloadsViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IDownloadTaskManager _tasksManager;
|
||||||
|
|
||||||
|
protected readonly IDialogsService _dialogsService;
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public ReadOnlyObservableCollection<DownloadTask> Tasks => _tasksManager.Tasks;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _taskListIsEmpty;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeDownloadsViewModel(IDownloadTaskManager tasksManager, IDialogsService dialogsService, ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
_tasksManager = tasksManager;
|
||||||
|
_tasksManager.TaskCollectionChanged += Tasks_CollectionChanged;
|
||||||
|
|
||||||
|
_dialogsService = dialogsService;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
|
||||||
|
_taskListIsEmpty = _tasksManager.Tasks.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task StartCancelTask(DownloadTask task)
|
||||||
|
{
|
||||||
|
DownloadTaskStatus[] idleStatuses =
|
||||||
|
[
|
||||||
|
DownloadTaskStatus.Idle,
|
||||||
|
DownloadTaskStatus.EndedUnsuccessfully,
|
||||||
|
DownloadTaskStatus.EndedSuccessfully,
|
||||||
|
DownloadTaskStatus.EndedCancelled
|
||||||
|
];
|
||||||
|
if (idleStatuses.Contains(task.Status))
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.HomeDownloadsView.Get("DialogErrorTitle");
|
||||||
|
string message = StringResourcesManager.HomeDownloadsView.Get("DialogErrorMessageNoInternetConnection");
|
||||||
|
await _dialogsService.ShowOk(title, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool continueEnqueue = true;
|
||||||
|
if
|
||||||
|
(
|
||||||
|
_settingsService.Data.Common.Tasks.ShowMeteredConnectionWarnings
|
||||||
|
&&
|
||||||
|
NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogTitle");
|
||||||
|
string message = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogMessage");
|
||||||
|
DialogResultYesNo result = await _dialogsService.ShowYesNo(title, message);
|
||||||
|
continueEnqueue = result == DialogResultYesNo.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (continueEnqueue)
|
||||||
|
{
|
||||||
|
task.Enqueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await task.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task RemoveTask(DownloadTask task)
|
||||||
|
{
|
||||||
|
await task.Cancel();
|
||||||
|
_tasksManager.RemoveTask(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private void Tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
TaskListIsEmpty = Tasks.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Tasks;
|
||||||
|
using VDownload.Core.ViewModels.Home.Helpers;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.UI.StoragePicker;
|
||||||
|
using VDownload.Sources.Twitch.Configuration.Models;
|
||||||
|
using SimpleToolkit.MVVM;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using VDownload.Services.Utility.Filename;
|
||||||
|
using VDownload.Services.UI.Dialogs;
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using VDownload.Services.Data.Application;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Home
|
||||||
|
{
|
||||||
|
public partial class HomeVideoCollectionViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IDownloadTaskManager _tasksManager;
|
||||||
|
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly IStoragePickerService _storagePickerService;
|
||||||
|
protected readonly IFilenameService _filenameService;
|
||||||
|
protected readonly IDialogsService _dialogsService;
|
||||||
|
protected readonly IApplicationDataService _applicationDataService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected VideoCollection _collection;
|
||||||
|
|
||||||
|
protected List<VideoViewModel> _removedVideos;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _name;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected ObservableDictionary<VideoViewModel, bool> _videos;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected int _removedCount;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected int _hiddenCount;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected bool _isSomethingHidden;
|
||||||
|
|
||||||
|
public string TitleFilter
|
||||||
|
{
|
||||||
|
get => _titleFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _titleFilter, value, nameof(TitleFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected string _titleFilter;
|
||||||
|
|
||||||
|
public string AuthorFilter
|
||||||
|
{
|
||||||
|
get => _authorFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _authorFilter, value, nameof(AuthorFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected string _authorFilter;
|
||||||
|
|
||||||
|
public long MinViewsFilter
|
||||||
|
{
|
||||||
|
get => _minViewsFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _minViewsFilter, value, nameof(MinViewsFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected long _minViewsFilter;
|
||||||
|
|
||||||
|
public long MaxViewsFilter
|
||||||
|
{
|
||||||
|
get => _maxViewsFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _maxViewsFilter, value, nameof(MaxViewsFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected long _maxViewsFilter;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected long _minViews;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected long _maxViews;
|
||||||
|
|
||||||
|
public DateTimeOffset MinDateFilter
|
||||||
|
{
|
||||||
|
get => _minDateFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _minDateFilter, value, nameof(MinDateFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected DateTimeOffset _minDateFilter;
|
||||||
|
|
||||||
|
public DateTimeOffset MaxDateFilter
|
||||||
|
{
|
||||||
|
get => _maxDateFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _maxDateFilter, value, nameof(MaxDateFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected DateTimeOffset _maxDateFilter;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected DateTimeOffset _minDate;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected DateTimeOffset _maxDate;
|
||||||
|
|
||||||
|
|
||||||
|
public TimeSpan MinDurationFilter
|
||||||
|
{
|
||||||
|
get => _minDurationFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _minDurationFilter, value, nameof(MinDurationFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected TimeSpan _minDurationFilter;
|
||||||
|
|
||||||
|
public TimeSpan MaxDurationFilter
|
||||||
|
{
|
||||||
|
get => _maxDurationFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _maxDurationFilter, value, nameof(MaxDurationFilter));
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected TimeSpan _maxDurationFilter;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _minDuration;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _maxDuration;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeVideoCollectionViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService, IFilenameService filenameService, IDialogsService dialogsService, IApplicationDataService applicationDataService)
|
||||||
|
{
|
||||||
|
_tasksManager = tasksManager;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_storagePickerService = storagePickerService;
|
||||||
|
_filenameService = filenameService;
|
||||||
|
_dialogsService = dialogsService;
|
||||||
|
_applicationDataService = applicationDataService;
|
||||||
|
|
||||||
|
_removedVideos = new List<VideoViewModel>();
|
||||||
|
|
||||||
|
_videos = new ObservableDictionary<VideoViewModel, bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public void LoadCollection(VideoCollection collection)
|
||||||
|
{
|
||||||
|
_collection = collection;
|
||||||
|
ParallelQuery<Video> playlistQuery = _collection.AsParallel();
|
||||||
|
|
||||||
|
_removedVideos.Clear();
|
||||||
|
|
||||||
|
_titleFilter = string.Empty;
|
||||||
|
_authorFilter = string.Empty;
|
||||||
|
|
||||||
|
IEnumerable<long> views = playlistQuery.Select(x => x.Views);
|
||||||
|
MinViews = views.Min();
|
||||||
|
MaxViews = views.Max();
|
||||||
|
_minViewsFilter = MinViews;
|
||||||
|
_maxViewsFilter = MaxViews;
|
||||||
|
|
||||||
|
IEnumerable<DateTimeOffset> date = _collection.Select(x => new DateTimeOffset(x.PublishDate));
|
||||||
|
MinDate = date.Min();
|
||||||
|
MaxDate = date.Max();
|
||||||
|
_minDateFilter = MinDate;
|
||||||
|
_maxDateFilter = MaxDate;
|
||||||
|
|
||||||
|
IEnumerable<TimeSpan> duration = playlistQuery.Select(x => x.Duration);
|
||||||
|
MinDuration = duration.Min();
|
||||||
|
MaxDuration = duration.Max();
|
||||||
|
_minDurationFilter = MinDuration;
|
||||||
|
_maxDurationFilter = MaxDuration;
|
||||||
|
|
||||||
|
Name = _collection.Name;
|
||||||
|
Videos.Clear();
|
||||||
|
foreach (Video video in _collection)
|
||||||
|
{
|
||||||
|
Videos.Add(new VideoViewModel(video, _settingsService, _storagePickerService, _filenameService, _applicationDataService), true);
|
||||||
|
}
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region COMMANDS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task SelectDirectory()
|
||||||
|
{
|
||||||
|
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||||
|
if (newDirectory is not null)
|
||||||
|
{
|
||||||
|
foreach (VideoViewModel video in Videos.Keys)
|
||||||
|
{
|
||||||
|
video.DirectoryPath = newDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicationDataService.Data.Common.LastOutputDirectory = newDirectory;
|
||||||
|
await _applicationDataService.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void RemoveVideo(VideoViewModel video)
|
||||||
|
{
|
||||||
|
Videos[video] = false;
|
||||||
|
|
||||||
|
_removedVideos.Add(video);
|
||||||
|
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void RestoreRemovedVideos()
|
||||||
|
{
|
||||||
|
foreach(VideoViewModel video in _removedVideos)
|
||||||
|
{
|
||||||
|
Videos[video] = true;
|
||||||
|
}
|
||||||
|
_removedVideos.Clear();
|
||||||
|
|
||||||
|
UpdateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task CreateTasksAndDownload() => await CreateTasks(true);
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task CreateTasks() => await CreateTasks(false);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected async Task CreateTasks(bool download)
|
||||||
|
{
|
||||||
|
if
|
||||||
|
(
|
||||||
|
download
|
||||||
|
&&
|
||||||
|
_settingsService.Data.Common.Tasks.ShowMeteredConnectionWarnings
|
||||||
|
&&
|
||||||
|
NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogTitle");
|
||||||
|
string message = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogMessage");
|
||||||
|
DialogResultYesNo result = await _dialogsService.ShowYesNo(title, message);
|
||||||
|
download = result == DialogResultYesNo.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<VideoViewModel> videos = Videos.Cast<ObservableKeyValuePair<VideoViewModel, bool>>()
|
||||||
|
.Where(x => x.Value)
|
||||||
|
.Select(x => x.Key);
|
||||||
|
|
||||||
|
foreach (VideoViewModel video in videos)
|
||||||
|
{
|
||||||
|
DownloadTask task = _tasksManager.AddTask(video.Video, video.BuildDownloadOptions());
|
||||||
|
if (download)
|
||||||
|
{
|
||||||
|
task.Enqueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateFilter()
|
||||||
|
{
|
||||||
|
Regex titleRegex = new Regex(TitleFilter);
|
||||||
|
Regex authorRegex = new Regex(AuthorFilter);
|
||||||
|
|
||||||
|
foreach (ObservableKeyValuePair<VideoViewModel, bool> item in Videos)
|
||||||
|
{
|
||||||
|
VideoViewModel video = item.Key;
|
||||||
|
bool hide =
|
||||||
|
(
|
||||||
|
_removedVideos.Contains(video)
|
||||||
|
||
|
||||||
|
!titleRegex.IsMatch(video.Title)
|
||||||
|
||
|
||||||
|
!authorRegex.IsMatch(video.Author)
|
||||||
|
||
|
||||||
|
MinViewsFilter > video.Views
|
||||||
|
||
|
||||||
|
MaxViewsFilter < video.Views
|
||||||
|
||
|
||||||
|
MinDateFilter.Date > video.PublishDate.Date
|
||||||
|
||
|
||||||
|
MaxDateFilter.Date < video.PublishDate.Date
|
||||||
|
||
|
||||||
|
MinDurationFilter > video.Duration
|
||||||
|
||
|
||||||
|
MaxDurationFilter < video.Duration
|
||||||
|
);
|
||||||
|
item.Value = !hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemovedCount = _removedVideos.Count;
|
||||||
|
HiddenCount = Videos.Values.Where(x => !x).Count();
|
||||||
|
IsSomethingHidden = HiddenCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENTS
|
||||||
|
|
||||||
|
public event EventHandler CloseRequested;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Core.Tasks;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Application;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.UI.Dialogs;
|
||||||
|
using VDownload.Services.UI.StoragePicker;
|
||||||
|
using VDownload.Services.Utility.Filename;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Home
|
||||||
|
{
|
||||||
|
public partial class HomeVideoViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IDownloadTaskManager _tasksManager;
|
||||||
|
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly IStoragePickerService _storagePickerService;
|
||||||
|
protected readonly IFilenameService _filenameService;
|
||||||
|
protected readonly IDialogsService _dialogsService;
|
||||||
|
protected readonly IApplicationDataService _applicationDataService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected Video _video;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Uri _thumbnailUrl;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _title;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _author;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected DateTime _publishDate;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _duration;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected long _views;
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected ObservableCollection<VideoStream> _streams;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected VideoStream _selectedStream;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected MediaType _mediaType;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _trimStart;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected TimeSpan _trimEnd;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _directoryPath;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _filename;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected VideoExtension _videoExtension;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected AudioExtension _audioExtension;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeVideoViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService, IFilenameService filenameService, IDialogsService dialogsService, IApplicationDataService applicationDataService)
|
||||||
|
{
|
||||||
|
_tasksManager = tasksManager;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_storagePickerService = storagePickerService;
|
||||||
|
_filenameService = filenameService;
|
||||||
|
_dialogsService = dialogsService;
|
||||||
|
_applicationDataService = applicationDataService;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task LoadVideo(Video video)
|
||||||
|
{
|
||||||
|
await _settingsService.Load();
|
||||||
|
|
||||||
|
_video = video;
|
||||||
|
|
||||||
|
ThumbnailUrl = video.ThumbnailUrl;
|
||||||
|
Title = video.Title;
|
||||||
|
Author = video.Author;
|
||||||
|
PublishDate = video.PublishDate;
|
||||||
|
Duration = video.Duration;
|
||||||
|
Views = video.Views;
|
||||||
|
|
||||||
|
Streams = [.. video.Streams];
|
||||||
|
SelectedStream = Streams[0];
|
||||||
|
MediaType = _settingsService.Data.Common.Tasks.DefaultMediaType;
|
||||||
|
TrimStart = TimeSpan.Zero;
|
||||||
|
TrimEnd = Duration;
|
||||||
|
Filename = _filenameService.CreateFilename(_settingsService.Data.Common.Tasks.FilenameTemplate, video);
|
||||||
|
VideoExtension = _settingsService.Data.Common.Tasks.DefaultVideoExtension;
|
||||||
|
AudioExtension = _settingsService.Data.Common.Tasks.DefaultAudioExtension;
|
||||||
|
|
||||||
|
if (_settingsService.Data.Common.Tasks.SaveLastOutputDirectory && !string.IsNullOrWhiteSpace(_applicationDataService.Data.Common.LastOutputDirectory))
|
||||||
|
{
|
||||||
|
DirectoryPath = _applicationDataService.Data.Common.LastOutputDirectory;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DirectoryPath = _settingsService.Data.Common.Tasks.DefaultOutputDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Browse()
|
||||||
|
{
|
||||||
|
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||||
|
if (newDirectory is not null)
|
||||||
|
{
|
||||||
|
this.DirectoryPath = newDirectory;
|
||||||
|
|
||||||
|
_applicationDataService.Data.Common.LastOutputDirectory = newDirectory;
|
||||||
|
await _applicationDataService.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task CreateTask() => await CreateTask(false);
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task CreateTaskAndDownload() => await CreateTask(true);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected async Task CreateTask(bool download)
|
||||||
|
{
|
||||||
|
if
|
||||||
|
(
|
||||||
|
download
|
||||||
|
&&
|
||||||
|
_settingsService.Data.Common.Tasks.ShowMeteredConnectionWarnings
|
||||||
|
&&
|
||||||
|
NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogTitle");
|
||||||
|
string message = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogMessage");
|
||||||
|
DialogResultYesNo result = await _dialogsService.ShowYesNo(title, message);
|
||||||
|
download = result == DialogResultYesNo.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadTask task = _tasksManager.AddTask(_video, BuildDownloadOptions());
|
||||||
|
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||||
|
if (download)
|
||||||
|
{
|
||||||
|
task.Enqueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected VideoDownloadOptions BuildDownloadOptions()
|
||||||
|
{
|
||||||
|
return new VideoDownloadOptions(Duration)
|
||||||
|
{
|
||||||
|
MediaType = this.MediaType,
|
||||||
|
SelectedStream = this.SelectedStream,
|
||||||
|
TrimStart = this.TrimStart,
|
||||||
|
TrimEnd = this.TrimEnd,
|
||||||
|
Directory = this.DirectoryPath,
|
||||||
|
Filename = _filenameService.SanitizeFilename(this.Filename),
|
||||||
|
Extension = (this.MediaType == MediaType.OnlyAudio ? this.AudioExtension.ToString() : this.VideoExtension.ToString()).ToLower(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENTS
|
||||||
|
|
||||||
|
public event EventHandler CloseRequested;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
429
VDownload.Core/VDownload.Core.ViewModels/Home/HomeViewModel.cs
Normal file
429
VDownload.Core/VDownload.Core.ViewModels/Home/HomeViewModel.cs
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Core.Tasks;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Configuration;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.Data.Subscriptions;
|
||||||
|
using VDownload.Services.UI.Dialogs;
|
||||||
|
using VDownload.Sources;
|
||||||
|
using VDownload.Sources.Common;
|
||||||
|
using VDownload.Sources.Twitch.Configuration.Models;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Home
|
||||||
|
{
|
||||||
|
public partial class HomeViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region ENUMS
|
||||||
|
|
||||||
|
public enum OptionBarContentType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
VideoSearch,
|
||||||
|
PlaylistSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MainContentType
|
||||||
|
{
|
||||||
|
Downloads,
|
||||||
|
Video
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly IConfigurationService _configurationService;
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly ISearchService _searchService;
|
||||||
|
protected readonly ISubscriptionsDataService _subscriptionsDataService;
|
||||||
|
protected readonly IDialogsService _dialogsService;
|
||||||
|
|
||||||
|
protected readonly IDownloadTaskManager _downloadTaskManager;
|
||||||
|
|
||||||
|
protected readonly HomeVideoViewModel _videoViewModel;
|
||||||
|
protected readonly HomeVideoCollectionViewModel _videoCollectionViewModel;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
protected readonly Type _downloadsView = typeof(HomeDownloadsViewModel);
|
||||||
|
protected readonly Type _videoView = typeof(HomeVideoViewModel);
|
||||||
|
protected readonly Type _videoCollectionView = typeof(HomeVideoCollectionViewModel);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Type _mainContent;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _optionBarError;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _optionBarIsErrorOpened;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private OptionBarContentType _optionBarContent;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _optionBarMessage;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _optionBarLoading;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _optionBarLoadSubscriptionButtonChecked;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _optionBarVideoSearchButtonChecked;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _optionBarPlaylistSearchButtonChecked;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _optionBarSearchNotPending;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _optionBarVideoSearchTBValue;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _optionBarPlaylistSearchTBValue;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _optionBarPlaylistSearchNBValue;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeViewModel(IConfigurationService configurationService, ISettingsService settingsService, ISearchService searchService, ISubscriptionsDataService subscriptionsDataService, IDialogsService dialogsService, IDownloadTaskManager downloadTaskManager, HomeVideoViewModel videoViewModel, HomeVideoCollectionViewModel videoCollectionViewModel)
|
||||||
|
{
|
||||||
|
_configurationService = configurationService;
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_searchService = searchService;
|
||||||
|
_subscriptionsDataService = subscriptionsDataService;
|
||||||
|
_dialogsService = dialogsService;
|
||||||
|
|
||||||
|
_downloadTaskManager = downloadTaskManager;
|
||||||
|
|
||||||
|
_videoViewModel = videoViewModel;
|
||||||
|
_videoViewModel.CloseRequested += BackToDownload_EventHandler;
|
||||||
|
|
||||||
|
_videoCollectionViewModel = videoCollectionViewModel;
|
||||||
|
_videoCollectionViewModel.CloseRequested += BackToDownload_EventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region COMMANDS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Navigation()
|
||||||
|
{
|
||||||
|
await _settingsService.Load();
|
||||||
|
|
||||||
|
MainContent = _downloadsView;
|
||||||
|
|
||||||
|
OptionBarContent = OptionBarContentType.None;
|
||||||
|
OptionBarLoading = false;
|
||||||
|
OptionBarMessage = null;
|
||||||
|
OptionBarError = null;
|
||||||
|
OptionBarIsErrorOpened = true;
|
||||||
|
OptionBarLoadSubscriptionButtonChecked = false;
|
||||||
|
OptionBarVideoSearchButtonChecked = false;
|
||||||
|
OptionBarPlaylistSearchButtonChecked = false;
|
||||||
|
OptionBarSearchNotPending = true;
|
||||||
|
OptionBarVideoSearchTBValue = string.Empty;
|
||||||
|
OptionBarPlaylistSearchNBValue = _settingsService.Data.Common.Searching.MaxNumberOfVideosToGetFromPlaylist;
|
||||||
|
OptionBarPlaylistSearchTBValue = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task LoadFromSubscription()
|
||||||
|
{
|
||||||
|
SearchButtonClicked();
|
||||||
|
|
||||||
|
if (OptionBarLoadSubscriptionButtonChecked)
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
ShowError(ErrorNoInternetConnection());
|
||||||
|
OptionBarLoadSubscriptionButtonChecked = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionBarContent = OptionBarContentType.None;
|
||||||
|
OptionBarVideoSearchButtonChecked = false;
|
||||||
|
OptionBarPlaylistSearchButtonChecked = false;
|
||||||
|
|
||||||
|
StartSearch();
|
||||||
|
|
||||||
|
SubscriptionsVideoList subList = new SubscriptionsVideoList { Name = StringResourcesManager.Common.Get("SubscriptionVideoListName") };
|
||||||
|
List<Task> tasks = new List<Task>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (Subscription sub in _subscriptionsDataService.Data.ToArray())
|
||||||
|
{
|
||||||
|
tasks.Add(Task.Run(async () =>
|
||||||
|
{
|
||||||
|
Playlist playlist;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
playlist = await _searchService.SearchPlaylist(sub.Url.OriginalString, 0);
|
||||||
|
}
|
||||||
|
catch (MediaSearchException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<Video> newIds = playlist.Where(x => !sub.VideoIds.Contains(x.Id));
|
||||||
|
subList.AddRange(newIds);
|
||||||
|
foreach (Video video in newIds)
|
||||||
|
{
|
||||||
|
sub.VideoIds.Add(video.Id);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
await _subscriptionsDataService.Save();
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is TaskCanceledException || ex is HttpRequestException)
|
||||||
|
{
|
||||||
|
ShowError(ErrorSearchTimeout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subList.Count > 0)
|
||||||
|
{
|
||||||
|
OptionBarMessage = $"{StringResourcesManager.HomeView.Get("OptionBarMessageVideosFound")} {subList.Count}";
|
||||||
|
|
||||||
|
_videoCollectionViewModel.LoadCollection(subList);
|
||||||
|
MainContent = _videoCollectionView;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OptionBarMessage = StringResourcesManager.HomeView.Get("OptionBarMessageVideosNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionBarSearchNotPending = true;
|
||||||
|
OptionBarLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void VideoSearchShow()
|
||||||
|
{
|
||||||
|
SearchButtonClicked();
|
||||||
|
|
||||||
|
if (OptionBarContent != OptionBarContentType.VideoSearch)
|
||||||
|
{
|
||||||
|
OptionBarContent = OptionBarContentType.VideoSearch;
|
||||||
|
OptionBarPlaylistSearchButtonChecked = false;
|
||||||
|
OptionBarLoadSubscriptionButtonChecked = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OptionBarContent = OptionBarContentType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void PlaylistSearchShow()
|
||||||
|
{
|
||||||
|
SearchButtonClicked();
|
||||||
|
|
||||||
|
if (OptionBarContent != OptionBarContentType.PlaylistSearch)
|
||||||
|
{
|
||||||
|
OptionBarContent = OptionBarContentType.PlaylistSearch;
|
||||||
|
OptionBarVideoSearchButtonChecked = false;
|
||||||
|
OptionBarLoadSubscriptionButtonChecked = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OptionBarContent = OptionBarContentType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task VideoSearchStart()
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
ShowError(ErrorNoInternetConnection());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartSearch();
|
||||||
|
|
||||||
|
Video video;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
video = await _searchService.SearchVideo(OptionBarVideoSearchTBValue);
|
||||||
|
}
|
||||||
|
catch (MediaSearchException ex)
|
||||||
|
{
|
||||||
|
ShowError(StringResourcesManager.Search.Get(ex.StringCode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is TaskCanceledException || ex is HttpRequestException)
|
||||||
|
{
|
||||||
|
ShowError(ErrorSearchTimeout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _videoViewModel.LoadVideo(video);
|
||||||
|
|
||||||
|
MainContent = _videoView;
|
||||||
|
|
||||||
|
OptionBarSearchNotPending = true;
|
||||||
|
OptionBarLoading = false;
|
||||||
|
OptionBarMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task PlaylistSearchStart()
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
ShowError(ErrorNoInternetConnection());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartSearch();
|
||||||
|
|
||||||
|
Playlist playlist;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
playlist = await _searchService.SearchPlaylist(OptionBarPlaylistSearchTBValue, OptionBarPlaylistSearchNBValue);
|
||||||
|
}
|
||||||
|
catch (MediaSearchException ex)
|
||||||
|
{
|
||||||
|
ShowError(StringResourcesManager.Search.Get(ex.StringCode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is TaskCanceledException || ex is HttpRequestException)
|
||||||
|
{
|
||||||
|
ShowError(ErrorSearchTimeout());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_videoCollectionViewModel.LoadCollection(playlist);
|
||||||
|
|
||||||
|
MainContent = _videoCollectionView;
|
||||||
|
|
||||||
|
OptionBarSearchNotPending = true;
|
||||||
|
OptionBarLoading = false;
|
||||||
|
OptionBarMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Download()
|
||||||
|
{
|
||||||
|
if (_downloadTaskManager.Tasks.Count > 0)
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
ShowError(ErrorNoInternetConnection());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if
|
||||||
|
(
|
||||||
|
_settingsService.Data.Common.Tasks.ShowMeteredConnectionWarnings
|
||||||
|
&&
|
||||||
|
NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string title = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogTitle");
|
||||||
|
string message = StringResourcesManager.Common.Get("StartAtMeteredConnectionDialogMessage");
|
||||||
|
DialogResultYesNo result = await _dialogsService.ShowYesNo(title, message);
|
||||||
|
if (result == DialogResultYesNo.No)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DownloadTask task in _downloadTaskManager.Tasks)
|
||||||
|
{
|
||||||
|
task.Enqueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Cancel()
|
||||||
|
{
|
||||||
|
List<Task> tasks = new List<Task>();
|
||||||
|
foreach (DownloadTask task in _downloadTaskManager.Tasks)
|
||||||
|
{
|
||||||
|
tasks.Add(task.Cancel());
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void CloseError()
|
||||||
|
{
|
||||||
|
OptionBarError = null;
|
||||||
|
OptionBarIsErrorOpened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected void ShowError(string message)
|
||||||
|
{
|
||||||
|
OptionBarError = message;
|
||||||
|
OptionBarSearchNotPending = true;
|
||||||
|
OptionBarLoading = false;
|
||||||
|
OptionBarMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SearchButtonClicked()
|
||||||
|
{
|
||||||
|
OptionBarSearchNotPending = true;
|
||||||
|
OptionBarLoading = false;
|
||||||
|
OptionBarMessage = null;
|
||||||
|
MainContent = _downloadsView;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void StartSearch()
|
||||||
|
{
|
||||||
|
OptionBarSearchNotPending = false;
|
||||||
|
OptionBarLoading = true;
|
||||||
|
OptionBarMessage = StringResourcesManager.HomeView.Get("OptionBarMessageLoading");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async void BackToDownload_EventHandler(object sender, EventArgs e) => await Navigation();
|
||||||
|
|
||||||
|
protected string ErrorNoInternetConnection() => StringResourcesManager.HomeView.Get("ErrorInfoBarNoInternetConnection");
|
||||||
|
|
||||||
|
protected string ErrorSearchTimeout() => StringResourcesManager.Search.Get("SearchTimeout");
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Configuration;
|
||||||
|
using VDownload.Services.Data.Settings;
|
||||||
|
using VDownload.Services.UI.StoragePicker;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Settings
|
||||||
|
{
|
||||||
|
public partial class SettingsViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly ISettingsService _settingsService;
|
||||||
|
protected readonly IConfigurationService _configurationService;
|
||||||
|
protected readonly IStoragePickerService _storagePickerService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public int SearchingPlaylistCount
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Searching.MaxNumberOfVideosToGetFromPlaylist;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Searching.MaxNumberOfVideosToGetFromPlaylist, value, _settingsService.Data.Common.Searching, (u, n) => u.MaxNumberOfVideosToGetFromPlaylist = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TasksRunningTasks
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.MaxNumberOfRunningTasks;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.MaxNumberOfRunningTasks, value, _settingsService.Data.Common.Tasks, (u, n) => u.MaxNumberOfRunningTasks = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaType TasksMediaType
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.DefaultMediaType;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.DefaultMediaType, value, _settingsService.Data.Common.Tasks, (u, n) => u.DefaultMediaType = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoExtension TasksVideoExtension
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.DefaultVideoExtension;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.DefaultVideoExtension, value, _settingsService.Data.Common.Tasks, (u, n) => u.DefaultVideoExtension = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioExtension TasksAudioExtension
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.DefaultAudioExtension;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.DefaultAudioExtension, value, _settingsService.Data.Common.Tasks, (u, n) => u.DefaultAudioExtension = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TasksFilenameTemplate
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.FilenameTemplate;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.FilenameTemplate, value, _settingsService.Data.Common.Tasks, (u, n) => u.FilenameTemplate = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TasksMeteredConnectionWarning
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.ShowMeteredConnectionWarnings;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.ShowMeteredConnectionWarnings, value, _settingsService.Data.Common.Tasks, (u, n) => u.ShowMeteredConnectionWarnings = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TasksSaveLastOutputDirectory
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.SaveLastOutputDirectory;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.SaveLastOutputDirectory, value, _settingsService.Data.Common.Tasks, (u, n) => u.SaveLastOutputDirectory = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TasksDefaultOutputDirectory
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.DefaultOutputDirectory;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.DefaultOutputDirectory, value, _settingsService.Data.Common.Tasks, (u, n) => u.DefaultOutputDirectory = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TasksReplaceOutputFile
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Tasks.ReplaceOutputFileIfExists;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Tasks.ReplaceOutputFileIfExists, value, _settingsService.Data.Common.Tasks, (u, n) => u.ReplaceOutputFileIfExists = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ProcessingFFmpegLocation
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Processing.FFmpegLocation;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Processing.FFmpegLocation, value, _settingsService.Data.Common.Processing, (u, n) => u.FFmpegLocation = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ProcessingUseHardwareAcceleration
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Processing.UseHardwareAcceleration;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Processing.UseHardwareAcceleration, value, _settingsService.Data.Common.Processing, (u, n) => u.UseHardwareAcceleration = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ProcessingUseMultithreading
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Processing.UseMultithreading;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Processing.UseMultithreading, value, _settingsService.Data.Common.Processing, (u, n) => u.UseMultithreading = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProcessingSpeed ProcessingSpeed
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Processing.Speed;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Processing.Speed, value, _settingsService.Data.Common.Processing, (u, n) => u.Speed = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NotificationsOnSuccessful
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Notifications.OnSuccessful;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Notifications.OnSuccessful, value, _settingsService.Data.Common.Notifications, (u, n) => u.OnSuccessful = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NotificationsOnUnsuccessful
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Notifications.OnUnsuccessful;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Notifications.OnUnsuccessful, value, _settingsService.Data.Common.Notifications, (u, n) => u.OnUnsuccessful = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TempDirectory
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Temp.Directory;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Temp.Directory, value, _settingsService.Data.Common.Temp, (u, n) => u.Directory = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TempDeleteOnFail
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Common.Temp.DeleteOnError;
|
||||||
|
set => SetProperty(_settingsService.Data.Common.Temp.DeleteOnError, value, _settingsService.Data.Common.Temp, (u, n) => u.DeleteOnError = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TwitchVodPassiveTrimming
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Twitch.Vod.PassiveTrimming;
|
||||||
|
set => SetProperty(_settingsService.Data.Twitch.Vod.PassiveTrimming, value, _settingsService.Data.Twitch.Vod, (u, n) => u.PassiveTrimming = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TwitchVodParallelDownloads
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Twitch.Vod.MaxNumberOfParallelDownloads;
|
||||||
|
set => SetProperty(_settingsService.Data.Twitch.Vod.MaxNumberOfParallelDownloads, value, _settingsService.Data.Twitch.Vod, (u, n) => u.MaxNumberOfParallelDownloads = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TwitchVodChunkDownloadingErrorRetry
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Twitch.Vod.ChunkDownloadingError.Retry;
|
||||||
|
set => SetProperty(_settingsService.Data.Twitch.Vod.ChunkDownloadingError.Retry, value, _settingsService.Data.Twitch.Vod.ChunkDownloadingError, (u, n) => u.Retry = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TwitchVodChunkDownloadingErrorRetryCount
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Twitch.Vod.ChunkDownloadingError.RetriesCount;
|
||||||
|
set => SetProperty(_settingsService.Data.Twitch.Vod.ChunkDownloadingError.RetriesCount, value, _settingsService.Data.Twitch.Vod.ChunkDownloadingError, (u, n) => u.RetriesCount = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TwitchVodChunkDownloadingErrorRetryDelay
|
||||||
|
{
|
||||||
|
get => _settingsService.Data.Twitch.Vod.ChunkDownloadingError.RetryDelay;
|
||||||
|
set => SetProperty(_settingsService.Data.Twitch.Vod.ChunkDownloadingError.RetryDelay, value, _settingsService.Data.Twitch.Vod.ChunkDownloadingError, (u, n) => u.RetryDelay = n);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _tasksFilenameTemplateTooltip;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public SettingsViewModel(ISettingsService settingsService, IConfigurationService configurationService, IStoragePickerService storagePickerService)
|
||||||
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
|
_configurationService = configurationService;
|
||||||
|
_storagePickerService = storagePickerService;
|
||||||
|
|
||||||
|
base.PropertyChanged += PropertyChangedEventHandler;
|
||||||
|
|
||||||
|
_tasksFilenameTemplateTooltip = string.Join('\n', _configurationService.Common.FilenameTemplates.Select(x => StringResourcesManager.FilenameTemplate.Get(x.Name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region COMMANDS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task BrowseTasksDefaultOutputDirectory()
|
||||||
|
{
|
||||||
|
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||||
|
if (newDirectory is not null)
|
||||||
|
{
|
||||||
|
this.TasksDefaultOutputDirectory = newDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task BrowseTempDirectory()
|
||||||
|
{
|
||||||
|
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||||
|
if (newDirectory is not null)
|
||||||
|
{
|
||||||
|
this.TempDirectory = newDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task BrowseProcessingFFmpegLocation()
|
||||||
|
{
|
||||||
|
string? newDirectory = await _storagePickerService.OpenDirectory();
|
||||||
|
if (newDirectory is not null)
|
||||||
|
{
|
||||||
|
this.ProcessingFFmpegLocation = newDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task RestoreToDefault()
|
||||||
|
{
|
||||||
|
await _settingsService.Restore();
|
||||||
|
foreach (PropertyInfo property in this.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(property.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private async void PropertyChangedEventHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
await _settingsService.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Models;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Subscriptions.Helpers
|
||||||
|
{
|
||||||
|
public partial class PlaylistViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Guid _guid;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _name;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected Source _source;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public PlaylistViewModel(string name, Source source, Guid guid)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_source = source;
|
||||||
|
_guid = guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.WinUI.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Strings;
|
||||||
|
using VDownload.Core.ViewModels.Subscriptions.Helpers;
|
||||||
|
using VDownload.Models;
|
||||||
|
using VDownload.Services.Data.Subscriptions;
|
||||||
|
using VDownload.Sources;
|
||||||
|
using VDownload.Sources.Common;
|
||||||
|
|
||||||
|
namespace VDownload.Core.ViewModels.Subscriptions
|
||||||
|
{
|
||||||
|
public partial class SubscriptionsViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
protected readonly ISearchService _searchService;
|
||||||
|
protected readonly ISubscriptionsDataService _subscriptionsDataService;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected ObservableCollection<PlaylistViewModel> _playlists;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _url;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected bool _loading;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected string _error;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
protected bool _isErrorOpened;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public SubscriptionsViewModel(ISearchService searchService, ISubscriptionsDataService subscriptionsDataService)
|
||||||
|
{
|
||||||
|
_searchService = searchService;
|
||||||
|
_subscriptionsDataService = subscriptionsDataService;
|
||||||
|
|
||||||
|
_playlists = new ObservableCollection<PlaylistViewModel>();
|
||||||
|
_loading = false;
|
||||||
|
_isErrorOpened = true;
|
||||||
|
_error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region COMMANDS
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void Navigation()
|
||||||
|
{
|
||||||
|
Playlists.Clear();
|
||||||
|
foreach (Subscription sub in _subscriptionsDataService.Data)
|
||||||
|
{
|
||||||
|
Playlists.Add(new PlaylistViewModel(sub.Name, sub.Source, sub.Guid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task RemovePlaylist(PlaylistViewModel playlist)
|
||||||
|
{
|
||||||
|
Playlists.Remove(playlist);
|
||||||
|
|
||||||
|
Subscription sub = _subscriptionsDataService.Data.FirstOrDefault(x => x.Guid == playlist.Guid);
|
||||||
|
_subscriptionsDataService.Data.Remove(sub);
|
||||||
|
await _subscriptionsDataService.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task Add()
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
ShowError(StringResourcesManager.SubscriptionsView.Get("NoInternetConnectionError"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading = true;
|
||||||
|
|
||||||
|
Playlist playlist;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
playlist = await _searchService.SearchPlaylist(Url, 0);
|
||||||
|
}
|
||||||
|
catch (MediaSearchException ex)
|
||||||
|
{
|
||||||
|
ShowError(StringResourcesManager.Search.Get(ex.StringCode));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is TaskCanceledException || ex is HttpRequestException)
|
||||||
|
{
|
||||||
|
ShowError(StringResourcesManager.Search.Get("SearchTimeout"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_subscriptionsDataService.Data.Any(x => x.Source == playlist.Source && x.Name == playlist.Name))
|
||||||
|
{
|
||||||
|
ShowError(StringResourcesManager.SubscriptionsView.Get("DuplicateError"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Subscription subscription = new Subscription
|
||||||
|
{
|
||||||
|
Name = playlist.Name,
|
||||||
|
Source = playlist.Source,
|
||||||
|
Url = playlist.Url,
|
||||||
|
};
|
||||||
|
playlist.ForEach(x => subscription.VideoIds.Add(x.Id));
|
||||||
|
|
||||||
|
_subscriptionsDataService.Data.Add(subscription);
|
||||||
|
await _subscriptionsDataService.Save();
|
||||||
|
Playlists.Add(new PlaylistViewModel(subscription.Name, subscription.Source, subscription.Guid));
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public void CloseError()
|
||||||
|
{
|
||||||
|
Error = null;
|
||||||
|
IsErrorOpened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected void ShowError(string message)
|
||||||
|
{
|
||||||
|
Error = message;
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>VDownload.Core.ViewModels</RootNamespace>
|
||||||
|
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<UseRidGraph>true</UseRidGraph>
|
||||||
|
<EnableCoreMrtTooling Condition=" '$(BuildingInsideVisualStudio)' != 'true' ">false</EnableCoreMrtTooling>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.0.240109" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||||
|
<PackageReference Include="SimpleToolkit.MVVM" Version="1.7.4" />
|
||||||
|
<PackageReference Include="SimpleToolkit.UI.Models" Version="1.7.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Application\VDownload.Services.Data.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Settings\VDownload.Services.Data.Settings.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.Dialogs\VDownload.Services.UI.Dialogs.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.DictionaryResources\VDownload.Services.UI.DictionaryResources.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StoragePicker\VDownload.Services.UI.StoragePicker.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.WebView\VDownload.Services.UI.WebView.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.Filename\VDownload.Services.Utility.Filename.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Authentication\VDownload.Sources.Twitch.Authentication.csproj" />
|
||||||
|
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources\VDownload.Sources.csproj" />
|
||||||
|
<ProjectReference Include="..\VDownload.Core.Tasks\VDownload.Core.Tasks.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
93
VDownload.Core/VDownload.Core.Views/About/AboutView.xaml
Normal file
93
VDownload.Core/VDownload.Core.Views/About/AboutView.xaml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.About.AboutView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.About"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="Transparent">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:EventTriggerBehavior EventName="Loaded">
|
||||||
|
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
|
||||||
|
</ic:EventTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
<Grid>
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" Spacing="20">
|
||||||
|
<StackPanel HorizontalAlignment="Center">
|
||||||
|
<Image Source="{StaticResource ImageLogo}"
|
||||||
|
Width="150"
|
||||||
|
Height="150"
|
||||||
|
HorizontalAlignment="Center"/>
|
||||||
|
<TextBlock Text="VDownload"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
FontSize="30"/>
|
||||||
|
<TextBlock Text="{Binding Version}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="16"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
Spacing="2">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AboutViewResources/Developers"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="17"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<ItemsRepeater HorizontalAlignment="Center"
|
||||||
|
ItemsSource="{Binding Developers}">
|
||||||
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<HyperlinkButton HorizontalAlignment="Center"
|
||||||
|
NavigateUri="{Binding Url}">
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
Text="{Binding Name}"/>
|
||||||
|
</HyperlinkButton>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
</ItemsRepeater>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
Spacing="2">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AboutViewResources/Translation"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="17"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<ItemsRepeater HorizontalAlignment="Center"
|
||||||
|
ItemsSource="{Binding Translators}">
|
||||||
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<HyperlinkButton HorizontalAlignment="Center"
|
||||||
|
NavigateUri="{Binding Url}">
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
Text="{Binding Name}"/>
|
||||||
|
</HyperlinkButton>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
</ItemsRepeater>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
Spacing="2">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AboutViewResources/More"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="17"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<HyperlinkButton HorizontalAlignment="Center"
|
||||||
|
NavigateUri="{Binding RepositoryUrl}">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AboutViewResources/Repository"
|
||||||
|
FontSize="12"/>
|
||||||
|
</HyperlinkButton>
|
||||||
|
<HyperlinkButton HorizontalAlignment="Center"
|
||||||
|
NavigateUri="{Binding DonationUrl}">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AboutViewResources/Donation"
|
||||||
|
FontSize="12"/>
|
||||||
|
</HyperlinkButton>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
32
VDownload.Core/VDownload.Core.Views/About/AboutView.xaml.cs
Normal file
32
VDownload.Core/VDownload.Core.Views/About/AboutView.xaml.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using VDownload.Core.ViewModels.About;
|
||||||
|
using VDownload.Core.ViewModels.Home;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.About
|
||||||
|
{
|
||||||
|
public sealed partial class AboutView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public AboutView(AboutViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Authentication.AuthenticationView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.Authentication"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ViewBackgroundColor}">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:EventTriggerBehavior EventName="Loaded">
|
||||||
|
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
|
||||||
|
</ic:EventTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
<Grid Padding="20"
|
||||||
|
RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/Header"
|
||||||
|
Grid.Row="0"
|
||||||
|
FontSize="28"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Spacing="10">
|
||||||
|
<ctc:SettingsCard Header="Twitch">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="NotEqual"
|
||||||
|
Value="Loading">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Description"
|
||||||
|
Value="{Binding TwitchDescription}"/>
|
||||||
|
<ic:ChangePropertyAction PropertyName="Content">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<Button Command="{Binding TwitchAuthenticationCommand}"
|
||||||
|
IsEnabled="{Binding TwitchButtonEnable}">
|
||||||
|
<Button.Content>
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="SignIn">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/AuthenticationButtonSignIn"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="SignOut">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/AuthenticationButtonSignOut"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Loading">
|
||||||
|
<ic:ChangePropertyAction x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/AuthenticationDescriptionLoading"
|
||||||
|
PropertyName="Description"/>
|
||||||
|
<ic:ChangePropertyAction PropertyName="Content">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<ProgressRing Width="20"
|
||||||
|
Height="20"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using VDownload.Core.ViewModels.Authentication;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Authentication
|
||||||
|
{
|
||||||
|
public sealed partial class AuthenticationView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public AuthenticationView(AuthenticationViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
67
VDownload.Core/VDownload.Core.Views/BaseWindow.xaml
Normal file
67
VDownload.Core/VDownload.Core.Views/BaseWindow.xaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Window
|
||||||
|
x:Class="VDownload.Core.Views.BaseWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
xmlns:cb="using:SimpleToolkit.UI.WinUI.Behaviors"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Window.SystemBackdrop>
|
||||||
|
<MicaBackdrop Kind="Base"/>
|
||||||
|
</Window.SystemBackdrop>
|
||||||
|
<Grid x:Name="Root"
|
||||||
|
Loaded="Root_Loaded">
|
||||||
|
<Border x:Name="AppTitleBar"
|
||||||
|
IsHitTestVisible="True"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Height="40"
|
||||||
|
Canvas.ZIndex="1"
|
||||||
|
Margin="55,4,0,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Image HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{StaticResource ImageLogo}"
|
||||||
|
Width="16"
|
||||||
|
Height="16"/>
|
||||||
|
<TextBlock Text="VDownload"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="12, 0, 0, 0"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<NavigationView IsTitleBarAutoPaddingEnabled="True"
|
||||||
|
IsBackButtonVisible="Collapsed"
|
||||||
|
PaneDisplayMode="LeftCompact"
|
||||||
|
Canvas.ZIndex="0"
|
||||||
|
MenuItemsSource="{Binding Items}"
|
||||||
|
FooterMenuItemsSource="{Binding FooterItems}"
|
||||||
|
SelectedItem="{Binding SelectedItem}"
|
||||||
|
Background="Transparent">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<cb:EventToCommandBehavior Command="{Binding NavigateCommand}"
|
||||||
|
Event="ItemInvoked"
|
||||||
|
PassArguments="True"/>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
<NavigationView.Resources>
|
||||||
|
<SolidColorBrush x:Key="NavigationViewContentBackground"
|
||||||
|
Color="Transparent"/>
|
||||||
|
</NavigationView.Resources>
|
||||||
|
<NavigationView.MenuItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<NavigationViewItem Content="{Binding Name}" Tag="{Binding}">
|
||||||
|
<NavigationViewItem.Icon>
|
||||||
|
<ImageIcon Source="{Binding IconSource}"/>
|
||||||
|
</NavigationViewItem.Icon>
|
||||||
|
</NavigationViewItem>
|
||||||
|
</DataTemplate>
|
||||||
|
</NavigationView.MenuItemTemplate>
|
||||||
|
<Frame Margin="0,48,0,0"
|
||||||
|
CornerRadius="10"
|
||||||
|
Content="{Binding CurrentViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ViewModelToViewConverter}}">
|
||||||
|
</Frame>
|
||||||
|
</NavigationView>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
70
VDownload.Core/VDownload.Core.Views/BaseWindow.xaml.cs
Normal file
70
VDownload.Core/VDownload.Core.Views/BaseWindow.xaml.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Windowing;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using VDownload.Core.ViewModels;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views
|
||||||
|
{
|
||||||
|
public sealed partial class BaseWindow : Window
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public XamlRoot XamlRoot => this.Root.XamlRoot;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENTS
|
||||||
|
|
||||||
|
public event EventHandler RootLoaded;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public BaseWindow(BaseViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.Activated += BaseWindow_Activated;
|
||||||
|
|
||||||
|
this.ExtendsContentIntoTitleBar = true;
|
||||||
|
this.SetTitleBar(this.AppTitleBar);
|
||||||
|
|
||||||
|
this.Root.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private void Root_Loaded(object sender, RoutedEventArgs e) => RootLoaded?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
private void BaseWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||||
|
WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
|
||||||
|
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
|
||||||
|
appWindow.SetIcon(@"Assets\Logo\Logo.ico");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
397
VDownload.Core/VDownload.Core.Views/Home/HomeDownloadsView.xaml
Normal file
397
VDownload.Core/VDownload.Core.Views/Home/HomeDownloadsView.xaml
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Home.HomeDownloadsView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.Home"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
xmlns:m="using:VDownload.Models"
|
||||||
|
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||||
|
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:ctb="using:CommunityToolkit.WinUI.Behaviors"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Name="Root"
|
||||||
|
Background="Transparent">
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding TaskListIsEmpty, Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="True">
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Image Source="{StaticResource ImageHomeDownloadsViewNoTasks}"
|
||||||
|
Width="100"/>
|
||||||
|
<TextBlock Text="Click Video/Playlist search button to add new tasks"
|
||||||
|
Foreground="{StaticResource GreyText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="False">
|
||||||
|
<ScrollViewer>
|
||||||
|
<ItemsControl ItemsSource="{Binding Tasks}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Background="{ThemeResource ViewBackgroundColor}"
|
||||||
|
CornerRadius="10"
|
||||||
|
Height="150"
|
||||||
|
Margin="0,0,0,10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Source="{Binding Video.ThumbnailUrl, TargetNullValue={StaticResource ImageOtherThumbnail}}"
|
||||||
|
VerticalAlignment="Stretch"/>
|
||||||
|
<Grid Grid.Column="1"
|
||||||
|
Margin="10"
|
||||||
|
RowSpacing="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
ColumnSpacing="10"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
FontSize="18"
|
||||||
|
Text="{Binding Video.Title}"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="Light"
|
||||||
|
FontSize="12"
|
||||||
|
Text="{Binding Video.Author}"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="1"
|
||||||
|
RowSpacing="10"
|
||||||
|
ColumnSpacing="10">
|
||||||
|
<Grid.Resources>
|
||||||
|
<x:Double x:Key="TextSize">12</x:Double>
|
||||||
|
</Grid.Resources>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Source="{ThemeResource ImageHomeDownloadsViewQuality}"/>
|
||||||
|
<StackPanel Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding DownloadOptions.MediaType, Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="Original">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOriginal"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyVideo">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyVideo"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyAudio">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyAudio"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
<TextBlock FontSize="{StaticResource TextSize}">
|
||||||
|
<Run Text=" ("/><Run Text="{Binding DownloadOptions.SelectedStream.Name}"/>)
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<Image Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Source="{ThemeResource ImageHomeDownloadsViewTime}"/>
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding DownloadOptions.DurationAfterTrim}"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
<TextBlock Visibility="{Binding DownloadOptions.IsTrimmed, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||||
|
FontSize="{StaticResource TextSize}">
|
||||||
|
<Run Text=" "/>(<Run Text="{Binding DownloadOptions.TrimStart}"/> - <Run Text="{Binding DownloadOptions.TrimEnd}"/>)
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<Image Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Source="{ThemeResource ImageHomeDownloadsViewFile}"/>
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
FontSize="{StaticResource TextSize}"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Run Text="{Binding DownloadOptions.Directory}"/>\<Run Text="{Binding DownloadOptions.Filename}"/>.<Run Text="{Binding DownloadOptions.Extension}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<Image Grid.Row="3"
|
||||||
|
Grid.Column="0">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Idle">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewIdle}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Queued">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewQueued}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Initializing">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewInitializing}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Downloading">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewDownloading}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Processing">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewProcessing}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Finalizing">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewFinalizing}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="EndedUnsuccessfully">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewError}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="EndedSuccessfully">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewDone}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="EndedCancelled">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{ThemeResource ImageHomeDownloadsViewCancelled}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</Image>
|
||||||
|
<Border Grid.Row="3"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Idle">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusIdle"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Queued">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusQueued"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Initializing">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusInitializing"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Downloading">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusDownloading"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
<TextBlock Text="{Binding Progress, Converter={StaticResource StringFormatConverter}, ConverterParameter='{} ({0:0.##}%)'}"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Processing">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock FontSize="{StaticResource TextSize}">
|
||||||
|
<Run x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusProcessing"/><Run Text=" ("/><Run Text="{Binding Progress, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:0.##}'}"/>%)
|
||||||
|
</TextBlock>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Finalizing">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusFinalizing"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="EndedUnsuccessfully">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock FontSize="{StaticResource TextSize}">
|
||||||
|
<Run x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusError"/><Run Text=" ("/><Run Text="{Binding Error}"/>)
|
||||||
|
</TextBlock>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="EndedSuccessfully">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusDone"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="EndedCancelled">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Child">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusCancelled"
|
||||||
|
FontSize="{StaticResource TextSize}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Column="2"
|
||||||
|
Margin="0,0,5,0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<AppBarButton Grid.Row="0"
|
||||||
|
Width="40"
|
||||||
|
Height="48">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:EventTriggerBehavior EventName="Click">
|
||||||
|
<ctb:NavigateToUriAction NavigateUri="{Binding Video.Url}"/>
|
||||||
|
</ic:EventTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Video.Source, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="Twitch">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Icon">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</AppBarButton>
|
||||||
|
<AppBarButton Grid.Row="1"
|
||||||
|
Width="40"
|
||||||
|
Height="48"
|
||||||
|
Command="{Binding ElementName=Root, Path=DataContext.StartCancelTaskCommand}"
|
||||||
|
CommandParameter="{Binding}">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
|
||||||
|
ComparisonCondition="LessThan"
|
||||||
|
Value="4">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Icon">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<SymbolIcon Symbol="Download"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
|
||||||
|
ComparisonCondition="GreaterThanOrEqual"
|
||||||
|
Value="4">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Icon">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<SymbolIcon Symbol="Cancel"/>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</AppBarButton>
|
||||||
|
<AppBarButton Grid.Row="2"
|
||||||
|
Icon="Delete"
|
||||||
|
Width="40"
|
||||||
|
Height="48"
|
||||||
|
Command="{Binding ElementName=Root, Path=DataContext.RemoveTaskCommand}"
|
||||||
|
CommandParameter="{Binding}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<ProgressBar Grid.Row="1"
|
||||||
|
Value="{Binding Progress}"
|
||||||
|
Maximum="100">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
|
||||||
|
ComparisonCondition="LessThan"
|
||||||
|
Value="5">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Visibility" Value="Collapsed"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
|
||||||
|
ComparisonCondition="GreaterThanOrEqual"
|
||||||
|
Value="5">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Visibility" Value="Visible"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
|
||||||
|
ComparisonCondition="LessThan"
|
||||||
|
Value="7">
|
||||||
|
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="True"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
|
||||||
|
ComparisonCondition="GreaterThanOrEqual"
|
||||||
|
Value="7">
|
||||||
|
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="False"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</ProgressBar>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using VDownload.Core.ViewModels.Home;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Home
|
||||||
|
{
|
||||||
|
public sealed partial class HomeDownloadsView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeDownloadsView(HomeDownloadsViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Home.HomeVideoCollectionView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.Home"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
xmlns:m="using:VDownload.Models"
|
||||||
|
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||||
|
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:c="using:SimpleToolkit.UI.WinUI.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ViewBackgroundColor}"
|
||||||
|
x:Name="Root">
|
||||||
|
|
||||||
|
<Grid Padding="15"
|
||||||
|
RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Grid.Row="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Name}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="20"
|
||||||
|
TextWrapping="WrapWholeWords"/>
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="-5"
|
||||||
|
Spacing="10">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Visibility="{Binding IsSomethingHidden, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<Run x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/HiddenTextBlock"/><Run Text="{Binding HiddenCount}"/>
|
||||||
|
</TextBlock>
|
||||||
|
<AppBarToggleButton x:Name="FilterButton"
|
||||||
|
Icon="Filter"
|
||||||
|
Width="40"
|
||||||
|
Height="48">
|
||||||
|
<AppBarToggleButton.Resources>
|
||||||
|
<TeachingTip x:Name="FilterWindow"
|
||||||
|
x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterWindow"
|
||||||
|
Target="{Binding ElementName=FilterButton}"
|
||||||
|
PreferredPlacement="BottomLeft"
|
||||||
|
IsOpen="{Binding ElementName=FilterButton, Path=IsChecked, Mode=TwoWay}">
|
||||||
|
<Grid VerticalAlignment="Top"
|
||||||
|
Margin="0,10,0,0"
|
||||||
|
ColumnSpacing="10"
|
||||||
|
RowSpacing="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterTitleTextBlock"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBox x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterTitleTextBox"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding TitleFilter, Mode=TwoWay}"/>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterAuthorTextBlock"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBox x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterAuthorTextBox"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding AuthorFilter, Mode=TwoWay}"/>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterViewsTextBlock"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Grid Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
ColumnSpacing="5">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<NumberBox Grid.Column="0"
|
||||||
|
SpinButtonPlacementMode="Compact"
|
||||||
|
Minimum="{Binding MinViews}"
|
||||||
|
Value="{Binding MinViewsFilter, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding MaxViewsFilter}"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="-"/>
|
||||||
|
<NumberBox Grid.Column="2"
|
||||||
|
SpinButtonPlacementMode="Compact"
|
||||||
|
Minimum="{Binding MinViewsFilter}"
|
||||||
|
Value="{Binding MaxViewsFilter, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding MaxViews}"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"/>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterDateTextBlock"
|
||||||
|
Grid.Row="3"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Grid Grid.Row="3"
|
||||||
|
Grid.Column="1"
|
||||||
|
ColumnSpacing="5">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<CalendarDatePicker Grid.Column="0"
|
||||||
|
MinDate="{Binding MinDate}"
|
||||||
|
Date="{Binding MinDateFilter, Mode=TwoWay}"
|
||||||
|
MaxDate="{Binding MaxDateFilter}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="-"/>
|
||||||
|
<CalendarDatePicker Grid.Column="2"
|
||||||
|
MinDate="{Binding MinDateFilter}"
|
||||||
|
Date="{Binding MaxDateFilter, Mode=TwoWay}"
|
||||||
|
MaxDate="{Binding MaxDate}"/>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterDurationTextBlock"
|
||||||
|
Grid.Row="4"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Grid Grid.Row="4"
|
||||||
|
Grid.Column="1"
|
||||||
|
RowSpacing="5">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<c:TimeSpanControl Grid.Row="0"
|
||||||
|
Minimum="{Binding MinDuration}"
|
||||||
|
Value="{Binding MinDurationFilter, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding MaxDurationFilter}"/>
|
||||||
|
<c:TimeSpanControl Grid.Row="1"
|
||||||
|
Minimum="{Binding MinDurationFilter}"
|
||||||
|
Value="{Binding MaxDurationFilter, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding MaxDuration}"/>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterRemovedTextBlock"
|
||||||
|
Grid.Row="5"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Grid Grid.Row="5"
|
||||||
|
Grid.Column="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding RemovedCount}"/>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilterRemovedButton"
|
||||||
|
Grid.Column="1"
|
||||||
|
Command="{Binding RestoreRemovedVideosCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TeachingTip>
|
||||||
|
</AppBarToggleButton.Resources>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
<AppBarButton x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/ApplyDirectoryButton"
|
||||||
|
Icon="Folder"
|
||||||
|
Width="40"
|
||||||
|
Height="48"
|
||||||
|
Command="{Binding SelectDirectoryCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<ItemsControl ItemsSource="{Binding Videos}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Expander HorizontalAlignment="Stretch"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
CornerRadius="10"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
Visibility="{Binding Value, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<Expander.Header>
|
||||||
|
<Grid Padding="-16,0,-16,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Source="{Binding Key.ThumbnailUrl, TargetNullValue={StaticResource ImageOtherThumbnail}}"
|
||||||
|
Height="100"/>
|
||||||
|
<Grid Grid.Column="1"
|
||||||
|
Margin="10"
|
||||||
|
RowSpacing="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
FontSize="16"
|
||||||
|
Text="{Binding Key.Title}"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<Grid Grid.Row="1"
|
||||||
|
RowSpacing="10"
|
||||||
|
ColumnSpacing="10">
|
||||||
|
<Grid.Resources>
|
||||||
|
<x:Double x:Key="IconSize">17</x:Double>
|
||||||
|
<x:Double x:Key="FontSize">13</x:Double>
|
||||||
|
</Grid.Resources>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Grid.Row="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{ThemeResource ImageHomeVideoCollectionViewAuthor}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="0"
|
||||||
|
FontSize="{StaticResource FontSize}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Key.Author}"/>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Grid.Row="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{ThemeResource ImageHomeVideoCollectionViewDate}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
FontSize="{StaticResource FontSize}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Key.PublishDate}"/>
|
||||||
|
<Image Grid.Column="2"
|
||||||
|
Grid.Row="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{ThemeResource ImageHomeVideoCollectionViewTime}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="3"
|
||||||
|
Grid.Row="0"
|
||||||
|
FontSize="{StaticResource FontSize}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Key.Duration}"/>
|
||||||
|
<Image Grid.Column="2"
|
||||||
|
Grid.Row="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Source="{ThemeResource ImageHomeVideoCollectionViewViews}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="3"
|
||||||
|
Grid.Row="1"
|
||||||
|
FontSize="{StaticResource FontSize}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Key.Views}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<AppBarButton Grid.Column="2"
|
||||||
|
Width="40"
|
||||||
|
Height="48"
|
||||||
|
Icon="Delete"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Command="{Binding ElementName=Root, Path=DataContext.RemoveVideoCommand}"
|
||||||
|
CommandParameter="{Binding Key}"/>
|
||||||
|
</Grid>
|
||||||
|
</Expander.Header>
|
||||||
|
<Expander.Content>
|
||||||
|
<StackPanel Spacing="20">
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/MediaOptionsHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/QualitySettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoCollectionViewMedia}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ComboBox ItemsSource="{Binding Key.Streams}"
|
||||||
|
SelectedItem="{Binding Key.SelectedStream, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/MediaTypeSettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoCollectionViewMedia}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:MediaType}"
|
||||||
|
SelectedItem="{Binding Key.MediaType, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="Original">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOriginal"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyVideo">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyVideo"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyAudio">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyAudio"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/TrimSettingsGroup">
|
||||||
|
<ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoCollectionViewTrim}"/>
|
||||||
|
</ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<ctc:SettingsExpander.Items>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/TrimStartSettingsCard">
|
||||||
|
<c:TimeSpanControl Value="{Binding Key.TrimStart, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding Key.TrimEnd, Mode=OneWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/TrimEndSettingsCard">
|
||||||
|
<c:TimeSpanControl Minimum="{Binding Key.TrimStart, Mode=OneWay}"
|
||||||
|
Value="{Binding Key.TrimEnd, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding Key.Duration, Mode=OneWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</ctc:SettingsExpander.Items>
|
||||||
|
</ctc:SettingsExpander>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FileOptionsHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/DirectorySettingsCard"
|
||||||
|
Description="{Binding Key.DirectoryPath}">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoCollectionViewDirectory}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/DirectorySettingsCardButton"
|
||||||
|
Command="{Binding Key.BrowseCommand}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FilenameSettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoCollectionViewFilename}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<TextBox Text="{Binding Key.Filename, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/FileTypeSettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoCollectionViewExtension}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Key.MediaType, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="OnlyAudio">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Content">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:AudioExtension}"
|
||||||
|
SelectedItem="{Binding Key.AudioExtension, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Key.MediaType, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="NotEqual"
|
||||||
|
Value="OnlyAudio">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Content">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:VideoExtension}"
|
||||||
|
SelectedItem="{Binding Key.VideoExtension, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Expander.Content>
|
||||||
|
</Expander>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10">
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/CreateAndStartButton"
|
||||||
|
Command="{Binding CreateTasksAndDownloadCommand}"/>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoCollectionViewResources/CreateButton"
|
||||||
|
Style="{StaticResource AccentButtonStyle}"
|
||||||
|
Command="{Binding CreateTasksCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using VDownload.Core.ViewModels.Home;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Home
|
||||||
|
{
|
||||||
|
public sealed partial class HomeVideoCollectionView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeVideoCollectionView(HomeVideoCollectionViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
220
VDownload.Core/VDownload.Core.Views/Home/HomeVideoView.xaml
Normal file
220
VDownload.Core/VDownload.Core.Views/Home/HomeVideoView.xaml
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Home.HomeVideoView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.Home"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
xmlns:m="using:VDownload.Models"
|
||||||
|
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||||
|
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:c="using:SimpleToolkit.UI.WinUI.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ViewBackgroundColor}">
|
||||||
|
|
||||||
|
<Grid Padding="15"
|
||||||
|
RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="150"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
ColumnSpacing="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||||
|
<Image Source="{Binding ThumbnailUrl, TargetNullValue={StaticResource ImageOtherThumbnail}}"
|
||||||
|
VerticalAlignment="Stretch"/>
|
||||||
|
</Border>
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Spacing="15">
|
||||||
|
<TextBlock Text="{Binding Title}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="20"
|
||||||
|
TextWrapping="WrapWholeWords"/>
|
||||||
|
<Grid ColumnSpacing="10"
|
||||||
|
RowSpacing="10">
|
||||||
|
<Grid.Resources>
|
||||||
|
<x:Double x:Key="IconSize">18</x:Double>
|
||||||
|
</Grid.Resources>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Source="{ThemeResource ImageHomeVideoViewAuthor}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="0"
|
||||||
|
Text="{Binding Author}"/>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Grid.Row="1"
|
||||||
|
Source="{ThemeResource ImageHomeVideoViewDate}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="1"
|
||||||
|
Text="{Binding PublishDate}"/>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Grid.Row="2"
|
||||||
|
Source="{ThemeResource ImageHomeVideoViewTime}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="2"
|
||||||
|
Text="{Binding Duration}"/>
|
||||||
|
<Image Grid.Column="0"
|
||||||
|
Grid.Row="3"
|
||||||
|
Source="{ThemeResource ImageHomeVideoViewViews}"
|
||||||
|
Width="{StaticResource IconSize}"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Grid.Row="3"
|
||||||
|
Text="{Binding Views}"/>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<StackPanel Spacing="20">
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaOptionsHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/QualitySettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoViewQuality}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ComboBox ItemsSource="{Binding Streams}"
|
||||||
|
SelectedItem="{Binding SelectedStream, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaTypeSettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoViewMedia}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:MediaType}"
|
||||||
|
SelectedItem="{Binding MediaType, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="Original">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOriginal"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyVideo">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyVideo"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyAudio">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyAudio"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/TrimSettingsGroup">
|
||||||
|
<ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoViewTrim}"/>
|
||||||
|
</ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<ctc:SettingsExpander.Items>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/TrimStartSettingsCard">
|
||||||
|
<c:TimeSpanControl Value="{Binding TrimStart, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding TrimEnd, Mode=OneWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/TrimEndSettingsCard">
|
||||||
|
<c:TimeSpanControl Minimum="{Binding TrimStart, Mode=OneWay}"
|
||||||
|
Value="{Binding TrimEnd, Mode=TwoWay}"
|
||||||
|
Maximum="{Binding Duration, Mode=OneWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</ctc:SettingsExpander.Items>
|
||||||
|
</ctc:SettingsExpander>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/FileOptionsHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/DirectorySettingsCard"
|
||||||
|
Description="{Binding DirectoryPath}">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoViewDirectory}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/DirectorySettingsCardButton"
|
||||||
|
Command="{Binding BrowseCommand}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/FilenameSettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoViewFilename}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<TextBox Text="{Binding Filename, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/FileTypeSettingsCard">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageHomeVideoViewExtension}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding MediaType, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="OnlyAudio">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Content">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:AudioExtension}"
|
||||||
|
SelectedItem="{Binding AudioExtension, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding MediaType, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="NotEqual"
|
||||||
|
Value="OnlyAudio">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Content">
|
||||||
|
<ic:ChangePropertyAction.Value>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:VideoExtension}"
|
||||||
|
SelectedItem="{Binding VideoExtension, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ic:ChangePropertyAction.Value>
|
||||||
|
</ic:ChangePropertyAction>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10">
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/CreateAndStartButton"
|
||||||
|
Command="{Binding CreateTaskAndDownloadCommand}"/>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/CreateButton"
|
||||||
|
Style="{StaticResource AccentButtonStyle}"
|
||||||
|
Command="{Binding CreateTaskCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using VDownload.Core.ViewModels.Home;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Home
|
||||||
|
{
|
||||||
|
public sealed partial class HomeVideoView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeVideoView(HomeVideoViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
156
VDownload.Core/VDownload.Core.Views/Home/HomeView.xaml
Normal file
156
VDownload.Core/VDownload.Core.Views/Home/HomeView.xaml
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Home.HomeView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="Transparent"
|
||||||
|
x:Name="Root">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:EventTriggerBehavior EventName="Loaded">
|
||||||
|
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
|
||||||
|
</ic:EventTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
<Grid RowSpacing="10"
|
||||||
|
Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Frame Content="{Binding MainContent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ViewModelToViewConverter}}">
|
||||||
|
|
||||||
|
</Frame>
|
||||||
|
<ctuc:SwitchPresenter Grid.Row="1"
|
||||||
|
Value="{Binding OptionBarError, Converter={StaticResource IsNotNullConverter}}">
|
||||||
|
<ctuc:Case Value="False">
|
||||||
|
<Grid Background="{ThemeResource OptionBarBackgroundColor}"
|
||||||
|
CornerRadius="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="50"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ctuc:UniformGrid Grid.Column="0"
|
||||||
|
Rows="1"
|
||||||
|
Margin="15,0,0,0">
|
||||||
|
<ctuc:UniformGrid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
</ctuc:UniformGrid.RowDefinitions>
|
||||||
|
<ctuc:UniformGrid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</ctuc:UniformGrid.ColumnDefinitions>
|
||||||
|
<ctuc:SwitchPresenter Grid.Row="0"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Margin="0,0,15,0"
|
||||||
|
Value="{Binding OptionBarContent, Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="None">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Visibility"
|
||||||
|
Value="Collapsed"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
ComparisonCondition="NotEqual"
|
||||||
|
Value="None">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Visibility"
|
||||||
|
Value="Visible"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
<ctuc:Case Value="VideoSearch">
|
||||||
|
<Grid ColumnSpacing="10"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarVideoSearchContentTextBox"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding OptionBarVideoSearchTBValue, Mode=TwoWay}"/>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarSearchButton"
|
||||||
|
Grid.Column="1"
|
||||||
|
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||||
|
Command="{Binding VideoSearchStartCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="PlaylistSearch">
|
||||||
|
<Grid ColumnSpacing="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarPlaylistSearchContentTextBox"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding OptionBarPlaylistSearchTBValue, Mode=TwoWay}"/>
|
||||||
|
<NumberBox x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarPlaylistSearchContentNumberBox"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
SpinButtonPlacementMode="Compact"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
Value="{Binding OptionBarPlaylistSearchNBValue, Mode=TwoWay}"
|
||||||
|
Minimum="0"/>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarSearchButton"
|
||||||
|
Grid.Column="2"
|
||||||
|
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||||
|
Command="{Binding PlaylistSearchStartCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
<StackPanel VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<ProgressRing Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
Visibility="{Binding OptionBarLoading, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||||
|
<TextBlock Text="{Binding OptionBarMessage}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</ctuc:UniformGrid>
|
||||||
|
<StackPanel Grid.Column="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Margin="0,0,4,0">
|
||||||
|
<AppBarToggleButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarLoadSubscription"
|
||||||
|
Icon="Favorite"
|
||||||
|
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||||
|
IsChecked="{Binding OptionBarLoadSubscriptionButtonChecked, Mode=TwoWay}"
|
||||||
|
Command="{Binding LoadFromSubscriptionCommand}"/>
|
||||||
|
<AppBarToggleButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarVideoSearch"
|
||||||
|
Icon="Video"
|
||||||
|
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||||
|
IsChecked="{Binding OptionBarVideoSearchButtonChecked, Mode=TwoWay}"
|
||||||
|
Command="{Binding VideoSearchShowCommand}"/>
|
||||||
|
<AppBarToggleButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarPlaylistSearch"
|
||||||
|
Icon="List"
|
||||||
|
IsEnabled="{Binding OptionBarSearchNotPending}"
|
||||||
|
IsChecked="{Binding OptionBarPlaylistSearchButtonChecked, Mode=TwoWay}"
|
||||||
|
Command="{Binding PlaylistSearchShowCommand}"/>
|
||||||
|
<AppBarSeparator/>
|
||||||
|
<AppBarButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarCancelAll"
|
||||||
|
Icon="Cancel"
|
||||||
|
Command="{Binding CancelCommand}"/>
|
||||||
|
<AppBarButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarDownloadAll"
|
||||||
|
Icon="Download"
|
||||||
|
Command="{Binding DownloadCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="True">
|
||||||
|
<InfoBar x:Uid="/VDownload.Core.Strings/HomeViewResources/ErrorInfoBar"
|
||||||
|
Severity="Error"
|
||||||
|
IsOpen="{Binding OptionBarIsErrorOpened, Mode=TwoWay}"
|
||||||
|
Message="{Binding OptionBarError}"
|
||||||
|
CloseButtonCommand="{Binding CloseErrorCommand}"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
18
VDownload.Core/VDownload.Core.Views/Home/HomeView.xaml.cs
Normal file
18
VDownload.Core/VDownload.Core.Views/Home/HomeView.xaml.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using VDownload.Core.ViewModels.Home;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Home
|
||||||
|
{
|
||||||
|
public sealed partial class HomeView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public HomeView(HomeViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
312
VDownload.Core/VDownload.Core.Views/Settings/SettingsView.xaml
Normal file
312
VDownload.Core/VDownload.Core.Views/Settings/SettingsView.xaml
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Settings.SettingsView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.Settings"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
xmlns:m="using:VDownload.Models"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ViewBackgroundColor}">
|
||||||
|
|
||||||
|
<Grid Padding="20"
|
||||||
|
RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/Header"
|
||||||
|
Grid.Row="0"
|
||||||
|
FontSize="28"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<StackPanel Spacing="20">
|
||||||
|
<!-- Searching -->
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/SearchingHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/SearchingPlaylistCount">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewSearchingPlaylistCount}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<NumberBox Value="{Binding SearchingPlaylistCount, Mode=TwoWay}"
|
||||||
|
Minimum="0"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
SpinButtonPlacementMode="Compact"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Tasks -->
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksRunningTasks">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTasksRunningTasks}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<NumberBox Value="{Binding TasksRunningTasks, Mode=TwoWay}"
|
||||||
|
Minimum="1"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
SpinButtonPlacementMode="Compact"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksDefaultMediaOptions">
|
||||||
|
<ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTasksDefaultMediaOptions}"/>
|
||||||
|
</ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<ctc:SettingsExpander.Items>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksMediaType">
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:MediaType}"
|
||||||
|
SelectedItem="{Binding TasksMediaType, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="Original">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOriginal"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyVideo">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyVideo"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="OnlyAudio">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/MediaTypeOnlyAudio"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksVideoExtension">
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:VideoExtension}"
|
||||||
|
SelectedItem="{Binding TasksVideoExtension, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksAudioExtension">
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:AudioExtension}"
|
||||||
|
SelectedItem="{Binding TasksAudioExtension, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</ctc:SettingsExpander.Items>
|
||||||
|
</ctc:SettingsExpander>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksFilenameTemplate">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTasksFilenameTemplate}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<TextBox Text="{Binding TasksFilenameTemplate, Mode=TwoWay}"
|
||||||
|
ToolTipService.ToolTip="{Binding TasksFilenameTemplateTooltip}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksMeteredConnectionWarning">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTasksMeteredConnectionWarning}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding TasksMeteredConnectionWarning, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksSaveLastOutputDirectory">
|
||||||
|
<ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTasksOutputDirectory}"/>
|
||||||
|
</ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding TasksSaveLastOutputDirectory, Mode=TwoWay}"/>
|
||||||
|
<ctc:SettingsExpander.Items>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksDefaultOutputDirectory"
|
||||||
|
IsEnabled="{Binding TasksSaveLastOutputDirectory, Converter={StaticResource BoolNegationConverter}}"
|
||||||
|
Description="{Binding TasksDefaultOutputDirectory}">
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksDefaultOutputDirectoryButton"
|
||||||
|
Command="{Binding BrowseTasksDefaultOutputDirectoryCommand}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</ctc:SettingsExpander.Items>
|
||||||
|
</ctc:SettingsExpander>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TasksReplaceOutputFile">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTasksReplaceOutputFile}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding TasksReplaceOutputFile, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Processing -->
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/ProcessingHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/ProcessingFFmpegLocation"
|
||||||
|
Description="{Binding ProcessingFFmpegLocation}">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewProcessingFFmpegLocation}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/SettingsViewResources/ProcessingFFmpegLocationButton"
|
||||||
|
Command="{Binding BrowseProcessingFFmpegLocationCommand}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/ProcessingUseMultithreading">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewProcessingUseMultithreading}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding ProcessingUseMultithreading, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/ProcessingUseHardwareAcceleration">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewProcessingUseHardwareAcceleration}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding ProcessingUseHardwareAcceleration, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/ProcessingSpeed">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewProcessingSpeed}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ComboBox ItemsSource="{ct:EnumValues Type=m:ProcessingSpeed}"
|
||||||
|
SelectedItem="{Binding ProcessingSpeed, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ctuc:SwitchPresenter Value="{Binding Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="VerySlow">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedVerySlow"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="Slower">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedSlower"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="Slow">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedSlow"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="Medium">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedMedium"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="Fast">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedFast"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="Faster">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedFaster"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="VeryFast">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedVeryFast"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="SuperFast">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedSuperFast"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="UltraFast">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/CommonResources/ProcessingSpeedUltraFast"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/NotificationsHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/NotificationsOnSuccessful">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewNotificationOnSuccessful}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding NotificationsOnSuccessful, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/NotificationsOnUnsuccessful">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewNotificationOnUnsuccessful}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding NotificationsOnUnsuccessful, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Temporary files -->
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/TempHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TempDirectory"
|
||||||
|
Description="{Binding TempDirectory}">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTempDirectory}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/SettingsViewResources/TempDirectoryButton"
|
||||||
|
Command="{Binding BrowseTempDirectoryCommand}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TempDeleteOnFail">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{ThemeResource ImageSettingsViewTempDeleteOnFail}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding TempDeleteOnFail, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Twitch -->
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SettingsViewResources/TwitchHeader"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="15"/>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TwitchVodPassiveTrimming">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding TwitchVodPassiveTrimming, Mode=TwoWay}"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TwitchVodParallelDownloads">
|
||||||
|
<ctc:SettingsCard.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||||
|
</ctc:SettingsCard.HeaderIcon>
|
||||||
|
<NumberBox Value="{Binding TwitchVodParallelDownloads, Mode=TwoWay}"
|
||||||
|
Minimum="1"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
SpinButtonPlacementMode="Compact"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/SettingsViewResources/TwitchVodChunkDownloadingErrorRetry">
|
||||||
|
<ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<BitmapIcon ShowAsMonochrome="False"
|
||||||
|
UriSource="{StaticResource ImageSourcesTwitch}"/>
|
||||||
|
</ctc:SettingsExpander.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{Binding TwitchVodChunkDownloadingErrorRetry, Mode=TwoWay}"/>
|
||||||
|
<ctc:SettingsExpander.Items>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TwitchVodChunkDownloadingErrorRetryCount"
|
||||||
|
IsEnabled="{Binding TwitchVodChunkDownloadingErrorRetry}">
|
||||||
|
<NumberBox Value="{Binding TwitchVodChunkDownloadingErrorRetryCount, Mode=TwoWay}"
|
||||||
|
Minimum="1"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
SpinButtonPlacementMode="Compact"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/SettingsViewResources/TwitchVodChunkDownloadingErrorRetryDelay"
|
||||||
|
IsEnabled="{Binding TwitchVodChunkDownloadingErrorRetry}">
|
||||||
|
<NumberBox Value="{Binding TwitchVodChunkDownloadingErrorRetryDelay, Mode=TwoWay}"
|
||||||
|
Minimum="0"
|
||||||
|
SmallChange="1"
|
||||||
|
LargeChange="10"
|
||||||
|
SpinButtonPlacementMode="Compact"/>
|
||||||
|
</ctc:SettingsCard>
|
||||||
|
</ctc:SettingsExpander.Items>
|
||||||
|
</ctc:SettingsExpander>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/SettingsViewResources/RestoreToDefaultButton"
|
||||||
|
Command="{Binding RestoreToDefaultCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using VDownload.Core.ViewModels.Settings;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Settings
|
||||||
|
{
|
||||||
|
public sealed partial class SettingsView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public SettingsView(SettingsViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="VDownload.Core.Views.Subscriptions.SubscriptionsView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Core.Views.Subscriptions"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
|
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ct="using:CommunityToolkit.WinUI"
|
||||||
|
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||||
|
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ViewBackgroundColor}"
|
||||||
|
x:Name="Root">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:EventTriggerBehavior EventName="Loaded">
|
||||||
|
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
|
||||||
|
</ic:EventTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
|
||||||
|
<Grid Padding="20"
|
||||||
|
RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock x:Uid="/VDownload.Core.Strings/SubscriptionsViewResources/Header"
|
||||||
|
Grid.Row="0"
|
||||||
|
FontSize="28"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<ItemsControl ItemsSource="{Binding Playlists}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<ctuc:StaggeredPanel ColumnSpacing="10"
|
||||||
|
DesiredColumnWidth="120"
|
||||||
|
RowSpacing="10"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid RowSpacing="10"
|
||||||
|
Padding="10"
|
||||||
|
Background="{ThemeResource PanelBackgroundColor}"
|
||||||
|
CornerRadius="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Image Grid.Row="0">
|
||||||
|
<i:Interaction.Behaviors>
|
||||||
|
<ic:DataTriggerBehavior Binding="{Binding Source, Converter={StaticResource ObjectToStringConverter}}"
|
||||||
|
Value="Twitch">
|
||||||
|
<ic:ChangePropertyAction PropertyName="Source"
|
||||||
|
Value="{StaticResource ImageSourcesTwitch}"/>
|
||||||
|
</ic:DataTriggerBehavior>
|
||||||
|
</i:Interaction.Behaviors>
|
||||||
|
</Image>
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Text="{Binding Name}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
<HyperlinkButton x:Uid="/VDownload.Core.Strings/SubscriptionsViewResources/RemovePlaylistButton"
|
||||||
|
Grid.Row="2"
|
||||||
|
Content="Remove"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Command="{Binding DataContext.RemovePlaylistCommand, ElementName=Root}"
|
||||||
|
CommandParameter="{Binding}"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
<ctuc:SwitchPresenter Grid.Row="2"
|
||||||
|
Value="{Binding Error, Converter={StaticResource IsNotNullConverter}}">
|
||||||
|
<ctuc:Case Value="False">
|
||||||
|
<Grid Background="{ThemeResource PanelBackgroundColor}"
|
||||||
|
Padding="10"
|
||||||
|
ColumnSpacing="10"
|
||||||
|
CornerRadius="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox x:Uid="/VDownload.Core.Strings/SubscriptionsViewResources/PlaylistUrlTextBox"
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="{Binding Url, Mode=TwoWay}"
|
||||||
|
IsEnabled="{Binding Loading, Converter={StaticResource BoolNegationConverter}}"/>
|
||||||
|
<ctuc:SwitchPresenter Grid.Column="1"
|
||||||
|
Value="{Binding Loading, Converter={StaticResource ObjectToStringConverter}}">
|
||||||
|
<ctuc:Case Value="True">
|
||||||
|
<ProgressRing IsIndeterminate="True"
|
||||||
|
Width="20"
|
||||||
|
Height="20"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="False">
|
||||||
|
<Button x:Uid="/VDownload.Core.Strings/SubscriptionsViewResources/PlaylistSearchButton"
|
||||||
|
Command="{Binding AddCommand}"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</Grid>
|
||||||
|
</ctuc:Case>
|
||||||
|
<ctuc:Case Value="True">
|
||||||
|
<InfoBar x:Uid="/VDownload.Core.Strings/SubscriptionsViewResources/ErrorInfoBar"
|
||||||
|
Severity="Error"
|
||||||
|
IsOpen="{Binding IsErrorOpened, Mode=TwoWay}"
|
||||||
|
Message="{Binding Error}"
|
||||||
|
CloseButtonCommand="{Binding CloseErrorCommand}"/>
|
||||||
|
</ctuc:Case>
|
||||||
|
</ctuc:SwitchPresenter>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using VDownload.Core.ViewModels.About;
|
||||||
|
using VDownload.Core.ViewModels.Subscriptions;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Views.Subscriptions
|
||||||
|
{
|
||||||
|
public sealed partial class SubscriptionsView : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public SubscriptionsView(SubscriptionsViewModel viewModel)
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
this.DataContext = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>VDownload.Core.Views</RootNamespace>
|
||||||
|
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<UseRidGraph>true</UseRidGraph>
|
||||||
|
<EnableCoreMrtTooling Condition=" '$(BuildingInsideVisualStudio)' != 'true' ">false</EnableCoreMrtTooling>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="About\AboutView.xaml" />
|
||||||
|
<None Remove="Home\HomeVideoCollectionView.xaml" />
|
||||||
|
<None Remove="Subscriptions\SubscriptionsView.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI" Version="7.1.2" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.0.240109" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.0.240109" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240227000" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
|
||||||
|
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||||
|
<PackageReference Include="SimpleToolkit.UI.WinUI.Behaviors" Version="1.7.4" />
|
||||||
|
<PackageReference Include="SimpleToolkit.UI.WinUI.Controls" Version="1.7.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\VDownload.Core.Strings\VDownload.Core.Strings.csproj" />
|
||||||
|
<ProjectReference Include="..\VDownload.Core.ViewModels\VDownload.Core.ViewModels.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="About\AboutView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<None Update="Authentication\AuthenticationView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</None>
|
||||||
|
<None Update="BaseWindow.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</None>
|
||||||
|
<None Update="Home\HomeDownloadsView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</None>
|
||||||
|
<Page Update="Subscriptions\SubscriptionsView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Update="Home\HomeVideoCollectionView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<None Update="Home\HomeVideoView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</None>
|
||||||
|
<None Update="Settings\SettingsView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Home\HomeVideoView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="BaseWindow.xaml">
|
||||||
|
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Home\HomeView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Home\HomeDownloadsView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Authentication\AuthenticationView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Settings\SettingsView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user