Added Storage Proxy (Hopefully dont break software)
This commit is contained in:
parent
1d1accb4d0
commit
0a67162acb
|
@ -35,7 +35,7 @@ namespace Tesses.YouTubeDownloader.ExtensionLoader
|
||||||
List<IExtension> extensions = new List<IExtension>();
|
List<IExtension> extensions = new List<IExtension>();
|
||||||
public List<IExtension> Extensions => extensions;
|
public List<IExtension> Extensions => extensions;
|
||||||
|
|
||||||
TYTDStorage Storage;
|
IStorage Storage;
|
||||||
MountableServer Server;
|
MountableServer Server;
|
||||||
string dir;
|
string dir;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -43,7 +43,7 @@ namespace Tesses.YouTubeDownloader.ExtensionLoader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="storage">Storage for TYTD</param>
|
/// <param name="storage">Storage for TYTD</param>
|
||||||
/// <param name="lookInDir">where to look for extensions</param>
|
/// <param name="lookInDir">where to look for extensions</param>
|
||||||
public Loader(TYTDStorage storage,string lookInDir="config/apidll")
|
public Loader(IStorage storage,string lookInDir="config/apidll")
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(lookInDir);
|
Directory.CreateDirectory(lookInDir);
|
||||||
dir=lookInDir;
|
dir=lookInDir;
|
||||||
|
@ -122,7 +122,7 @@ namespace Tesses.YouTubeDownloader.ExtensionLoader
|
||||||
/// Use relative paths please
|
/// Use relative paths please
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value></value>
|
/// <value></value>
|
||||||
public TYTDStorage Storage {get; internal set;}
|
public IStorage Storage {get; internal set;}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get extension storage dir use "Storage" to actually access files
|
/// Get extension storage dir use "Storage" to actually access files
|
||||||
/// The path config/apistore/{Name} is created if it doesnt exist
|
/// The path config/apistore/{Name} is created if it doesnt exist
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader.ExtensionLoader</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.ExtensionLoader</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.0.0.0</Version>
|
<Version>1.1.0</Version>
|
||||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||||
<FileVersion>1.0.0.0</FileVersion>
|
<FileVersion>1.1.0</FileVersion>
|
||||||
<Description>Load Extensions into TYTD (Not Tested)</Description>
|
<Description>Load Extensions into TYTD (Not Tested)</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 WebDevSimplified
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,345 @@
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1000px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-inline: auto;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.video-info {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
.video-info .video-title
|
||||||
|
{
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.video-info .video-stats
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #5a5a5a;
|
||||||
|
}
|
||||||
|
.channel-info
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.channel-info img
|
||||||
|
{
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.channel-info div
|
||||||
|
{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.channel-info div p
|
||||||
|
{
|
||||||
|
color: #000;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.channel-info .subscribe
|
||||||
|
{
|
||||||
|
background: red;
|
||||||
|
color: #FFF;
|
||||||
|
padding: 8px 30px;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.channel-info .subscribed
|
||||||
|
{
|
||||||
|
padding: 8px 30px;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background:rgb(230, 230, 230);
|
||||||
|
color: #5a5a5a;
|
||||||
|
}
|
||||||
|
.channel-info .bell
|
||||||
|
{
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.video-actions span,
|
||||||
|
.video-actions button {
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
font-size: 1.1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 30;
|
||||||
|
height: 30;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.theater,
|
||||||
|
.video-container.full-screen {
|
||||||
|
max-width: initial;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.theater {
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.full-screen {
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
color: white;
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls-container::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, .75), transparent);
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 6 / 1;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container:hover .video-controls-container,
|
||||||
|
.video-container:focus-within .video-controls-container,
|
||||||
|
.video-container.paused .video-controls-container {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls-container .controls {
|
||||||
|
display: flex;
|
||||||
|
gap: .5rem;
|
||||||
|
padding: .25rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls-container .controls button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: .85;
|
||||||
|
transition: opacity 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls-container .controls button:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.paused .pause-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container:not(.paused) .play-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.theater .tall {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container:not(.theater) .wide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.full-screen .open {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container:not(.full-screen) .close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-high-icon,
|
||||||
|
.volume-low-icon,
|
||||||
|
.volume-muted-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container[data-volume-level="high"] .volume-high-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container[data-volume-level="low"] .volume-low-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container[data-volume-level="muted"] .volume-muted-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.volume-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider {
|
||||||
|
width: 0;
|
||||||
|
transform-origin: left;
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: width 150ms ease-in-out, transform 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-container:hover .volume-slider,
|
||||||
|
.volume-slider:focus-within {
|
||||||
|
width: 100px;
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .25rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.captions .captions-btn {
|
||||||
|
border-bottom: 3px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls-container .controls button.wide-btn {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-container {
|
||||||
|
height: 7px;
|
||||||
|
margin-inline: .5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
background-color: rgba(100, 100, 100, .5);
|
||||||
|
height: 3px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: calc(100% - var(--preview-position) * 100%);
|
||||||
|
background-color: rgb(150, 150, 150);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: calc(100% - var(--progress-position) * 100%);
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline .thumb-indicator {
|
||||||
|
--scale: 0;
|
||||||
|
position: absolute;
|
||||||
|
transform: translateX(-50%) scale(var(--scale));
|
||||||
|
height: 200%;
|
||||||
|
top: -50%;
|
||||||
|
left: calc(var(--progress-position) * 100%);
|
||||||
|
background-color: red;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: transform 150ms ease-in-out;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline .preview-img {
|
||||||
|
position: absolute;
|
||||||
|
height: 80px;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
top: -1rem;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
left: calc(var(--preview-position) * 100%);
|
||||||
|
border-radius: .25rem;
|
||||||
|
border: 2px solid white;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.scrubbing .thumbnail-img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.scrubbing .preview-img,
|
||||||
|
.timeline-container:hover .preview-img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.scrubbing .timeline::before,
|
||||||
|
.timeline-container:hover .timeline::before {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.scrubbing .thumb-indicator,
|
||||||
|
.timeline-container:hover .thumb-indicator {
|
||||||
|
--scale: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.scrubbing .timeline,
|
||||||
|
.timeline-container:hover .timeline {
|
||||||
|
height: 100%;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,20 @@
|
||||||
|
function num2unit(sz)
|
||||||
|
{
|
||||||
|
if(sz < 1024)
|
||||||
|
{
|
||||||
|
return `${sz}`;
|
||||||
|
}
|
||||||
|
if(sz < (1024*1024))
|
||||||
|
{
|
||||||
|
return `${(sz / 1024).toFixed()}K`
|
||||||
|
}
|
||||||
|
if(sz < (1024*1024*1024))
|
||||||
|
{
|
||||||
|
return `${(sz / (1024 * 1024)).toFixed()}M`
|
||||||
|
}
|
||||||
|
if(sz < (1024*1024*1024*1024))
|
||||||
|
{
|
||||||
|
return `${(sz / (1024 * 1024 * 1024)).toFixed()}G`
|
||||||
|
}
|
||||||
|
return `${(sz / (1024 * 1024 * 1024 * 1024)).toFixed()}T`
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
const playPauseBtn = document.querySelector(".play-pause-btn")
|
||||||
|
const theaterBtn = document.querySelector(".theater-btn")
|
||||||
|
const fullScreenBtn = document.querySelector(".full-screen-btn")
|
||||||
|
const miniPlayerBtn = document.querySelector(".mini-player-btn")
|
||||||
|
const muteBtn = document.querySelector(".mute-btn")
|
||||||
|
const captionsBtn = document.querySelector(".captions-btn")
|
||||||
|
const speedBtn = document.querySelector(".speed-btn")
|
||||||
|
const currentTimeElem = document.querySelector(".current-time")
|
||||||
|
const totalTimeElem = document.querySelector(".total-time")
|
||||||
|
const thumbnailImg = document.querySelector(".thumbnail-img")
|
||||||
|
const volumeSlider = document.querySelector(".volume-slider")
|
||||||
|
const videoContainer = document.querySelector(".video-container")
|
||||||
|
const timelineContainer = document.querySelector(".timeline-container")
|
||||||
|
const video = document.querySelector("video")
|
||||||
|
const mux=document.getElementById("Mux");
|
||||||
|
const premux=document.getElementById("PreMuxed");
|
||||||
|
const audio=document.getElementById("Audio");
|
||||||
|
const resbtn =document.querySelector(".res-btn");
|
||||||
|
const dlbtn =document.querySelector(".download-btn");
|
||||||
|
|
||||||
|
const videoTitle = document.getElementById("video-title");
|
||||||
|
const videoViews = document.getElementById("video-views");
|
||||||
|
const videoLikes = document.getElementById("video-likes");
|
||||||
|
const videoDislikes = document.getElementById("video-dislikes");
|
||||||
|
const videoUploadDate = document.getElementById("video-date");
|
||||||
|
const videoChannelIcon = document.getElementById("video-channel-icon");
|
||||||
|
const videoChannelTitle = document.getElementById("video-channel-title");
|
||||||
|
const videoDescription = document.getElementById("video-description");
|
||||||
|
var res=1;
|
||||||
|
var id=getid();
|
||||||
|
|
||||||
|
function getfilename(id,res)
|
||||||
|
{
|
||||||
|
return `../api/Storage/VideoRes/${res}/${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function reviver(value) {
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return new Date(value.substring(0,value.indexOf("T")));
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
function getid()
|
||||||
|
{
|
||||||
|
const params = new Proxy(new URLSearchParams(window.location.search), {
|
||||||
|
get: (searchParams, prop) => searchParams.get(prop),
|
||||||
|
});
|
||||||
|
|
||||||
|
mux.href=getfilename(params.v,0);
|
||||||
|
premux.href=getfilename(params.v,1);
|
||||||
|
audio.href=getfilename(params.v,2);
|
||||||
|
video.src=getfilename(params.v,1);
|
||||||
|
var tytd=new TYTD(window.location,1);
|
||||||
|
tytd.getvideoinfo(params.v,(e)=>{
|
||||||
|
videoTitle.innerText = e.Title;
|
||||||
|
videoViews.innerText = num2unit(e.Views);
|
||||||
|
if(e.Likes>0)
|
||||||
|
{
|
||||||
|
videoLikes.innerText = num2unit(e.Likes);
|
||||||
|
}
|
||||||
|
if(e.Dislikes>0)
|
||||||
|
{
|
||||||
|
videoDislikes.innerText=num2unit(e.Dislikes);
|
||||||
|
}
|
||||||
|
videoChannelTitle.innerText=e.AuthorTitle;
|
||||||
|
videoDescription.innerText = e.Description;
|
||||||
|
videoChannelIcon.src=`../api/Storage/File/Thumbnails/${e.AuthorChannelId}/900x900.jpg`
|
||||||
|
var date=reviver(e.UploadDate);
|
||||||
|
|
||||||
|
let formattedDate = new Intl.DateTimeFormat("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "2-digit",
|
||||||
|
}).format(date);
|
||||||
|
videoUploadDate.innerText=formattedDate;
|
||||||
|
})
|
||||||
|
return params.v;
|
||||||
|
|
||||||
|
}
|
||||||
|
resbtn.addEventListener("click",()=>{
|
||||||
|
res++;
|
||||||
|
if(res >2)
|
||||||
|
{
|
||||||
|
res=0;
|
||||||
|
}
|
||||||
|
switch(res)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
resbtn.innerText="Mux";
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
resbtn.innerText="PreMuxed";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
resbtn.innerText="Audio";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
video.src=getfilename(id,res);
|
||||||
|
|
||||||
|
})
|
||||||
|
dlbtn.addEventListener("click",()=>{
|
||||||
|
switch(res)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
mux.click();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
premux.click();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
audio.click();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.addEventListener("keydown", e => {
|
||||||
|
const tagName = document.activeElement.tagName.toLowerCase()
|
||||||
|
|
||||||
|
if (tagName === "input") return
|
||||||
|
|
||||||
|
switch (e.key.toLowerCase()) {
|
||||||
|
case " ":
|
||||||
|
if (tagName === "button") return
|
||||||
|
case "k":
|
||||||
|
togglePlay()
|
||||||
|
break
|
||||||
|
case "f":
|
||||||
|
toggleFullScreenMode()
|
||||||
|
break
|
||||||
|
case "t":
|
||||||
|
toggleTheaterMode()
|
||||||
|
break
|
||||||
|
case "i":
|
||||||
|
toggleMiniPlayerMode()
|
||||||
|
break
|
||||||
|
case "m":
|
||||||
|
toggleMute()
|
||||||
|
break
|
||||||
|
case "arrowleft":
|
||||||
|
case "j":
|
||||||
|
skip(-5)
|
||||||
|
break
|
||||||
|
case "arrowright":
|
||||||
|
case "l":
|
||||||
|
skip(5)
|
||||||
|
break
|
||||||
|
case "c":
|
||||||
|
toggleCaptions()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Timeline
|
||||||
|
timelineContainer.addEventListener("mousemove", handleTimelineUpdate)
|
||||||
|
timelineContainer.addEventListener("mousedown", toggleScrubbing)
|
||||||
|
document.addEventListener("mouseup", e => {
|
||||||
|
if (isScrubbing) toggleScrubbing(e)
|
||||||
|
})
|
||||||
|
document.addEventListener("mousemove", e => {
|
||||||
|
if (isScrubbing) handleTimelineUpdate(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
let isScrubbing = false
|
||||||
|
let wasPaused
|
||||||
|
function toggleScrubbing(e) {
|
||||||
|
const rect = timelineContainer.getBoundingClientRect()
|
||||||
|
const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width
|
||||||
|
isScrubbing = (e.buttons & 1) === 1
|
||||||
|
videoContainer.classList.toggle("scrubbing", isScrubbing)
|
||||||
|
if (isScrubbing) {
|
||||||
|
wasPaused = video.paused
|
||||||
|
video.pause()
|
||||||
|
} else {
|
||||||
|
video.currentTime = percent * video.duration
|
||||||
|
if (!wasPaused) video.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTimelineUpdate(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTimelineUpdate(e) {
|
||||||
|
const rect = timelineContainer.getBoundingClientRect()
|
||||||
|
const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width
|
||||||
|
const previewImgNumber = Math.max(
|
||||||
|
1,
|
||||||
|
Math.floor((percent * video.duration) / 10)
|
||||||
|
)
|
||||||
|
|
||||||
|
timelineContainer.style.setProperty("--preview-position", percent)
|
||||||
|
|
||||||
|
if (isScrubbing) {
|
||||||
|
e.preventDefault()
|
||||||
|
thumbnailImg.src = previewImgSrc
|
||||||
|
timelineContainer.style.setProperty("--progress-position", percent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playback Speed
|
||||||
|
speedBtn.addEventListener("click", changePlaybackSpeed)
|
||||||
|
|
||||||
|
function changePlaybackSpeed() {
|
||||||
|
let newPlaybackRate = video.playbackRate + 0.25
|
||||||
|
if (newPlaybackRate > 2) newPlaybackRate = 0.25
|
||||||
|
video.playbackRate = newPlaybackRate
|
||||||
|
speedBtn.textContent = `${newPlaybackRate}x`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captions
|
||||||
|
|
||||||
|
|
||||||
|
// Duration
|
||||||
|
video.addEventListener("loadeddata", () => {
|
||||||
|
totalTimeElem.textContent = formatDuration(video.duration)
|
||||||
|
})
|
||||||
|
|
||||||
|
video.addEventListener("timeupdate", () => {
|
||||||
|
currentTimeElem.textContent = formatDuration(video.currentTime)
|
||||||
|
const percent = video.currentTime / video.duration
|
||||||
|
timelineContainer.style.setProperty("--progress-position", percent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const leadingZeroFormatter = new Intl.NumberFormat(undefined, {
|
||||||
|
minimumIntegerDigits: 2,
|
||||||
|
})
|
||||||
|
function formatDuration(time) {
|
||||||
|
const seconds = Math.floor(time % 60)
|
||||||
|
const minutes = Math.floor(time / 60) % 60
|
||||||
|
const hours = Math.floor(time / 3600)
|
||||||
|
if (hours === 0) {
|
||||||
|
return `${minutes}:${leadingZeroFormatter.format(seconds)}`
|
||||||
|
} else {
|
||||||
|
return `${hours}:${leadingZeroFormatter.format(
|
||||||
|
minutes
|
||||||
|
)}:${leadingZeroFormatter.format(seconds)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function skip(duration) {
|
||||||
|
video.currentTime += duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
muteBtn.addEventListener("click", toggleMute)
|
||||||
|
volumeSlider.addEventListener("input", e => {
|
||||||
|
video.volume = e.target.value
|
||||||
|
video.muted = e.target.value === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleMute() {
|
||||||
|
video.muted = !video.muted
|
||||||
|
}
|
||||||
|
|
||||||
|
video.addEventListener("volumechange", () => {
|
||||||
|
volumeSlider.value = video.volume
|
||||||
|
let volumeLevel
|
||||||
|
if (video.muted || video.volume === 0) {
|
||||||
|
volumeSlider.value = 0
|
||||||
|
volumeLevel = "muted"
|
||||||
|
} else if (video.volume >= 0.5) {
|
||||||
|
volumeLevel = "high"
|
||||||
|
} else {
|
||||||
|
volumeLevel = "low"
|
||||||
|
}
|
||||||
|
|
||||||
|
videoContainer.dataset.volumeLevel = volumeLevel
|
||||||
|
})
|
||||||
|
|
||||||
|
// View Modes
|
||||||
|
theaterBtn.addEventListener("click", toggleTheaterMode)
|
||||||
|
fullScreenBtn.addEventListener("click", toggleFullScreenMode)
|
||||||
|
miniPlayerBtn.addEventListener("click", toggleMiniPlayerMode)
|
||||||
|
|
||||||
|
function toggleTheaterMode() {
|
||||||
|
videoContainer.classList.toggle("theater")
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFullScreenMode() {
|
||||||
|
if (document.fullscreenElement == null) {
|
||||||
|
videoContainer.requestFullscreen()
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMiniPlayerMode() {
|
||||||
|
if (videoContainer.classList.contains("mini-player")) {
|
||||||
|
document.exitPictureInPicture()
|
||||||
|
} else {
|
||||||
|
video.requestPictureInPicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("fullscreenchange", () => {
|
||||||
|
videoContainer.classList.toggle("full-screen", document.fullscreenElement)
|
||||||
|
})
|
||||||
|
|
||||||
|
video.addEventListener("enterpictureinpicture", () => {
|
||||||
|
videoContainer.classList.add("mini-player")
|
||||||
|
})
|
||||||
|
|
||||||
|
video.addEventListener("leavepictureinpicture", () => {
|
||||||
|
videoContainer.classList.remove("mini-player")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Play/Pause
|
||||||
|
playPauseBtn.addEventListener("click", togglePlay)
|
||||||
|
video.addEventListener("click", togglePlay)
|
||||||
|
|
||||||
|
function togglePlay() {
|
||||||
|
video.paused ? video.play() : video.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
video.addEventListener("play", () => {
|
||||||
|
videoContainer.classList.remove("paused")
|
||||||
|
})
|
||||||
|
|
||||||
|
video.addEventListener("pause", () => {
|
||||||
|
videoContainer.classList.add("paused")
|
||||||
|
})
|
|
@ -0,0 +1,214 @@
|
||||||
|
class TYTD
|
||||||
|
{
|
||||||
|
constructor(server,defaultres)
|
||||||
|
{
|
||||||
|
this.server=server;
|
||||||
|
this.defaultres=defaultres;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultRes()
|
||||||
|
{
|
||||||
|
return this.defaultres;
|
||||||
|
}
|
||||||
|
set defaultRes(value)
|
||||||
|
{
|
||||||
|
this.defaultres=value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get serverUrl()
|
||||||
|
{
|
||||||
|
return this.server;
|
||||||
|
}
|
||||||
|
request(url,call)
|
||||||
|
{
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
// Typical action to be performed when the document is ready:
|
||||||
|
call(xhttp.responseText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", url, true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
downloadItem(url,res)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/AddItemRes/${res}/${url}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){});
|
||||||
|
}
|
||||||
|
downloadItem(url)
|
||||||
|
{
|
||||||
|
this.downloadItem(url,this.defaultres);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress(p)
|
||||||
|
{
|
||||||
|
var callurl=new URL("api/v2/Progress",this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
p(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
queuelist(ql)
|
||||||
|
{
|
||||||
|
var callurl=new URL("api/v2/QueueList",this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
ql(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getvideos(vid)
|
||||||
|
{
|
||||||
|
this.getvideoinfofiles(function(e){
|
||||||
|
//an array
|
||||||
|
e.forEach(element => {
|
||||||
|
this.getvideoinfofile(element,vid);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getplaylists(pl)
|
||||||
|
{
|
||||||
|
this.getplaylistinfofiles(function(e){
|
||||||
|
//an array
|
||||||
|
e.forEach(element => {
|
||||||
|
this.getplaylistinfofile(element,pl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getchannels(chan)
|
||||||
|
{
|
||||||
|
this.getchannelinfofiles(function(e){
|
||||||
|
e.forEach(element =>{
|
||||||
|
this.getchannelinfofile(element,chan);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getchannelinfo(id,info)
|
||||||
|
{
|
||||||
|
this.getchannelinfofile(`${id}.json`,info);
|
||||||
|
}
|
||||||
|
getvideoinfo(id,info)
|
||||||
|
{
|
||||||
|
this.getvideoinfofile(`${id}.json`,info);
|
||||||
|
}
|
||||||
|
getvideoinfofile(filename,info)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/File/Info/${filename}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
info(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getchannelinfofile(filename,info)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/File/Channel/${filename}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
info(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getvideoinfofiles(vid)
|
||||||
|
{
|
||||||
|
var callurl=new URL("api/Storage/GetFiles/Info",this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
vid(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getplaylistinfo(info)
|
||||||
|
{
|
||||||
|
this.getplaylistinfofile(`${id}.json`,info);
|
||||||
|
}
|
||||||
|
getplaylistinfofile(info)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/File/Playlist/${filename}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
info(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getplaylistinfofiles(pl)
|
||||||
|
{
|
||||||
|
var callurl = new URL("api/Storage/GetFiles/Playlist",this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
pl(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getchannelinfofiles(chan)
|
||||||
|
{
|
||||||
|
var callurl = new URL("api/Storage/GetFiles/Channel",this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
chan(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getsubscriptions(subs)
|
||||||
|
{
|
||||||
|
var callurl = new URL("api/v2/subscriptions",this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
subs(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
subscribe(cid,getinfo,conf)
|
||||||
|
{
|
||||||
|
var ginfo = getinfo == true ? "true" : "false";
|
||||||
|
var callurl = new URL(`api/v2/subscribe?id=${encodeURIComponent(cid)}&conf=${conf}&getinfo=${getinfo}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
subscribe(name,conf)
|
||||||
|
{
|
||||||
|
var callurl = new URL(`api/v2/subscribe?id=${encodeURIComponent(name)}&conf=${conf}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unsubscribe(cid)
|
||||||
|
{
|
||||||
|
var callurl = new URL(`api/v2/unsubscribe?id=${encodeURIComponent(cid)}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resubscribe(cid,conf)
|
||||||
|
{
|
||||||
|
var callurl = new URL(`api/v2/resubscribe?id=${encodeURIComponent(cid)}&conf=${conf}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getfiles(path,ls)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/GetFiles/${path}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
ls(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getdirectories(path,ls)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/GetDirectories/${path}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
ls(JSON.parse(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fileexists(path,exists,doesntexist)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/FileExists/${path}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
if(e==="true")
|
||||||
|
{
|
||||||
|
exists();
|
||||||
|
}else{
|
||||||
|
doesntexist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
directoryexists(path,exists,doesntexist)
|
||||||
|
{
|
||||||
|
var callurl=new URL(`api/Storage/DirectoryExists/${path}`,this.server).href;
|
||||||
|
this.request(callurl,function(e){
|
||||||
|
if(e==="true")
|
||||||
|
{
|
||||||
|
exists();
|
||||||
|
}else{
|
||||||
|
doesntexist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Video Player</title>
|
||||||
|
<link rel="stylesheet" href="css/player.css">
|
||||||
|
<script src="js/player.js" defer></script>
|
||||||
|
<script src="js/num2unit.js"></script>
|
||||||
|
<script src="js/tytd-jquery.js"></script>
|
||||||
|
<script src="js/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="video-container paused" data-volume-level="high">
|
||||||
|
<img class="thumbnail-img">
|
||||||
|
<div class="video-controls-container">
|
||||||
|
<div class="timeline-container">
|
||||||
|
<div class="timeline">
|
||||||
|
<div class="thumb-indicator"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<button class="play-pause-btn">
|
||||||
|
<svg class="play-icon" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" />
|
||||||
|
</svg>
|
||||||
|
<svg class="pause-icon" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M14,19H18V5H14M6,19H10V5H6V19Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="volume-container">
|
||||||
|
<button class="mute-btn">
|
||||||
|
<svg class="volume-high-icon" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z" />
|
||||||
|
</svg>
|
||||||
|
<svg class="volume-low-icon" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M5,9V15H9L14,20V4L9,9M18.5,12C18.5,10.23 17.5,8.71 16,7.97V16C17.5,15.29 18.5,13.76 18.5,12Z" />
|
||||||
|
</svg>
|
||||||
|
<svg class="volume-muted-icon" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M12,4L9.91,6.09L12,8.18M4.27,3L3,4.27L7.73,9H3V15H7L12,20V13.27L16.25,17.53C15.58,18.04 14.83,18.46 14,18.7V20.77C15.38,20.45 16.63,19.82 17.68,18.96L19.73,21L21,19.73L12,10.73M19,12C19,12.94 18.8,13.82 18.46,14.64L19.97,16.15C20.62,14.91 21,13.5 21,12C21,7.72 18,4.14 14,3.23V5.29C16.89,6.15 19,8.83 19,12M16.5,12C16.5,10.23 15.5,8.71 14,7.97V10.18L16.45,12.63C16.5,12.43 16.5,12.21 16.5,12Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<input class="volume-slider" type="range" min="0" max="1" step="any" value="1">
|
||||||
|
</div>
|
||||||
|
<div class="duration-container">
|
||||||
|
<div class="current-time">0:00</div>
|
||||||
|
/
|
||||||
|
<div class="total-time"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<button class="speed-btn wide-btn">
|
||||||
|
1x
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<button class="download-btn">
|
||||||
|
<svg viewBox="0 0 48 48">
|
||||||
|
<path fill="currentColor" d="M24 32.35 14.35 22.7 16.5 20.55 22.5 26.55V8H25.5V26.55L31.5 20.55L33.65 22.7ZM11 40Q9.8 40 8.9 39.1Q8 38.2 8 37V29.85H11V37Q11 37 11 37Q11 37 11 37H37Q37 37 37 37Q37 37 37 37V29.85H40V37Q40 38.2 39.1 39.1Q38.2 40 37 40Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="mini-player-btn">
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zm-10-7h9v6h-9z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="theater-btn">
|
||||||
|
<svg class="tall" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="wide" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M19 7H5c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zm0 8H5V9h14v6z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="full-screen-btn">
|
||||||
|
<svg class="open" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="close" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<video src=""></video>
|
||||||
|
<a href="" download id="Mux" hidden></a>
|
||||||
|
<a href="" download id="PreMuxed" hidden></a>
|
||||||
|
<a href="" download id="Audio" hidden></a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="video-info">
|
||||||
|
<div class="video-title" >
|
||||||
|
<h3 id="video-title">Title</h3>
|
||||||
|
</div>
|
||||||
|
<div class="video-stats">
|
||||||
|
<div><span id="video-views">0 </span> views • <span id="video-date"></span></div>
|
||||||
|
<div class="video-actions">
|
||||||
|
<button class="video-action" onclick="alert('Likes are readonly')">
|
||||||
|
|
||||||
|
<svg viewBox="0 0 24 24" width="30" height="30">
|
||||||
|
<path d="M18.77,11h-4.23l1.52-4.94C16.38,5.03,15.54,4,14.38,4c-0.58,0-1.14,0.24-1.52,0.65L7,11H3v10h4h1h9.43 c1.06,0,1.98-0.67,2.19-1.61l1.34-6C21.23,12.15,20.18,11,18.77,11z M7,20H4v-8h3V20z M19.98,13.17l-1.34,6 C18.54,19.65,18.03,20,17.43,20H8v-8.61l5.6-6.06C13.79,5.12,14.08,5,14.38,5c0.26,0,0.5,0.11,0.63,0.3 c0.07,0.1,0.15,0.26,0.09,0.47l-1.52,4.94L13.18,12h1.35h4.23c0.41,0,0.8,0.17,1.03,0.46C19.92,12.61,20.05,12.86,19.98,13.17z"/>
|
||||||
|
</svg> <a id="video-likes">LIKE</a>
|
||||||
|
</button>
|
||||||
|
<button class="video-action" onclick="alert('Dislikes are readonly')">
|
||||||
|
|
||||||
|
<svg viewBox="0 0 24 24" width="30" height="30">
|
||||||
|
<path d="M17,4h-1H6.57C5.5,4,4.59,4.67,4.38,5.61l-1.34,6C2.77,12.85,3.82,14,5.23,14h4.23l-1.52,4.94C7.62,19.97,8.46,21,9.62,21 c0.58,0,1.14-0.24,1.52-0.65L17,14h4V4H17z M10.4,19.67C10.21,19.88,9.92,20,9.62,20c-0.26,0-0.5-0.11-0.63-0.3 c-0.07-0.1-0.15-0.26-0.09-0.47l1.52-4.94l0.4-1.29H9.46H5.23c-0.41,0-0.8-0.17-1.03-0.46c-0.12-0.15-0.25-0.4-0.18-0.72l1.34-6 C5.46,5.35,5.97,5,6.57,5H16v8.61L10.4,19.67z M20,13h-3V5h3V13z"/>
|
||||||
|
</svg> <a id="video-dislikes"> DISLIKE</a>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="video-action">
|
||||||
|
|
||||||
|
<svg class="share-icon" viewBox="0 0 24 24" width="30" height="30">
|
||||||
|
<path fill="currentColor" d="M15,5.63L20.66,12L15,18.37V15v-1h-1c-3.96,0-7.14,1-9.75,3.09c1.84-4.07,5.11-6.4,9.89-7.1L15,9.86V9V5.63 M14,3v6 C6.22,10.13,3.11,15.33,2,21c2.78-3.97,6.44-6,12-6v6l8-9L14,3L14,3z"/>
|
||||||
|
</svg> SHARE
|
||||||
|
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<button class="video-action res-btn">
|
||||||
|
PreMuxed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="channel-info">
|
||||||
|
<img src="" id="video-channel-icon" alt="" width="64" height="64">
|
||||||
|
<div>
|
||||||
|
<p id="video-channel-title"></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="subscribe">SUBSCRIBE</button>
|
||||||
|
<button type="button" class="bell">
|
||||||
|
<svg viewBox="0 0 24 24" width="30" height="30">
|
||||||
|
<path d="M10,20h4c0,1.1-0.9,2-2,2S10,21.1,10,20z M20,17.35V19H4v-1.65l2-1.88v-5.15c0-2.92,1.56-5.22,4-5.98V3.96 c0-1.42,1.49-2.5,2.99-1.76C13.64,2.52,14,3.23,14,3.96l0,0.39c2.44,0.75,4,3.06,4,5.98v5.15L20,17.35z M19,17.77l-2-1.88v-5.47 c0-2.47-1.19-4.36-3.13-5.1c-1.26-0.53-2.64-0.5-3.84,0.03C8.15,6.11,7,7.99,7,10.42v5.47l-2,1.88V18h14V17.77z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="description" id="video-description">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus dolorum dolore corrupti quos, unde aliquam rerum libero saepe eius ipsam neque? Impedit magnam quam minus molestias ipsam quos voluptates repellat.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -64,8 +64,8 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}
|
}
|
||||||
internal class ApiStorage : Tesses.WebServer.Server
|
internal class ApiStorage : Tesses.WebServer.Server
|
||||||
{
|
{
|
||||||
TYTDBase baseCtl;
|
ITYTDBase baseCtl;
|
||||||
public ApiStorage(TYTDBase baseCtl)
|
public ApiStorage(ITYTDBase baseCtl)
|
||||||
{
|
{
|
||||||
this.baseCtl=baseCtl;
|
this.baseCtl=baseCtl;
|
||||||
|
|
||||||
|
@ -341,7 +341,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}
|
}
|
||||||
public async Task Subscriptions(ServerContext ctx)
|
public async Task Subscriptions(ServerContext ctx)
|
||||||
{
|
{
|
||||||
TYTDStorage storage = Downloader as TYTDStorage;
|
IStorage storage = Downloader as IStorage;
|
||||||
if(storage != null)
|
if(storage != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -355,7 +355,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}
|
}
|
||||||
public async Task Resubscribe(ServerContext ctx)
|
public async Task Resubscribe(ServerContext ctx)
|
||||||
{
|
{
|
||||||
TYTDStorage storage = Downloader as TYTDStorage;
|
IStorage storage = Downloader as IStorage;
|
||||||
if(storage != null)
|
if(storage != null)
|
||||||
{
|
{
|
||||||
string id;
|
string id;
|
||||||
|
@ -389,7 +389,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
|
|
||||||
public async Task Unsubscribe(ServerContext ctx)
|
public async Task Unsubscribe(ServerContext ctx)
|
||||||
{
|
{
|
||||||
TYTDStorage storage = Downloader as TYTDStorage;
|
IStorage storage = Downloader as IStorage;
|
||||||
if(storage != null)
|
if(storage != null)
|
||||||
{
|
{
|
||||||
string id;
|
string id;
|
||||||
|
@ -415,7 +415,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}
|
}
|
||||||
public async Task Subscribe(ServerContext ctx)
|
public async Task Subscribe(ServerContext ctx)
|
||||||
{
|
{
|
||||||
TYTDStorage storage = Downloader as TYTDStorage;
|
IStorage storage = Downloader as IStorage;
|
||||||
if(storage != null)
|
if(storage != null)
|
||||||
{
|
{
|
||||||
string id;
|
string id;
|
||||||
|
@ -595,7 +595,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseCtl">TYTD context</param>
|
/// <param name="baseCtl">TYTD context</param>
|
||||||
public TYTDServer(TYTDBase baseCtl)
|
public TYTDServer(ITYTDBase baseCtl)
|
||||||
{
|
{
|
||||||
ExtensionsServer=new ChangeableServer();
|
ExtensionsServer=new ChangeableServer();
|
||||||
RootServer=new ChangeableServer();
|
RootServer=new ChangeableServer();
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Tesses.WebServer" Version="1.0.1" />
|
<PackageReference Include="Tesses.WebServer" Version="1.0.3.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -15,9 +15,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.0.2.2</Version>
|
<Version>1.1.0</Version>
|
||||||
<AssemblyVersion>1.0.2.2</AssemblyVersion>
|
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||||
<FileVersion>1.0.2.2</FileVersion>
|
<FileVersion>1.1.0</FileVersion>
|
||||||
<Description>Adds WebServer to TYTD</Description>
|
<Description>Adds WebServer to TYTD</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Properties>
|
||||||
|
<MonoDevelop.Ide.Workbench>
|
||||||
|
<Pads>
|
||||||
|
<Pad Id="ProjectPad">
|
||||||
|
<State name="__root__">
|
||||||
|
<Node name="Tesses.YouTubeDownloader" expanded="True" selected="True">
|
||||||
|
<Node name="Tesses.YouTubeDownloader" expanded="True" />
|
||||||
|
</Node>
|
||||||
|
</State>
|
||||||
|
</Pad>
|
||||||
|
</Pads>
|
||||||
|
</MonoDevelop.Ide.Workbench>
|
||||||
|
<MonoDevelop.Ide.DebuggingService.PinnedWatches />
|
||||||
|
<MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" />
|
||||||
|
<MonoDevelop.Ide.DebuggingService.Breakpoints>
|
||||||
|
<BreakpointStore />
|
||||||
|
</MonoDevelop.Ide.DebuggingService.Breakpoints>
|
||||||
|
<MultiItemStartupConfigurations />
|
||||||
|
</Properties>
|
|
@ -18,7 +18,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public BestStreamInfo AudioOnlyStreamInfo {get;set;}
|
public BestStreamInfo AudioOnlyStreamInfo {get;set;}
|
||||||
|
|
||||||
public static async Task<string> GetPathResolution(TYTDBase storage,SavedVideo video,Resolution resolution=Resolution.PreMuxed)
|
public static async Task<string> GetPathResolution(ITYTDBase storage,SavedVideo video,Resolution resolution=Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
if(video.LegacyVideo)
|
if(video.LegacyVideo)
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public class BestStreamInfo : IStreamInfo
|
public class BestStreamInfo : IStreamInfo
|
||||||
{
|
{
|
||||||
public static async Task<BestStreams> GetBestStreams(TYTDBase storage,VideoId id)
|
public static async Task<BestStreams> GetBestStreams(ITYTDBase storage,VideoId id)
|
||||||
{
|
{
|
||||||
//Console.WriteLine("IN FUNC");
|
//Console.WriteLine("IN FUNC");
|
||||||
if(storage.DirectoryExists("StreamInfo"))
|
if(storage.DirectoryExists("StreamInfo"))
|
||||||
|
@ -66,7 +66,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public static async Task<BestStreams> GetBestStreams(TYTDStorage storage,VideoId id,CancellationToken token=default(CancellationToken),bool expire_check=true)
|
public static async Task<BestStreams> GetBestStreams(IStorage storage,VideoId id,CancellationToken token=default(CancellationToken),bool expire_check=true)
|
||||||
{
|
{
|
||||||
if(storage.DirectoryExists("StreamInfo"))
|
if(storage.DirectoryExists("StreamInfo"))
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
public readonly SavedVideoProgress Progress = new SavedVideoProgress();
|
public readonly SavedVideoProgress Progress = new SavedVideoProgress();
|
||||||
private async Task ReportProgress(double progress)
|
private async Task ReportProgress(double progress)
|
||||||
{
|
{
|
||||||
|
|
||||||
Progress.Progress = (int)(progress * 100);
|
Progress.Progress = (int)(progress * 100);
|
||||||
Progress.ProgressRaw = progress;
|
Progress.ProgressRaw = progress;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Videos;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.IO;
|
||||||
|
using YoutubeExplode.Playlists;
|
||||||
|
using YoutubeExplode.Channels;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Tesses.YouTubeDownloader
|
||||||
|
{
|
||||||
|
public interface IStorage : IWritable, IDownloader, ITYTDBase
|
||||||
|
{
|
||||||
|
|
||||||
|
Task<bool> MuxVideosAsync(SavedVideo video,string videoSrc,string audioSrc,string videoDest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken));
|
||||||
|
Task<bool> Continue(string path);
|
||||||
|
Task WriteVideoInfoAsync(SavedVideo channel);
|
||||||
|
Task WritePlaylistInfoAsync(SavedPlaylist channel);
|
||||||
|
Task WriteChannelInfoAsync(SavedChannel channel);
|
||||||
|
void CreateDirectoryIfNotExist(string path);
|
||||||
|
LegacyConverter Legacy {get;}
|
||||||
|
Logger GetLogger();
|
||||||
|
LoggerProperties GetLoggerProperties();
|
||||||
|
IReadOnlyList<Subscription> GetLoadedSubscriptions();
|
||||||
|
event EventHandler<BellEventArgs> Bell;
|
||||||
|
event EventHandler<VideoStartedEventArgs> VideoStarted;
|
||||||
|
|
||||||
|
event EventHandler<BeforeSaveInfoEventArgs> BeforeSaveInfo;
|
||||||
|
|
||||||
|
event EventHandler<VideoProgressEventArgs> VideoProgress;
|
||||||
|
|
||||||
|
public event EventHandler<VideoFinishedEventArgs> VideoFinished;
|
||||||
|
bool CanDownload {get;set;}
|
||||||
|
IExtensionContext ExtensionContext {get;set;}
|
||||||
|
HttpClient HttpClient {get;set;}
|
||||||
|
YoutubeClient YoutubeClient {get;set;}
|
||||||
|
Task<Stream> OpenOrCreateAsync(string path);
|
||||||
|
|
||||||
|
void RenameFile(string src, string dest);
|
||||||
|
|
||||||
|
Task<Stream> CreateAsync(string path);
|
||||||
|
void Unsubscribe(ChannelId id);
|
||||||
|
void CreateDirectory(string path);
|
||||||
|
|
||||||
|
void MoveDirectory(string src, string dest);
|
||||||
|
|
||||||
|
void DeleteFile(string file);
|
||||||
|
|
||||||
|
void DeleteDirectory(string dir, bool recursive = false);
|
||||||
|
Task<bool> DownloadVideoOnlyAsync(SavedVideo video,CancellationToken token,IProgress<double> progress,bool report=true);
|
||||||
|
Task MoveLegacyStreams(SavedVideo video,BestStreams streams);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Videos;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.IO;
|
||||||
|
using YoutubeExplode.Playlists;
|
||||||
|
using YoutubeExplode.Channels;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Tesses.YouTubeDownloader
|
||||||
|
{
|
||||||
|
public interface ITYTDBase : IPersonalPlaylistGet
|
||||||
|
{
|
||||||
|
|
||||||
|
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
||||||
|
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);
|
||||||
|
Task<long> GetLengthAsync(string path);
|
||||||
|
bool FileExists(string path);
|
||||||
|
IAsyncEnumerable<string> GetVideoIdsAsync();
|
||||||
|
Task<SavedVideo> GetVideoInfoAsync(VideoId id);
|
||||||
|
IAsyncEnumerable<SavedVideo> GetVideosAsync();
|
||||||
|
|
||||||
|
IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync();
|
||||||
|
Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id);
|
||||||
|
IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync();
|
||||||
|
|
||||||
|
|
||||||
|
Task<byte[]> ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken));
|
||||||
|
|
||||||
|
IAsyncEnumerable<string> GetPlaylistIdsAsync();
|
||||||
|
IAsyncEnumerable<string> GetChannelIdsAsync();
|
||||||
|
IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync();
|
||||||
|
Task<SavedChannel> GetChannelInfoAsync(ChannelId id);
|
||||||
|
IAsyncEnumerable<SavedChannel> GetChannelsAsync();
|
||||||
|
|
||||||
|
bool PlaylistInfoExists(PlaylistId id);
|
||||||
|
bool VideoInfoExists(VideoId id);
|
||||||
|
bool ChannelInfoExists(ChannelId id);
|
||||||
|
Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id);
|
||||||
|
Task<string> ReadAllTextAsync(string file);
|
||||||
|
bool DirectoryExists(string path);
|
||||||
|
|
||||||
|
IEnumerable<string> EnumerateFiles(string path);
|
||||||
|
IEnumerable<string> EnumerateDirectories(string path);
|
||||||
|
Task<Stream> OpenReadAsyncWithLength(string path);
|
||||||
|
Task<Stream> OpenReadAsync(string path);
|
||||||
|
|
||||||
|
Task<bool> FileExistsAsync(string path);
|
||||||
|
|
||||||
|
Task<bool> DirectoryExistsAsync(string path);
|
||||||
|
|
||||||
|
IAsyncEnumerable<string> EnumerateFilesAsync(string path);
|
||||||
|
|
||||||
|
IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path);
|
||||||
|
}
|
||||||
|
}
|
|
@ -270,6 +270,19 @@ namespace Tesses.YouTubeDownloader
|
||||||
List<Thumbnail> thumbnails=new List<Thumbnail>();
|
List<Thumbnail> thumbnails=new List<Thumbnail>();
|
||||||
return new Playlist(Id,Title,new Author(AuthorChannelId,AuthorTitle),Description,thumbnails);
|
return new Playlist(Id,Title,new Author(AuthorChannelId,AuthorTitle),Description,thumbnails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<(VideoId Id,Resolution Resolution)> ToPersonalPlaylist(Resolution res=Resolution.PreMuxed)
|
||||||
|
{
|
||||||
|
List<(VideoId Id,Resolution Resolution)> items=new List<(VideoId Id, Resolution Resolution)>();
|
||||||
|
if(Videos !=null)
|
||||||
|
{
|
||||||
|
foreach(var vid in Videos)
|
||||||
|
{
|
||||||
|
items.Add((vid,res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<SavedVideo> GetVideosAsync(TYTDBase baseCls)
|
public async IAsyncEnumerable<SavedVideo> GetVideosAsync(TYTDBase baseCls)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,10 +9,11 @@ using System.Net.Http;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using YoutubeExplode.Playlists;
|
using YoutubeExplode.Playlists;
|
||||||
using YoutubeExplode.Channels;
|
using YoutubeExplode.Channels;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Tesses.YouTubeDownloader
|
namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public abstract partial class TYTDStorage : TYTDBase, IWritable, IDownloader
|
public abstract partial class TYTDStorage : TYTDBase, IStorage
|
||||||
{
|
{
|
||||||
private static readonly HttpClient _default = new HttpClient();
|
private static readonly HttpClient _default = new HttpClient();
|
||||||
public abstract Task<Stream> CreateAsync(string path);
|
public abstract Task<Stream> CreateAsync(string path);
|
||||||
|
@ -43,14 +44,38 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
bool can_download=true;
|
bool can_download=true;
|
||||||
public bool CanDownload {get {return can_download;} set {can_download=value;}}
|
public bool CanDownload {get {return can_download;} set {can_download=value;}}
|
||||||
|
public IExtensionContext ExtensionContext {get;set;}
|
||||||
|
public HttpClient HttpClient {get;set;}
|
||||||
|
public YoutubeClient YoutubeClient {get;set;}
|
||||||
public abstract void MoveDirectory(string src,string dest);
|
public abstract void MoveDirectory(string src,string dest);
|
||||||
public abstract void DeleteFile(string file);
|
public abstract void DeleteFile(string file);
|
||||||
public abstract void DeleteDirectory(string dir,bool recursive=false);
|
public abstract void DeleteDirectory(string dir,bool recursive=false);
|
||||||
public IExtensionContext ExtensionContext {get;set;}
|
|
||||||
public HttpClient HttpClient {get;set;}
|
|
||||||
public YoutubeClient YoutubeClient {get;set;}
|
|
||||||
|
|
||||||
|
public async Task WriteVideoInfoAsync(SavedVideo info)
|
||||||
|
{
|
||||||
|
string file = $"Info/{info.Id}.json";
|
||||||
|
if(!FileExists(file))
|
||||||
|
{
|
||||||
|
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task WritePlaylistInfoAsync(SavedPlaylist info)
|
||||||
|
{
|
||||||
|
string file = $"Playlist/{info.Id}.json";
|
||||||
|
if(!FileExists(file))
|
||||||
|
{
|
||||||
|
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task WriteChannelInfoAsync(SavedChannel info)
|
||||||
|
{
|
||||||
|
string file = $"Channel/{info.Id}.json";
|
||||||
|
if(!FileExists(file))
|
||||||
|
{
|
||||||
|
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
public async Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed)
|
public async Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
lock(Temporary)
|
lock(Temporary)
|
||||||
|
|
|
@ -74,9 +74,9 @@ namespace Tesses.YouTubeDownloader
|
||||||
baseStrm.Close();
|
baseStrm.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public abstract class TYTDBase
|
public abstract class TYTDBase : ITYTDBase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public async IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string playlist)
|
public async IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string playlist)
|
||||||
{
|
{
|
||||||
|
@ -214,6 +214,14 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in GetVideoIdsAsync())
|
||||||
|
{
|
||||||
|
VideoId? id= VideoId.TryParse(item);
|
||||||
|
if(id.HasValue) yield return id.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<SavedChannel>(await ReadAllTextAsync($"Channel/{id}.json"));
|
return JsonConvert.DeserializeObject<SavedChannel>(await ReadAllTextAsync($"Channel/{id}.json"));
|
||||||
|
@ -230,12 +238,23 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PlaylistInfoExists(PlaylistId id)
|
||||||
|
{
|
||||||
|
return FileExists($"Playlist/{id}.json");
|
||||||
|
}
|
||||||
|
public bool VideoInfoExists(VideoId id)
|
||||||
|
{
|
||||||
|
return FileExists($"Info/{id}.json");
|
||||||
|
}
|
||||||
|
public bool ChannelInfoExists(ChannelId id)
|
||||||
|
{
|
||||||
|
return FileExists($"Channel/{id}.json");
|
||||||
|
}
|
||||||
public async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
|
public async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<SavedPlaylist>(await ReadAllTextAsync($"Playlist/{id}.json"));
|
return JsonConvert.DeserializeObject<SavedPlaylist>(await ReadAllTextAsync($"Playlist/{id}.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<string> ReadAllTextAsync(string file)
|
public async Task<string> ReadAllTextAsync(string file)
|
||||||
{
|
{
|
||||||
using(var s = await OpenReadAsync(file))
|
using(var s = await OpenReadAsync(file))
|
||||||
|
@ -290,7 +309,15 @@ namespace Tesses.YouTubeDownloader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url">Video, Playlist, Channel Or UserName Url Or Id</param>
|
/// <param name="url">Video, Playlist, Channel Or UserName Url Or Id</param>
|
||||||
/// <param name="resolution">Video Resolution</param>
|
/// <param name="resolution">Video Resolution</param>
|
||||||
|
public static async Task<List<(VideoId Id,Resolution Resolution)>> ToPersonalPlaylist(this IAsyncEnumerable<VideoId> list,Resolution res)
|
||||||
|
{
|
||||||
|
List<(VideoId Id,Resolution Resolution)> items=new List<(VideoId Id, Resolution Resolution)>();
|
||||||
|
await foreach(var item in list)
|
||||||
|
{
|
||||||
|
items.Add((item,res));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
public static async Task AddItemAsync(this IDownloader downloader,string url,Resolution resolution=Resolution.PreMuxed)
|
public static async Task AddItemAsync(this IDownloader downloader,string url,Resolution resolution=Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -388,10 +415,19 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
return resStr[(int)res];
|
return resStr[(int)res];
|
||||||
}
|
}
|
||||||
public static async Task<bool> CopyVideoToFileAsync(VideoId id,TYTDBase src,Stream destFile,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
public static async Task<bool> CopyVideoToFileAsync(VideoId id,ITYTDBase src,Stream destFile,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||||
{
|
{
|
||||||
string resDir = ResolutionToDirectory(res);
|
string infoFile=$"Info/{id.Value}.json";
|
||||||
string path=$"{resDir}/{id.Value}.mp4";
|
string path="";
|
||||||
|
if(await src.FileExistsAsync(infoFile)){
|
||||||
|
string f=await src.ReadAllTextAsync(infoFile);
|
||||||
|
SavedVideo video = JsonConvert.DeserializeObject<SavedVideo>(f);
|
||||||
|
path=await BestStreams.GetPathResolution(src,video,res);
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(string.IsNullOrWhiteSpace(path)) return false;
|
||||||
|
|
||||||
bool ret=false;
|
bool ret=false;
|
||||||
double len=await src.GetLengthAsync(path);
|
double len=await src.GetLengthAsync(path);
|
||||||
if(await src.FileExistsAsync(path))
|
if(await src.FileExistsAsync(path))
|
||||||
|
@ -410,7 +446,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
public static async Task CopyVideoToFileAsync(VideoId id,TYTDBase src,string dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
public static async Task CopyVideoToFileAsync(VideoId id,ITYTDBase src,string dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||||
{
|
{
|
||||||
bool delete=false;
|
bool delete=false;
|
||||||
using(var f = File.Create(dest))
|
using(var f = File.Create(dest))
|
||||||
|
@ -457,20 +493,238 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public static async Task CopyThumbnailsAsync(string id,ITYTDBase src,IStorage dest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||||
public static async Task CopyVideoAsync(VideoId id,TYTDBase src,TYTDStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
|
||||||
{
|
{
|
||||||
string resDir = ResolutionToDirectory(res);
|
|
||||||
|
|
||||||
string path=$"{resDir}/{id.Value}.mp4";
|
|
||||||
string infoFile = $"Info/{id.Value}.json";
|
|
||||||
|
|
||||||
|
|
||||||
if(await src.FileExistsAsync(infoFile))
|
if(!src.DirectoryExists("Thumbnails") || !src.DirectoryExists($"Thumbnails/{id}")) return;
|
||||||
|
await dest.CopyDirectoryFrom(src,$"Thumbnails/{id}",$"Thumbnails/{id}",progress,token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task CopyDirectoryFrom(this IStorage _dest,ITYTDBase _src,string src,string dest,IProgress<double> progress,CancellationToken token=default(CancellationToken))
|
||||||
|
{
|
||||||
|
|
||||||
|
List<string> dirs=new List<string>();
|
||||||
|
|
||||||
|
await foreach(var dir in _src.EnumerateDirectoriesAsync(src))
|
||||||
{
|
{
|
||||||
if(!await dest.FileExistsAsync(infoFile))
|
dirs.Add(dir);
|
||||||
|
_dest.CreateDirectoryIfNotExist($"{dest.TrimEnd('/')}/{dir.TrimStart('/')}");
|
||||||
|
}
|
||||||
|
List<string> files=new List<string>();
|
||||||
|
await foreach(var file in _src.EnumerateFilesAsync(src))
|
||||||
|
{
|
||||||
|
files.Add(file);
|
||||||
|
}
|
||||||
|
int total = dirs.Count + files.Count;
|
||||||
|
int i=0;
|
||||||
|
double segLen = 1.0 / total;
|
||||||
|
foreach(var item in dirs)
|
||||||
|
{
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
await CopyDirectoryFrom(_dest,_src,$"{src.TrimEnd('/')}/{item.TrimStart('/')}",$"{dest.TrimEnd('/')}/{item.TrimStart('/')}",new Progress<double>(e=>{
|
||||||
|
double percent = e / total;
|
||||||
|
percent += segLen * i;
|
||||||
|
if(percent >= 1) percent=1;
|
||||||
|
if(percent <= 0) percent= 0;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}),token);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
foreach(var item in files)
|
||||||
|
{
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
await CopyFileFrom(_dest,_src,$"{src.TrimEnd('/')}/{item.TrimStart('/')}",$"{dest.TrimEnd('/')}/{item.TrimStart('/')}",new Progress<double>(e=>{
|
||||||
|
double percent = e / total;
|
||||||
|
percent += segLen * i;
|
||||||
|
if(percent >= 1) percent=1;
|
||||||
|
if(percent <= 0) percent= 0;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}),token);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task CopyFileFrom(this IStorage _dest,ITYTDBase _src,string src,string dest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||||
|
{
|
||||||
|
double len=await _src.GetLengthAsync(src);
|
||||||
|
using(var srcFile = await _src.OpenReadAsync(src))
|
||||||
{
|
{
|
||||||
string f=await src.ReadAllTextAsync(infoFile);
|
bool deleteFile=false;
|
||||||
|
using(var destFile = await _dest.CreateAsync(dest))
|
||||||
|
{
|
||||||
|
deleteFile=!await CopyStream(srcFile,destFile,new Progress<long>((e)=>{
|
||||||
|
if(progress !=null)
|
||||||
|
{
|
||||||
|
progress.Report(e/len);
|
||||||
|
}
|
||||||
|
}),token);
|
||||||
|
}
|
||||||
|
//dest.DeleteFile(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static async Task CopyVideoAsync(VideoId id,ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||||
|
{
|
||||||
|
await CopyVideoAsync(id,src,dest,res,progress,token,true);
|
||||||
|
}
|
||||||
|
public static async Task CopyChannelContentsAsync(ChannelId id,ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
List<(VideoId Id, Resolution Resolution)> items=new List<(VideoId Id, Resolution Resolution)>();
|
||||||
|
await foreach(var v in src.GetVideosAsync())
|
||||||
|
{
|
||||||
|
if(v.AuthorChannelId == id.Value)
|
||||||
|
{
|
||||||
|
VideoId? id2=VideoId.TryParse(v.Id);
|
||||||
|
if(id2.HasValue)
|
||||||
|
{
|
||||||
|
items.Add((id2.Value,res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await CopyPersonalPlaylistContentsAsync(items,src,dest,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyChannelAsync(ChannelId id,ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=false)
|
||||||
|
{
|
||||||
|
if(!src.ChannelInfoExists(id)) return;
|
||||||
|
var channel = await src.GetChannelInfoAsync(id);
|
||||||
|
await dest.WriteChannelInfoAsync(channel);
|
||||||
|
if(copyThumbnails)
|
||||||
|
await CopyThumbnailsAsync(id,src,dest,progress,default(CancellationToken));
|
||||||
|
|
||||||
|
if(copyContents)
|
||||||
|
await CopyChannelContentsAsync(id,src,dest,res,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyEverythingAsync(ITYTDBase src,IStorage dest,Resolution res,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await CopyAllPlaylistsAsync(src,dest,res,null,token,copyThumbnails,false);
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
await CopyAllChannelsAsync(src,dest,res,null,token,true);
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
await CopyAllVideosAsync(src,dest,res,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyEverythingAsync(ITYTDBase src,IStorage dest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await CopyEverythingAsync(src,dest,Resolution.Mux,new Progress<double>(e=>{
|
||||||
|
double percent=e / 4;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}));
|
||||||
|
await CopyEverythingAsync(src,dest,Resolution.PreMuxed,new Progress<double>(e=>{
|
||||||
|
double percent=e / 4;
|
||||||
|
percent+=0.25;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}));
|
||||||
|
await CopyEverythingAsync(src,dest,Resolution.AudioOnly,new Progress<double>(e=>{
|
||||||
|
double percent=e / 4;
|
||||||
|
percent+=0.50;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}));
|
||||||
|
await CopyEverythingAsync(src,dest,Resolution.VideoOnly,new Progress<double>(e=>{
|
||||||
|
double percent=e / 4;
|
||||||
|
percent+=0.75;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
public static async Task CopyAllVideosAsync(ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await CopyPersonalPlaylistContentsAsync(await src.GetYouTubeExplodeVideoIdsAsync().ToPersonalPlaylist(res),src,dest,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyAllPlaylistsAsync(ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=true)
|
||||||
|
{
|
||||||
|
List<SavedPlaylist> pl=new List<SavedPlaylist>();
|
||||||
|
await foreach(var playlist in src.GetPlaylistsAsync())
|
||||||
|
{
|
||||||
|
pl.Add(playlist);
|
||||||
|
}
|
||||||
|
int total=pl.Count;
|
||||||
|
int i = 0;
|
||||||
|
double segLen = 1.0 / total;
|
||||||
|
foreach(var item in pl)
|
||||||
|
{
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
await CopyPlaylistAsync(item.Id,src,dest,res,new Progress<double>(e=>{
|
||||||
|
double percent = e / total;
|
||||||
|
percent += segLen * i;
|
||||||
|
if(percent >= 1) percent=1;
|
||||||
|
if(percent <= 0) percent= 0;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}),token,copyThumbnails,copyContents);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task CopyAllChannelsAsync(ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=false)
|
||||||
|
{
|
||||||
|
//copying all channels is equivalent to copying all videos except we copy all channels info also
|
||||||
|
await foreach(var item in src.GetChannelsAsync())
|
||||||
|
{
|
||||||
|
await dest.WriteChannelInfoAsync(item);
|
||||||
|
}
|
||||||
|
if(copyContents)
|
||||||
|
await CopyAllVideosAsync(src,dest,res,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyPersonalPlaylistContentsAsync(List<(VideoId Id,Resolution Resolution)> list,ITYTDBase src,IStorage dest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken), bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
int total=list.Count;
|
||||||
|
int i = 0;
|
||||||
|
double segLen = 1.0 / total;
|
||||||
|
|
||||||
|
foreach(var item in list)
|
||||||
|
{
|
||||||
|
if(token.IsCancellationRequested) return;
|
||||||
|
await CopyVideoAsync(item.Id,src,dest,item.Resolution,new Progress<double>(e=>{
|
||||||
|
double percent = e / total;
|
||||||
|
percent += segLen * i;
|
||||||
|
if(percent >= 1) percent=1;
|
||||||
|
if(percent <= 0) percent= 0;
|
||||||
|
progress?.Report(percent);
|
||||||
|
}),token,copyThumbnails);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task CopyPlaylistContentsAsync(SavedPlaylist list,ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken), bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
|
||||||
|
await CopyPersonalPlaylistContentsAsync(list.ToPersonalPlaylist(res),src,dest,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyPlaylistAsync(PlaylistId id,ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token = default(CancellationToken),bool copyThumbnails=true,bool copyContents=true)
|
||||||
|
{
|
||||||
|
//copy playlist and videos
|
||||||
|
if(!src.PlaylistInfoExists(id)) return;
|
||||||
|
var playlist=await src.GetPlaylistInfoAsync(id);
|
||||||
|
await dest.WritePlaylistInfoAsync(playlist);
|
||||||
|
if(copyContents)
|
||||||
|
await CopyPlaylistContentsAsync(playlist,src,dest,res,progress,token,copyThumbnails);
|
||||||
|
}
|
||||||
|
public static async Task CopyVideoAsync(VideoId id,ITYTDBase src,IStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
if(copyThumbnails)
|
||||||
|
{
|
||||||
|
await CopyThumbnailsAsync(id.Value,src,dest,progress,token);
|
||||||
|
}
|
||||||
|
string resDir = ResolutionToDirectory(res);
|
||||||
|
|
||||||
|
string infoFile = $"Info/{id}.json";
|
||||||
|
|
||||||
|
string streamInfo = $"StreamInfo/{id}.json";
|
||||||
|
string path="";
|
||||||
|
if(src.VideoInfoExists(id))
|
||||||
|
{
|
||||||
|
|
||||||
|
SavedVideo video = await src.GetVideoInfoAsync(id);
|
||||||
|
path=await BestStreams.GetPathResolution(src,video,res);
|
||||||
|
if(!dest.VideoInfoExists(id))
|
||||||
|
{
|
||||||
|
await dest.WriteVideoInfoAsync(video);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(string.IsNullOrWhiteSpace(path)) return;
|
||||||
|
if(await src.FileExistsAsync(streamInfo))
|
||||||
|
{
|
||||||
|
if(!await dest.FileExistsAsync(streamInfo))
|
||||||
|
{
|
||||||
|
string f = await src.ReadAllTextAsync(streamInfo);
|
||||||
await dest.WriteAllTextAsync(infoFile,f);
|
await dest.WriteAllTextAsync(infoFile,f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -520,9 +774,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
return $"[{Offset}-{Offset.Add(Length)}] {ChapterName}";
|
return $"[{Offset}-{Offset.Add(Length)}] {ChapterName}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public interface IWritable
|
public interface IPersonalPlaylistGet
|
||||||
{
|
{
|
||||||
public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name);
|
public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name);
|
||||||
public Task WriteAllTextAsync(string path,string data);
|
|
||||||
|
}
|
||||||
|
public interface IWritable : IPersonalPlaylistGet
|
||||||
|
{
|
||||||
|
public Task WriteAllTextAsync(string path,string data);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,663 @@
|
||||||
|
using System;
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Videos;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using YoutubeExplode.Playlists;
|
||||||
|
using YoutubeExplode.Channels;
|
||||||
|
using System.IO;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Tesses.YouTubeDownloader
|
||||||
|
{
|
||||||
|
public class TYTDDownloaderStorageProxy : IStorage
|
||||||
|
{
|
||||||
|
public IDownloader Downloader {get;set;}
|
||||||
|
|
||||||
|
private ITYTDBase _base=null;
|
||||||
|
public ITYTDBase Storage {get {return _base;} set{_base=value;
|
||||||
|
var v = value as IStorage;
|
||||||
|
if(v != null)
|
||||||
|
SetStorage(v);}}
|
||||||
|
|
||||||
|
private void SetStorage(IStorage storage)
|
||||||
|
{
|
||||||
|
if(_storage !=null)
|
||||||
|
{
|
||||||
|
_storage.Bell -= _EVT_BELL;
|
||||||
|
_storage.BeforeSaveInfo -= _EVT_BSI;
|
||||||
|
_storage.VideoFinished -= _EVT_VFIN;
|
||||||
|
_storage.VideoProgress -= _EVT_VPROG;
|
||||||
|
_storage.VideoStarted -= _EVT_VSTAR;
|
||||||
|
}
|
||||||
|
_storage=storage;
|
||||||
|
if(storage != null)
|
||||||
|
{
|
||||||
|
_storage.Bell += _EVT_BELL;
|
||||||
|
_storage.BeforeSaveInfo += _EVT_BSI;
|
||||||
|
_storage.VideoFinished += _EVT_VFIN;
|
||||||
|
_storage.VideoProgress += _EVT_VPROG;
|
||||||
|
_storage.VideoStarted += _EVT_VSTAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
private void _EVT_VSTAR(object sender,VideoStartedEventArgs evt)
|
||||||
|
{
|
||||||
|
VideoStarted?.Invoke(this,evt);
|
||||||
|
}
|
||||||
|
private void _EVT_VPROG(object sender,VideoProgressEventArgs evt)
|
||||||
|
{
|
||||||
|
VideoProgress?.Invoke(this,evt);
|
||||||
|
}
|
||||||
|
private void _EVT_VFIN(object sender,VideoFinishedEventArgs evt)
|
||||||
|
{
|
||||||
|
VideoFinished?.Invoke(this,evt);
|
||||||
|
}
|
||||||
|
private void _EVT_BSI(object sender,BeforeSaveInfoEventArgs evt)
|
||||||
|
{
|
||||||
|
BeforeSaveInfo?.Invoke(this,evt);
|
||||||
|
}
|
||||||
|
private void _EVT_BELL(object sender,BellEventArgs evt)
|
||||||
|
{
|
||||||
|
Bell?.Invoke(this,evt);
|
||||||
|
}
|
||||||
|
IStorage _storage=null;
|
||||||
|
|
||||||
|
public LegacyConverter Legacy {
|
||||||
|
get{
|
||||||
|
LegacyConverter conv=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
conv=e.Legacy;
|
||||||
|
});
|
||||||
|
return conv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanDownload { get {
|
||||||
|
bool dl=false;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
dl=e.CanDownload;
|
||||||
|
});
|
||||||
|
return dl;
|
||||||
|
} set {StorageAsStorage((e)=>{e.CanDownload=value;});} }
|
||||||
|
public IExtensionContext ExtensionContext { get {IExtensionContext ctx=null;StorageAsStorage((e)=>{ctx=e.ExtensionContext;});return ctx;} set {StorageAsStorage((e)=>{e.ExtensionContext=value;});} }
|
||||||
|
public HttpClient HttpClient
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
HttpClient clt=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
clt=e.HttpClient;
|
||||||
|
});
|
||||||
|
return clt;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.HttpClient=value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public YoutubeClient YoutubeClient
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
YoutubeClient clt=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
clt=e.YoutubeClient;
|
||||||
|
});
|
||||||
|
return clt;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.YoutubeClient=value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<BellEventArgs> Bell;
|
||||||
|
public event EventHandler<VideoStartedEventArgs> VideoStarted;
|
||||||
|
public event EventHandler<BeforeSaveInfoEventArgs> BeforeSaveInfo;
|
||||||
|
public event EventHandler<VideoProgressEventArgs> VideoProgress;
|
||||||
|
public event EventHandler<VideoFinishedEventArgs> VideoFinished;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public async Task StorageAsStorageAsync(Func<IStorage,Task> callback)
|
||||||
|
{
|
||||||
|
var store = Storage as IStorage;
|
||||||
|
if(store != null && callback != null) await callback(store);
|
||||||
|
}
|
||||||
|
public void StorageAsStorage(Action<IStorage> callback)
|
||||||
|
{
|
||||||
|
var store = Storage as IStorage;
|
||||||
|
if(store != null && callback != null) callback(store);
|
||||||
|
}
|
||||||
|
public async Task StorageAsWritableAsync(Func<IWritable,Task> callback)
|
||||||
|
{
|
||||||
|
var store = Storage as IWritable;
|
||||||
|
if(store != null && callback != null) await callback(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public DownloaderMigration Migration
|
||||||
|
{
|
||||||
|
get{
|
||||||
|
return new DownloaderMigration(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> OpenOrCreateAsync(string path)
|
||||||
|
{
|
||||||
|
Stream strm=Stream.Null;
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
strm=await e.OpenOrCreateAsync(path);
|
||||||
|
});
|
||||||
|
return strm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenameFile(string src, string dest)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.RenameFile(src,dest);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> CreateAsync(string path)
|
||||||
|
{
|
||||||
|
Stream strm=Stream.Null;
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
strm=await e.CreateAsync(path);
|
||||||
|
});
|
||||||
|
return strm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDirectory(string path)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.CreateDirectory(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveDirectory(string src, string dest)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{e.MoveDirectory(src,dest);});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFile(string file)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{e.DeleteFile(file);});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteDirectory(string dir, bool recursive = false)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{e.DeleteDirectory(dir,recursive);});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> OpenReadAsync(string path)
|
||||||
|
{
|
||||||
|
return await Storage.OpenReadAsync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> FileExistsAsync(string path)
|
||||||
|
{
|
||||||
|
return await Storage.FileExistsAsync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DirectoryExistsAsync(string path)
|
||||||
|
{
|
||||||
|
return await Storage.DirectoryExistsAsync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> EnumerateFilesAsync(string path)
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.EnumerateFilesAsync(path))
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path)
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.EnumerateDirectoriesAsync(path))
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteAllTextAsync(string path, string data)
|
||||||
|
{
|
||||||
|
await StorageAsWritableAsync(async(e)=>{await e.WriteAllTextAsync(path,data);});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> MuxVideosAsync(SavedVideo video, string videoSrc, string audioSrc, string videoDest, IProgress<double> progress = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
bool res=false;
|
||||||
|
await StorageAsStorageAsync(async (e)=>{
|
||||||
|
res=await e.MuxVideosAsync(video,videoSrc,audioSrc,videoDest,progress,token);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Continue(string path)
|
||||||
|
{
|
||||||
|
bool res=false;
|
||||||
|
await StorageAsStorageAsync(async (e)=>{
|
||||||
|
res=await e.Continue(path);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteVideoInfoAsync(SavedVideo channel)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async (e)=>{
|
||||||
|
await e.WriteVideoInfoAsync(channel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WritePlaylistInfoAsync(SavedPlaylist channel)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async (e)=>{
|
||||||
|
await e.WritePlaylistInfoAsync(channel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteChannelInfoAsync(SavedChannel channel)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async (e)=>{
|
||||||
|
await e.WriteChannelInfoAsync(channel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDirectoryIfNotExist(string path)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.CreateDirectoryIfNotExist(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logger GetLogger()
|
||||||
|
{
|
||||||
|
Logger logger=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
logger=e.GetLogger();
|
||||||
|
});
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoggerProperties GetLoggerProperties()
|
||||||
|
{
|
||||||
|
LoggerProperties properties=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
properties=e.GetLoggerProperties();
|
||||||
|
});
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadVideoOnlyAsync(SavedVideo video, CancellationToken token, IProgress<double> progress, bool report = true)
|
||||||
|
{
|
||||||
|
bool res=false;
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
res= await e.DownloadVideoOnlyAsync(video,token,progress,report);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MoveLegacyStreams(SavedVideo video, BestStreams streams)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.MoveLegacyStreams(video,streams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<(VideoId Id, Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name)
|
||||||
|
{
|
||||||
|
IAsyncEnumerable<(VideoId Id,Resolution Resolution)> items=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
items=e.GetPersonalPlaylistContentsAsync(name);
|
||||||
|
|
||||||
|
});
|
||||||
|
if(items == null) yield break;
|
||||||
|
await foreach(var item in items)
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
|
{
|
||||||
|
await Downloader.AddVideoAsync(id,resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
|
{
|
||||||
|
await Downloader.AddPlaylistAsync(id,resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
|
{
|
||||||
|
await Downloader.AddChannelAsync(id,resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
|
||||||
|
{
|
||||||
|
await Downloader.AddUserAsync(userName,resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
|
||||||
|
{
|
||||||
|
return Downloader.GetQueueList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SavedVideoProgress GetProgress()
|
||||||
|
{
|
||||||
|
return Downloader.GetProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<Subscription> GetSubscriptionsAsync()
|
||||||
|
{
|
||||||
|
IAsyncEnumerable<Subscription> subscriptions=null;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
subscriptions= e.GetSubscriptionsAsync();
|
||||||
|
});
|
||||||
|
if(subscriptions == null) yield break;
|
||||||
|
await foreach(var item in subscriptions)
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UnsubscribeAsync(ChannelId id)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.UnsubscribeAsync(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SubscribeAsync(ChannelId id, bool downloadChannelInfo = false, ChannelBellInfo bellInfo = ChannelBellInfo.NotifyAndDownload)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.SubscribeAsync(id,downloadChannelInfo,bellInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SubscribeAsync(UserName name, ChannelBellInfo info = ChannelBellInfo.NotifyAndDownload)
|
||||||
|
{
|
||||||
|
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.SubscribeAsync(name,info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResubscribeAsync(ChannelId id, ChannelBellInfo info = ChannelBellInfo.NotifyAndDownload)
|
||||||
|
{
|
||||||
|
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.ResubscribeAsync(id,info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> GetPersonalPlaylistsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetPersonalPlaylistsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(string Path, bool Delete)> GetRealUrlOrPathAsync(string path)
|
||||||
|
{
|
||||||
|
return await Storage.GetRealUrlOrPathAsync(path);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetLengthAsync(string path)
|
||||||
|
{
|
||||||
|
return await Storage.GetLengthAsync(path);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FileExists(string path)
|
||||||
|
{
|
||||||
|
return Storage.FileExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> GetVideoIdsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var id in Storage.GetVideoIdsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SavedVideo> GetVideoInfoAsync(VideoId id)
|
||||||
|
{
|
||||||
|
return await Storage.GetVideoInfoAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<SavedVideo> GetVideosAsync()
|
||||||
|
{
|
||||||
|
await foreach(var vid in Storage.GetVideosAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(vid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetLegacyVideosAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id)
|
||||||
|
{
|
||||||
|
return await Storage.GetLegacyVideoInfoAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetPlaylistsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return await Storage.ReadAllBytesAsync(path,token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetPlaylistIdsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<string> GetChannelIdsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetChannelIdsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetYouTubeExplodeVideoIdsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
||||||
|
{
|
||||||
|
return await Storage.GetChannelInfoAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in Storage.GetChannelsAsync())
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PlaylistInfoExists(PlaylistId id)
|
||||||
|
{
|
||||||
|
return Storage.PlaylistInfoExists(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool VideoInfoExists(VideoId id)
|
||||||
|
{
|
||||||
|
return Storage.VideoInfoExists(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChannelInfoExists(ChannelId id)
|
||||||
|
{
|
||||||
|
return Storage.ChannelInfoExists(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
|
||||||
|
{
|
||||||
|
return await Storage.GetPlaylistInfoAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> ReadAllTextAsync(string file)
|
||||||
|
{
|
||||||
|
return Storage.ReadAllTextAsync(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DirectoryExists(string path)
|
||||||
|
{
|
||||||
|
return Storage.DirectoryExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> EnumerateFiles(string path)
|
||||||
|
{
|
||||||
|
return Storage.EnumerateFiles(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> EnumerateDirectories(string path)
|
||||||
|
{
|
||||||
|
return Storage.EnumerateDirectories(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> OpenReadAsyncWithLength(string path)
|
||||||
|
{
|
||||||
|
return await Storage.OpenReadAsyncWithLength(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<Subscription> GetLoadedSubscriptions()
|
||||||
|
{
|
||||||
|
IReadOnlyList<Subscription> subs=new List<Subscription>();
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
subs=e.GetLoadedSubscriptions();
|
||||||
|
});
|
||||||
|
return subs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unsubscribe(ChannelId id)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.Unsubscribe(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloaderMigration
|
||||||
|
{
|
||||||
|
private TYTDDownloaderStorageProxy proxy;
|
||||||
|
|
||||||
|
public DownloaderMigration(TYTDDownloaderStorageProxy proxy)
|
||||||
|
{
|
||||||
|
this.proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloaderAsBaseAsync(Func<ITYTDBase,Task> callback)
|
||||||
|
{
|
||||||
|
var dl = proxy.Downloader as ITYTDBase;
|
||||||
|
if(dl != null && callback !=null) await callback(dl);
|
||||||
|
}
|
||||||
|
public void DownloaderAsBase(Action<ITYTDBase> callback)
|
||||||
|
{
|
||||||
|
var dl = proxy.Downloader as ITYTDBase;
|
||||||
|
if(dl != null && callback !=null) callback(dl);
|
||||||
|
}
|
||||||
|
public async Task CopyVideoAsync(VideoId id,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyVideoAsync(id,e,f,res,progress,token,copyThumbnails);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task CopyAllVideosAsync(Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyAllVideosAsync(e,f,res,progress,token,copyThumbnails);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
public async Task CopyPlaylistAsync(PlaylistId id,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=true)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyPlaylistAsync(id,e,f,res,progress,token,copyThumbnails,copyContents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task CopyChannelAsync(ChannelId id,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=false)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyChannelAsync(id,e,f,res,progress,token,copyThumbnails,copyContents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task CopyAllPlaylistsAsync(Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=true)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyAllPlaylistsAsync(e,f,res,progress,token,copyThumbnails,copyContents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task CopyAllChannelsAsync(Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true,bool copyContents=false)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyAllChannelsAsync(e,f,res,progress,token,copyThumbnails,copyContents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task CopyEverythingAsync(Resolution res,IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyEverythingAsync(e,f,res,progress,token,copyThumbnails);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async Task CopyEverythingAsync(IProgress<double> progress=null,CancellationToken token=default(CancellationToken),bool copyThumbnails=true)
|
||||||
|
{
|
||||||
|
await DownloaderAsBaseAsync(async(e)=>{
|
||||||
|
await proxy.StorageAsStorageAsync(async(f)=>{
|
||||||
|
await TYTDManager.CopyEverythingAsync(e,f,progress,token,copyThumbnails);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader</PackageId>
|
<PackageId>Tesses.YouTubeDownloader</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.0.3.6</Version>
|
<Version>1.1.0</Version>
|
||||||
<AssemblyVersion>1.0.3.6</AssemblyVersion>
|
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||||
<FileVersion>1.0.3.6</FileVersion>
|
<FileVersion>1.1.0</FileVersion>
|
||||||
<Description>A YouTube Downloader</Description>
|
<Description>A YouTube Downloader</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tesses.YouTubeDownloader", "Tesses.YouTubeDownloader.csproj", "{77B23470-768E-4927-8F09-20357D2E8032}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{77B23470-768E-4927-8F09-20357D2E8032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{77B23470-768E-4927-8F09-20357D2E8032}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{77B23470-768E-4927-8F09-20357D2E8032}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{77B23470-768E-4927-8F09-20357D2E8032}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
Loading…
Reference in New Issue