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>();
|
||||
public List<IExtension> Extensions => extensions;
|
||||
|
||||
TYTDStorage Storage;
|
||||
IStorage Storage;
|
||||
MountableServer Server;
|
||||
string dir;
|
||||
/// <summary>
|
||||
|
@ -43,7 +43,7 @@ namespace Tesses.YouTubeDownloader.ExtensionLoader
|
|||
/// </summary>
|
||||
/// <param name="storage">Storage for TYTD</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);
|
||||
dir=lookInDir;
|
||||
|
@ -122,7 +122,7 @@ namespace Tesses.YouTubeDownloader.ExtensionLoader
|
|||
/// Use relative paths please
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public TYTDStorage Storage {get; internal set;}
|
||||
public IStorage Storage {get; internal set;}
|
||||
/// <summary>
|
||||
/// Get extension storage dir use "Storage" to actually access files
|
||||
/// The path config/apistore/{Name} is created if it doesnt exist
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<PackageId>Tesses.YouTubeDownloader.ExtensionLoader</PackageId>
|
||||
<Author>Mike Nolan</Author>
|
||||
<Company>Tesses</Company>
|
||||
<Version>1.0.0.0</Version>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<Version>1.1.0</Version>
|
||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||
<FileVersion>1.1.0</FileVersion>
|
||||
<Description>Load Extensions into TYTD (Not Tested)</Description>
|
||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||
<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
|
||||
{
|
||||
TYTDBase baseCtl;
|
||||
public ApiStorage(TYTDBase baseCtl)
|
||||
ITYTDBase baseCtl;
|
||||
public ApiStorage(ITYTDBase baseCtl)
|
||||
{
|
||||
this.baseCtl=baseCtl;
|
||||
|
||||
|
@ -341,7 +341,7 @@ namespace Tesses.YouTubeDownloader.Server
|
|||
}
|
||||
public async Task Subscriptions(ServerContext ctx)
|
||||
{
|
||||
TYTDStorage storage = Downloader as TYTDStorage;
|
||||
IStorage storage = Downloader as IStorage;
|
||||
if(storage != null)
|
||||
{
|
||||
|
||||
|
@ -355,7 +355,7 @@ namespace Tesses.YouTubeDownloader.Server
|
|||
}
|
||||
public async Task Resubscribe(ServerContext ctx)
|
||||
{
|
||||
TYTDStorage storage = Downloader as TYTDStorage;
|
||||
IStorage storage = Downloader as IStorage;
|
||||
if(storage != null)
|
||||
{
|
||||
string id;
|
||||
|
@ -389,7 +389,7 @@ namespace Tesses.YouTubeDownloader.Server
|
|||
|
||||
public async Task Unsubscribe(ServerContext ctx)
|
||||
{
|
||||
TYTDStorage storage = Downloader as TYTDStorage;
|
||||
IStorage storage = Downloader as IStorage;
|
||||
if(storage != null)
|
||||
{
|
||||
string id;
|
||||
|
@ -415,7 +415,7 @@ namespace Tesses.YouTubeDownloader.Server
|
|||
}
|
||||
public async Task Subscribe(ServerContext ctx)
|
||||
{
|
||||
TYTDStorage storage = Downloader as TYTDStorage;
|
||||
IStorage storage = Downloader as IStorage;
|
||||
if(storage != null)
|
||||
{
|
||||
string id;
|
||||
|
@ -595,7 +595,7 @@ namespace Tesses.YouTubeDownloader.Server
|
|||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="baseCtl">TYTD context</param>
|
||||
public TYTDServer(TYTDBase baseCtl)
|
||||
public TYTDServer(ITYTDBase baseCtl)
|
||||
{
|
||||
ExtensionsServer=new ChangeableServer();
|
||||
RootServer=new ChangeableServer();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Tesses.WebServer" Version="1.0.1" />
|
||||
<PackageReference Include="Tesses.WebServer" Version="1.0.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
@ -15,9 +15,9 @@
|
|||
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
||||
<Author>Mike Nolan</Author>
|
||||
<Company>Tesses</Company>
|
||||
<Version>1.0.2.2</Version>
|
||||
<AssemblyVersion>1.0.2.2</AssemblyVersion>
|
||||
<FileVersion>1.0.2.2</FileVersion>
|
||||
<Version>1.1.0</Version>
|
||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||
<FileVersion>1.1.0</FileVersion>
|
||||
<Description>Adds WebServer to TYTD</Description>
|
||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||
<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 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)
|
||||
{
|
||||
|
@ -45,7 +45,7 @@ namespace Tesses.YouTubeDownloader
|
|||
|
||||
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");
|
||||
if(storage.DirectoryExists("StreamInfo"))
|
||||
|
@ -66,7 +66,7 @@ namespace Tesses.YouTubeDownloader
|
|||
}
|
||||
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"))
|
||||
{
|
||||
|
|
|
@ -44,6 +44,7 @@ namespace Tesses.YouTubeDownloader
|
|||
public readonly SavedVideoProgress Progress = new SavedVideoProgress();
|
||||
private async Task ReportProgress(double progress)
|
||||
{
|
||||
|
||||
Progress.Progress = (int)(progress * 100);
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -271,6 +271,19 @@ namespace Tesses.YouTubeDownloader
|
|||
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)
|
||||
{
|
||||
if(Videos !=null)
|
||||
|
|
|
@ -9,10 +9,11 @@ using System.Net.Http;
|
|||
using System.IO;
|
||||
using YoutubeExplode.Playlists;
|
||||
using YoutubeExplode.Channels;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
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();
|
||||
public abstract Task<Stream> CreateAsync(string path);
|
||||
|
@ -43,14 +44,38 @@ namespace Tesses.YouTubeDownloader
|
|||
|
||||
bool can_download=true;
|
||||
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 DeleteFile(string file);
|
||||
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)
|
||||
{
|
||||
lock(Temporary)
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace Tesses.YouTubeDownloader
|
|||
baseStrm.Close();
|
||||
}
|
||||
}
|
||||
public abstract class TYTDBase
|
||||
public abstract class TYTDBase : ITYTDBase
|
||||
{
|
||||
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<SavedPlaylist>(await ReadAllTextAsync($"Playlist/{id}.json"));
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> ReadAllTextAsync(string file)
|
||||
{
|
||||
using(var s = await OpenReadAsync(file))
|
||||
|
@ -290,7 +309,15 @@ namespace Tesses.YouTubeDownloader
|
|||
/// </summary>
|
||||
/// <param name="url">Video, Playlist, Channel Or UserName Url Or Id</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)
|
||||
{
|
||||
|
||||
|
@ -388,10 +415,19 @@ namespace Tesses.YouTubeDownloader
|
|||
{
|
||||
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 path=$"{resDir}/{id.Value}.mp4";
|
||||
string infoFile=$"Info/{id.Value}.json";
|
||||
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;
|
||||
double len=await src.GetLengthAsync(path);
|
||||
if(await src.FileExistsAsync(path))
|
||||
|
@ -410,7 +446,7 @@ namespace Tesses.YouTubeDownloader
|
|||
}
|
||||
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;
|
||||
using(var f = File.Create(dest))
|
||||
|
@ -457,20 +493,238 @@ namespace Tesses.YouTubeDownloader
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task CopyVideoAsync(VideoId id,TYTDBase src,TYTDStorage dest,Resolution res=Resolution.PreMuxed,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||
public static async Task CopyThumbnailsAsync(string id,ITYTDBase src,IStorage dest,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(!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))
|
||||
{
|
||||
|
||||
if(await src.FileExistsAsync(infoFile))
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -520,9 +774,13 @@ namespace Tesses.YouTubeDownloader
|
|||
return $"[{Offset}-{Offset.Add(Length)}] {ChapterName}";
|
||||
}
|
||||
}
|
||||
public interface IWritable
|
||||
public interface IPersonalPlaylistGet
|
||||
{
|
||||
public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name);
|
||||
public Task WriteAllTextAsync(string path,string data);
|
||||
public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name);
|
||||
|
||||
}
|
||||
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>
|
||||
<Author>Mike Nolan</Author>
|
||||
<Company>Tesses</Company>
|
||||
<Version>1.0.3.6</Version>
|
||||
<AssemblyVersion>1.0.3.6</AssemblyVersion>
|
||||
<FileVersion>1.0.3.6</FileVersion>
|
||||
<Version>1.1.0</Version>
|
||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||
<FileVersion>1.1.0</FileVersion>
|
||||
<Description>A YouTube Downloader</Description>
|
||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||
<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