Added Storage Proxy (Hopefully dont break software)

This commit is contained in:
Mike Nolan 2022-06-14 13:21:36 -05:00
parent 1d1accb4d0
commit 0a67162acb
22 changed files with 2237 additions and 50 deletions

View File

@ -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

View File

@ -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>

View File

@ -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.

View File

@ -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

View File

@ -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`
}

View File

@ -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")
})

View File

@ -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();
}
});
}
}

View File

@ -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>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
<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 &bull; <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>

View File

@ -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();

View File

@ -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>

View File

@ -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>

View File

@ -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"))
{ {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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)
{ {

View File

@ -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)

View File

@ -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);
} }
} }

View File

@ -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);
});
});
}
}
}

View File

@ -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>

View File

@ -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