Compare commits

...

23 Commits

Author SHA1 Message Date
a902ef7e5d Maybe not be a redirect in animeflv.cc 2021-04-07 23:40:24 +02:00
sergiotarxz
10745de950
fix: missing package.json. 2021-03-31 18:43:54 +02:00
sergiotarxz
8d44c9f21e
Adding aprils fools theme. 2021-03-31 18:39:52 +02:00
sergiotarxz
bd2b1094e0 Merge pull request 'tests: Adding tests to check youtube keeps the current compability.' (#25) from test/adding_youtube_integration_test into master
Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/pulls/25
2021-01-23 18:04:15 +01:00
sergiotarxz
03c8e35f88 Merge branch 'master' into test/adding_youtube_integration_test 2021-01-23 18:03:47 +01:00
sergiotarxz
f09440cb45
tests: Adding tests to check youtube keeps the current compability. 2021-01-23 17:49:33 +01:00
sergiotarxz
c5cdc61bb4 Merge pull request 'feature: Adding a open-rc initscript.' (#24) from feature/adding_example_gentoo_init_script into master
Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/pulls/24
2021-01-23 16:55:13 +01:00
sergiotarxz
cb04211505
feature: Adding a open-rc initscript. 2021-01-23 16:51:18 +01:00
sergiotarxz
272c8812c3
fix: Bad width again. 2021-01-22 14:47:14 +01:00
sergiotarxz
b72a4c40df
fix: Solving text overflow on format selector. 2021-01-22 14:41:53 +01:00
sergiotarxz
7e14c2aa4b
fix: Solving cache. 2021-01-22 14:36:40 +01:00
sergiotarxz
ef7cb4b628
fix: Fixing poping notice. 2021-01-22 14:30:34 +01:00
sergiotarxz
8ce749fd98
fix: Trying poping notice to act better on phone. 2021-01-22 14:09:31 +01:00
sergiotarxz
304d8c2e8d
fix: Avoiding caching. 2021-01-22 13:51:41 +01:00
sergiotarxz
7feda0e461
fix: Solving some issues on phones. 2021-01-22 13:47:00 +01:00
sergiotarxz
b3e47dc7d0
Adding Abnazhor to AUTHORS 2021-01-22 01:26:21 +01:00
sergiotarxz
f788eae6e0 Merge pull request 'feature/new_look_default_theme' (#22) from feature/new_look_default_theme into master
Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/pulls/22
2021-01-22 01:25:08 +01:00
sergiotarxz
27cfeed687
feat: Improving the support of the format list. 2021-01-22 01:19:18 +01:00
sergiotarxz
0014816375
feat: Adding ogg tags. 2021-01-21 23:44:14 +01:00
sergiotarxz
a7925763a8
fix: Solving the video still playing on close window. 2021-01-21 23:28:46 +01:00
sergiotarxz
9795d5856e
feat: Spitting on a new theme while the Jorge theme goes stable and solving some errors. 2021-01-21 23:18:21 +01:00
sergiotarxz
93e32bd0a0
fix: Making poping notice more responsive. 2021-01-20 20:17:47 +01:00
sergiotarxz
5228ddfe6b Merge pull request 'New CSS template for default look' (#21) from abnazhor/Peertube-dl:master into feature/new_look_default_theme
I am merging the actual changes to allow me editing it on feature/new_look_default_theme, if you want to further contribute in this changes use that branch for your pull request.

Reviewed-on: https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/pulls/21
2021-01-20 20:09:47 +01:00
36 changed files with 2402 additions and 159 deletions

View File

@ -1,4 +1,4 @@
@sergiotarxz:sergiotarxz.hopto.org (Matrix address.)
@ale@ale.manalejandro.com/@ale@mastodon.madrid (Fediverse accounts.)
@destinyuwu:sergiotarxz.hopto.org (Matrix address.)
@abnazhor (Telegram.)

7
META.json Normal file
View File

@ -0,0 +1,7 @@
{
"version" : "0.0.1",
"name" : "Peertube-DL",
"author" : "sergiotarxz",
"abstract" : "A module for downloading videos from various webpages",
"license" : "AGPLv3"
}

View File

@ -43,6 +43,9 @@ if ( defined $download_data->{options}{list} && $download_data->{options}{list}
say "The urls are:\n" . join "\n", @$urls;
}
} elsif ( defined $download_data->{options}{list_formats} && $download_data->{options}{list_formats} ) {
say 'Listing formats available.';
delete $download_data->{options};
say "The available formats are: @{[Data::Dumper::Dumper $download_data]}";
exit 0;
} else {
downloadVideo( $ua, $download_data );

View File

@ -0,0 +1,24 @@
#!/sbin/openrc-run
# Requires OpenRC >= 0.35
directory=/var/lib/peertubedl
PATH="/var/lib/peertubedl/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/var/lib/peertubedl/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/var/lib/peertubedl/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/var/lib/peertubedl/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/var/lib/peertubedl/perl5"; export PERL_MM_OPT;
command=/var/lib/peertubedl/perl5/bin/peertube-dl-hypnotoad
command_args=""
command_user=peertubedl:peertubedl
pidfile="/var/lib/peertubedl/peertube-dl-web.pid"
export PIDFILE=$pidfile
depend() {
need postgresql
}

View File

@ -298,7 +298,10 @@ sub animeid {
filename => $filename,
};
} else {
die 'Getting redirect failed because: ' . $video_response->status_line;
return {
url => $download_redirect_url,
filename => $filename,
}
}
}
1;

View File

@ -67,9 +67,6 @@ sub getDownloadDataFromURL {
die "No description." unless defined $download_data->{description};
die "No formats available." unless defined $download_data->{formats};
die "Formats is not a hash." unless ref $download_data->{formats} eq 'HASH';
say "The video title is $download_data->{title}.";
say "The video description is $download_data->{description}.";
say "The available formats are: @{[Data::Dumper::Dumper $download_data->{formats}]}.";
return $download_data;
}
}

View File

@ -5,5 +5,6 @@
clients => 3,
proxy => 1,
pid_file => $ENV{PIDFILE} || '/var/run/peertube-dl-web.pid',
}
},
theme => 'new_look_default',
};

321
t-download/00-youtube.com.t Normal file
View File

@ -0,0 +1,321 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::Most tests => 5;
use Digest::SHA qw/sha512_hex/;
use Data::Dumper;
BEGIN {
use_ok 'Peertube::DL::URLHandler';
}
## Tests WITH signatures.
{
my $download_data = Peertube::DL::URLHandler::getDownloadDataFromURL(
'https://www.youtube.com/watch?v=nqc4t4029as');
my $download_data_expected = {
'formats' => {
'audio_formats' => [
{
'mimeType' => 'audio/webm; codecs="opus"',
'quality' => 'tiny',
'audioSampleRate' => 48000,
'bitrate' => 133750,
'id' => 251
},
{
'bitrate' => 67550,
'id' => 250,
'mimeType' => 'audio/webm; codecs="opus"',
'quality' => 'tiny',
'audioSampleRate' => 48000
},
{
'bitrate' => 51104,
'id' => 249,
'mimeType' => 'audio/webm; codecs="opus"',
'audioSampleRate' => 48000,
'quality' => 'tiny'
}
],
'video_formats' => [
{
'qualityLabel' => 1080,
'mimeType' => 'video/mp4; codecs="avc1.640028"',
'quality' => 'hd1080',
'id' => 137,
'bitrate' => 1610654
},
{
'quality' => 'hd1080',
'mimeType' => 'video/webm; codecs="vp9"',
'qualityLabel' => 1080,
'bitrate' => 1552727,
'id' => 248
},
{
'bitrate' => 1074995,
'id' => 399,
'mimeType' => 'video/mp4; codecs="av01.0.08M.08"',
'qualityLabel' => 1080,
'quality' => 'hd1080'
},
{
'id' => 247,
'bitrate' => 687059,
'quality' => 'hd720',
'mimeType' => 'video/webm; codecs="vp9"',
'qualityLabel' => 720
},
{
'quality' => 'hd720',
'qualityLabel' => 720,
'mimeType' => 'video/mp4; codecs="av01.0.05M.08"',
'id' => 398,
'bitrate' => 659074
},
{
'mimeType' => 'video/mp4; codecs="avc1.4d401f"',
'qualityLabel' => 720,
'quality' => 'hd720',
'id' => 136,
'bitrate' => 579992
},
{
'id' => 244,
'bitrate' => 421009,
'qualityLabel' => 480,
'mimeType' => 'video/webm; codecs="vp9"',
'quality' => 'large'
},
{
'quality' => 'large',
'mimeType' => 'video/mp4; codecs="av01.0.04M.08"',
'qualityLabel' => 480,
'id' => 397,
'bitrate' => 385094
},
{
'mimeType' => 'video/mp4; codecs="avc1.4d401e"',
'qualityLabel' => 480,
'quality' => 'large',
'id' => 135,
'bitrate' => 353336
},
{
'bitrate' => 645178,
'id' => 18,
'mimeType' => 'video/mp4; codecs="avc1.42001E, mp4a.40.2"',
'quality' => 'medium',
'qualityLabel' => 360,
'audioSampleRate' => '44100'
},
{
'quality' => 'medium',
'qualityLabel' => 360,
'mimeType' => 'video/webm; codecs="vp9"',
'bitrate' => 281570,
'id' => 243
},
{
'id' => 134,
'bitrate' => 241943,
'quality' => 'medium',
'qualityLabel' => 360,
'mimeType' => 'video/mp4; codecs="avc1.4d401e"'
},
{
'bitrate' => 234609,
'id' => 396,
'quality' => 'medium',
'qualityLabel' => 360,
'mimeType' => 'video/mp4; codecs="av01.0.01M.08"'
},
{
'mimeType' => 'video/webm; codecs="vp9"',
'qualityLabel' => 240,
'quality' => 'small',
'id' => 242,
'bitrate' => 160233
},
{
'qualityLabel' => 240,
'mimeType' => 'video/mp4; codecs="avc1.4d4015"',
'quality' => 'small',
'bitrate' => 130312,
'id' => 133
},
{
'id' => 395,
'bitrate' => 121695,
'mimeType' => 'video/mp4; codecs="av01.0.00M.08"',
'qualityLabel' => 240,
'quality' => 'small'
},
{
'id' => 278,
'bitrate' => 86048,
'quality' => 'tiny',
'qualityLabel' => 144,
'mimeType' => 'video/webm; codecs="vp9"'
},
{
'bitrate' => 77340,
'id' => 160,
'qualityLabel' => 144,
'mimeType' => 'video/mp4; codecs="avc1.4d400c"',
'quality' => 'tiny'
},
{
'mimeType' => 'video/mp4; codecs="av01.0.00M.08"',
'qualityLabel' => 144,
'quality' => 'tiny',
'bitrate' => 63369,
'id' => 394
}
]
},
'options' => { 'list_formats' => 1 },
'description' =>
"Tras el \x{e9}xito de Zeus, Hades, Poseid\x{f3}n y Dem\x{e9}ter, hoy os traemos la canci\x{f3}n de otra de sus hermanas, \x{a1}Hestia! \x{bf}A qu\x{e9} esperas para descubrir la historia de esta diosa de la mitolog\x{ed}a griega en un nuevo Destripando la Historia?
\x{a1}Suscr\x{ed}bete a nuestro canal!
http://bit.ly/pascuyrodri
\x{a1}Escucha esta y otras canciones en SPOTIFY!
https://spoti.fi/2SY6zni
\x{a1}Consigue nuestras camisetas, chapas y l\x{e1}minas!
https://www.destripandolahistoria.es
*Solo con env\x{ed}o a Espa\x{f1}a peninsular.
\x{a1}Tambi\x{e9}n ten\x{e9}is parte de nuestro merchandising en Teespring!
(Con env\x{ed}os internacionales)
https://www.teespring.com/stores/destripandolahistoria
\x{a1}Puedes seguir a Destripando la Historia en Facebook, Instagram y Twitter! Publicamos contenido extra como dibujos, v\x{ed}deos cantando en directo o tonter\x{ed}as absurdas, ya nos conoc\x{e9}is...
http://www.instagram.com/destripandolahistoria
http://www.facebook.com/destripandolahistoria
https://twitter.com/pascuyrodri
\x{a1}Hola a todos!
\x{a1}La cuarentena no ha podido con nosotros y aqu\x{ed} ten\x{e9}is un nuevo v\x{ed}deo! Esperamos que est\x{e9}is todos bien y que lo disfrut\x{e9}is. Si hab\x{e9}is le\x{ed}do hasta aqu\x{ed}, como venganza porque Pascu ha metido una foto m\x{ed}a al principio del v\x{ed}deo, os pido que pong\x{e1}is en los comentarios: \x{201c}Pascu, baja la tapa del v\x{e1}ter\x{201d}.
Rodri
\x{a1}Recordad que pod\x{e9}is seguirnos tambi\x{e9}n en nuestras redes sociales personales!
http://www.instagram.com/rodrigoseptienmusic
https://www.instagram.com/alv_pascu/
https://twitter.com/var_Stark
https://twitter.com/RodrigoSeptien",
'title' => 'Hestia | Destripando la Historia'
};
is_deeply $download_data, $download_data_expected,
'Testing listing formats with obfuscated signatures.';
}
{
my $download_data = Peertube::DL::URLHandler::getDownloadDataFromURL(
'https://www.youtube.com/watch?v=nqc4t4029as', { format => 18 } );
my $ua = Peertube::DL::URLHandler::generateUA();
my $downloaded_video_checksum =
sha512_hex( $ua->get( $download_data->{url} )->decoded_content );
my $expected_checksum =
'a59f5f1e582bd558ef4e928d7edfd942452aff5084218a3470da946d73c94f28e102773d5b19f47c6224492a82e6c7072a47aae87333bcfeda457a33626c92e7';
is $downloaded_video_checksum, $expected_checksum,
'Testing downloading with obfuscated signatures.';
}
## WITHOUT signature.
{
my $expected_download_data = {
'description' =>
'Richard Stallman en Ecuador, cantando el temita, del free software, grabado por Julian Coccia.',
'formats' => {
'audio_formats' => [
{
'bitrate' => 139941,
'id' => 251,
'quality' => 'tiny',
'audioSampleRate' => 48000,
'mimeType' => 'audio/webm; codecs="opus"'
},
{
'bitrate' => 72446,
'id' => 250,
'audioSampleRate' => 48000,
'quality' => 'tiny',
'mimeType' => 'audio/webm; codecs="opus"'
},
{
'bitrate' => 55976,
'id' => 249,
'quality' => 'tiny',
'audioSampleRate' => 48000,
'mimeType' => 'audio/webm; codecs="opus"'
}
],
'video_formats' => [
{
'id' => 18,
'bitrate' => 367576,
'audioSampleRate' => '44100',
'quality' => 'small',
'mimeType' => 'video/mp4; codecs="avc1.42001E, mp4a.40.2"',
'qualityLabel' => 240
},
{
'bitrate' => 244690,
'id' => 133,
'quality' => 'small',
'qualityLabel' => 240,
'mimeType' => 'video/mp4; codecs="avc1.4d400d"'
},
{
'mimeType' => 'video/webm; codecs="vp9"',
'quality' => 'small',
'qualityLabel' => 240,
'id' => 242,
'bitrate' => 161442
},
{
'bitrate' => 112818,
'id' => 160,
'qualityLabel' => 144,
'quality' => 'tiny',
'mimeType' => 'video/mp4; codecs="avc1.4d400c"'
},
{
'mimeType' => 'video/webm; codecs="vp9"',
'quality' => 'tiny',
'qualityLabel' => 144,
'id' => 278,
'bitrate' => 70172
}
]
},
'title' => 'Richard Stallman Free software Song'
};
my $download_data = Peertube::DL::URLHandler::getDownloadDataFromURL(
'https://www.youtube.com/watch?v=9sJUDx7iEJw');
delete $download_data->{options};
is_deeply( $download_data, $expected_download_data,
'Testing listing signatures without obfuscated signatures.' );
}
{
my $download_data = Peertube::DL::URLHandler::getDownloadDataFromURL(
'https://www.youtube.com/watch?v=9sJUDx7iEJw', { format => 18 } );
my $ua = Peertube::DL::URLHandler::generateUA();
my $downloaded_video_checksum =
sha512_hex( $ua->get( $download_data->{url} )->decoded_content );
my $expected_checksum =
'16343940a5473a2ac3f485d11b1e920c974a4f506859368f669ad8af4a30b885b41c8ba301364997fc2ae50bb41d9d167768bcd9bfb4a51d76767ec165b744da';
is $downloaded_video_checksum, $expected_checksum,
'Testing downloading without obfuscated signatures.';
}

View File

@ -1,189 +1,276 @@
body {
height: 98vh;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
background-color: #111827;
height: 99.9%;
margin: 0;
padding: 0;
}
.application-container {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
border-radius: 0.3rem;
background-color: #1f2937;
padding: 1.5rem 3rem 1.5rem 3rem;
a {
color: blue;
}
.application-container form {
display: flex;
flex-flow: column;
justify-content: center;
width: 100%;
padding: 1rem;
a:hover,a:focus {
text-decoration: underline;
}
.application-container h2 {
color: #ffffff;
font-weight: 400;
font-size: 1.6rem;
}
.application-container input {
padding: 1rem;
color: #ffffff;
background-color: #374151;
border-radius: 0.2rem;
border: 1px solid transparent;
}
.application-container button {
margin-top: 1rem;
color: #ffffff;
font-weight: bold;
background-color: #059669;
border-radius: 0.2rem;
border: 1px solid transparent;
padding: 0.5rem 1rem 0.5rem 1rem;
}
.application-container button,
.application-container input {
font-size: 0.9rem;
}
.application-container.active {
display: flex;
}
#poping-notice {
position: absolute;
display: none;
width: 40%;
padding: 3rem;
border-radius: 0.3rem;
background-color: #374151;
color: white;
}
#poping-notice-content a {
text-decoration: none;
color: #10b981;
}
#poping-notice-container-bar {
display: flex;
justify-content: center;
}
#close-poping-notice {
background-color: #059669;
padding: 0.5rem 5rem 0.5rem 5rem;
border-radius: 0.3rem;
text-decoration: none;
font-weight: bolder;
color: white;
#video {
width: 100%;
margin: 3px;
}
#modal-video-container {
position: absolute;
display: none;
display: none;
background: white;
position: fixed;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
transform: translate(-50%, -50%);
border: black 1px solid;
}
height: 100vh;
width: 100%;
flex-flow: column;
align-items: center;
background-color: #111827;
#modal-video-container.active {
display: block;
}
.video-container-bar {
width: 95%;
height: 2rem;
padding: 1rem;
display: flex;
justify-content: end;
display: flex;
justify-content: right;
}
.video-container-bar a {
padding: 1rem;
border-radius: 0.3rem;
display: flex;
align-items: center;
justify-content: center;
background-color: #dc2626;
#close-and-reset-video-container {
margin-top: 0.25rem;
margin-right: 0.25rem;
border: 1px solid black;
}
#modal-video-container > #block {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
#close-and-reset-video-container:hover:#close-and-reset-video-container:focus {
background: black;
color: white;
}
#download-video-container {
padding: 2rem;
display: flex;
align-items: center;
justify-content: center;
margin: 10px;
justify-content: center;
}
#download-video-prepare,
#download-video {
display: none;
border-radius: 0.3rem;
background-color: #059669;
text-decoration: none;
color: white;
user-select: none;
cursor: pointer;
#download-video-container a {
display: none;
padding-left: 5px;
padding-right: 5px;
padding-top: 5px;
padding-bottom: 5px;
width: 100%;
background: #0a0;
border-radius: 5px;
font-size: 30px;
color: white;
height: 30px;
}
#download-video-loading.active {
height: 2rem;
#download-video-container a.active {
display: block;
}
#download-video-container a.active,
#download-video-loading.active {
display: block;
padding: 0.5rem 2rem 0.5rem 2rem;
#download-video-container a embed {
height: 30px;
}
#modal-loading,
#modal-format-selector {
display: none;
#video-container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
}
#poping-notice.active,
#modal-video-container.active {
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
.block {
display: block;
}
.application-container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
height: 100%;
background: #000;
color: white;
}
#download-form {
display: flex;
justify-content: center;
flex-direction: column;
}
#download-form-button {
margin-top: 5px;
height: 50px;
font-size: 1.5rem;
background: #fff;
color: black;
border: none;
}
h2 {
font-size: 2rem;
}
#modal-loading {
display: none;
position: fixed;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
transform: translate(-50%, -50%);
border: black 1px solid;
justify-content: right;
align-items: center;
}
#modal-loading.active {
display: flex;
}
#modal-loading embed {
width: 10%;
}
#poping-notice {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: black 1px solid;
width: 91%;
background: white;
padding: 10px;
border-radius: 15px;
max-height: 95%;
overflow-y: scroll;
}
#poping-notice.active {
display: block;
display: block;
}
#modal-video-container.active {
display: flex;
}
#poping-notice-container-bar {
display: flex;
justify-content: center;
font-size: 5rem;
}
#close-poping-notice {
width: 150px;
height: 150px;
align-items: center;
display: flex;
text-align: center;
justify-content: center;
border-radius: 50%;
background: #0f0;
color: black;
text-decoration: none;
}
#close-poping-notice:hover,#close-poping-notice:focus {
background: black;
color: white;
}
#modal-format-selector {
display: none;
background: white;
position: fixed;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
transform: translate(-50%, -50%);
border: black 1px solid;
flex-direction: column;
overflow-y: scroll;
}
#modal-format-selector.active {
display: flex;
}
#modal-format-selector > h2 {
text-align: center;
width: 100%;
}
#modal-format-selector > p {
margin-left: 2rem;
width: 100%;
}
#modal-format-selector .format-list {
box-sizing: border-box;
background: #fff;
margin: 2rem;
}
#close-modal-format-selector {
margin-top: 0.50rem;
margin-right: 0.50rem;
border: 1px solid black;
background: grey;
color: white;
width: 25px;
height: 25px;
text-align: center;
font-size: 20px;
font-weight: bold;
}
#close-modal-format-selector:hover,#close-modal-format-selector:focus {
background: black;
}
.format-list > div {
width: 100%;
display: grid;
grid-auto-columns: 50%;
grid-template-areas: "a a";
}
.format-list > div > a {
border: 1px solid black;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
padding-right: 5%;
padding-left: 5%;
text-decoration: none;
color: black;
background: #eee;
overflow-wrap: anywhere;
}
.format-list > div > a:hover {
background: black;
color: white;
}
.format-list > div > a:after {
padding-bottom: 100%;
display: block;
content: "";
}
div.video-formats a {
background: #f00;
}
@media (min-width: 668px) {
#poping-notice {
width: 629px;
}
}

View File

@ -0,0 +1,327 @@
body {
height: 98vh;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
background-color: #FF69B4;
}
.application-container {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
border-radius: 0.3rem;
background-color: #C71585;
padding: 1.5rem 3rem 1.5rem 3rem;
}
.application-container form {
display: flex;
flex-flow: column;
justify-content: center;
width: 100%;
padding: 1rem;
}
.application-container h2 {
color: #ffffff;
font-weight: 400;
font-size: 1.6rem;
}
.application-container input {
padding: 1rem;
color: #C71585;
background-color: #FFB6C1;
border-radius: 0.2rem;
border: 1px solid transparent;
}
.application-container button {
margin-top: 1rem;
color: #ffffff;
font-weight: bold;
background-color: #059669;
border-radius: 0.2rem;
border: 1px solid transparent;
padding: 0.5rem 1rem 0.5rem 1rem;
}
.application-container button:hover,.application-container button:focus {
background-color: #059;
}
.application-container button,
.application-container input {
font-size: 0.9rem;
}
.application-container.active {
display: flex;
}
#poping-notice-content a {
text-decoration: none;
color: lime;
}
#poping-notice-content a:hover,#poping-notice-content a:focus {
color: aqua;
}
#poping-notice-container-bar {
display: flex;
justify-content: center;
}
#close-poping-notice {
background-color: #059669;
padding: 0.5rem 5rem 0.5rem 5rem;
border-radius: 0.3rem;
text-decoration: none;
font-weight: bolder;
color: white;
}
#close-poping-notice:hover,#close-poping-notice:focus {
background-color: #059;
}
.modal {
position: fixed;
display: none;
color: white;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#poping-notice {
padding: 3rem;
border-radius: 0.3rem;
background-color: #C71585;
color: white;
overflow-y: scroll;
width: 94%;
max-height: 70%;
flex-direction: column;
scrollbar-width: none;
}
#modal-video-container,#modal-format-selector {
height: 100%;
width: 100%;
flex-flow: column;
align-items: center;
background-color: #FF69B4;
}
#modal-format-selector h2 {
width: 100%;
}
#modal-format-selector p {
width: 100%;
}
.video-container-bar {
width: 99%;
height: 2rem;
padding-top: 1rem;
padding-bottom: 1rem;
display: flex;
justify-content: end;
margin-bottom: 1rem;
}
.video-container-bar a {
padding: 1rem;
margin-right: 1rem;
border-radius: 0.3rem;
display: flex;
align-items: center;
justify-content: center;
background-color: #dc2626;
}
.video-container-bar a:hover,.video-container-bar a:focus {
color: white;
background-color: grey;
}
#modal-video-container > #block {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
}
#download-video-container {
padding: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
.button-download {
display: none;
border-radius: 0.3rem;
background-color: #059669;
text-decoration: none;
color: white;
user-select: none;
cursor: pointer;
height: 30px;
padding: 0 2rem;
}
.button-download:hover,.button-download:focus {
background-color: #059;
}
.button-download embed {
height: 100%;
}
#download-video-loading.active {
height: 2rem;
}
#download-video-container .button-download.active {
display: flex;
align-items: center;
}
#modal-loading {
display: none;
}
.modal {
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
overflow-y: scroll;
}
#poping-notice.active {
display: flex;
}
.modal.active {
display: flex;
scrollbar-width: none;
}
.modal.active.active::-webkit-scrollbar {
display: none;
}
#modal-loading {
display: none;
position: fixed;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
transform: translate(-50%, -50%);
border: black 1px solid;
justify-content: right;
align-items: center;
}
#modal-loading.active {
display: flex;
}
#modal-loading embed {
width: 10%;
}
#video {
width: 100%;
max-width: 636px;
}
#video-container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.format-list > div {
display: grid;
grid-column-gap: 10px;
grid-row-gap: 10px;
grid-template-areas: "a";
grid-auto-columns: 99%;
margin: 1rem;
}
.format-list > div > a {
border: 1px solid black;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
padding-right: 5%;
padding-left: 5%;
text-decoration: none;
border-radius: 0.5rem;
background-color: #059669;
overflow-wrap: anywhere;
}
.format-list > div > a:focus,.format-list > div > a:hover {
background-color: #059;
}
.format-list > div > a:after {
padding-bottom: 100%;
display: block;
content: "";
}
.scale {
height: 1em;
width; 1em;
}
@media (min-width: 600px) {
.format-list > div {
grid-template-areas: "a a";
grid-auto-columns: 49.75%;
}
}
@media (min-width: 805px) {
.format-list > div {
grid-template-areas: "a a a";
grid-auto-columns: 33%;
}
}
@media (min-width: 812px) {
#poping-notice {
width: 40%;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,76 @@
<html>
<head>
<script src="js/peertube-dl-web.js?v=1"></script>
<link rel="stylesheet" href="css/index.css?v=4"></script>
<link rel="icon" type="image/png" href="img/peertube-dl-logo.png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Peertube-dl Web</title>
<meta name="description" content="Webpage intended to download videos from various webpages for fair use."/>
<meta property="og:type" content="website" />
<meta property="og:title" content="Peertube-dl Web"/>
<meta property="og:site_name" content="Peertube-dl"/>
<meta property="og:description" content="Webpage intended to download videos from various webpages for fair use."/>
<meta property="og:image" content="./img/peertube-dl-logo-fullsize.png"/>
</head>
<body>
<div class="application-container">
<h2>Peertube-dl Web Application</h2>
<form id="download-form">
<input class="block" type="text" id="download-form-url" placeholder="Introduce the url you want to download."/>
<button class="block" id="download-form-button" >Fetch from api</button>
</form>
</div>
<div id="modal-format-selector" class="modal">
<div class="video-container-bar">
<a id="close-modal-format-selector">x</a>
</div>
<h2>Example video</h2>
<p>Example description</p>
<div class="format-list">
<h3>Video Formats.</h3>
<div class="video-formats">
</div>
<h3>Audio Formats.</h3>
<div class="audio-formats">
</div>
</div>
</div>
<div id="modal-loading">
<embed src="img/spinner.svg"/>
</div>
<div id="modal-video-container" class="modal">
<div class="video-container-bar">
<a id="close-and-reset-video-container">x</a>
</div>
<div id="video-container">
<div class="block">
<video id="video" controls="controls"></video>
</div>
<div id="download-video-container" class="block">
<a id="download-video-prepare" class="button-download active">Prepare download</a>
<a id="download-video-loading" class="button-download"><embed src="img/spinner.svg"/></a>
<a id="download-video" class="button-download">Download</a>
</div>
</div>
</div>
<div id="poping-notice" class="modal">
<div id="poping-notice-content">
<p>This webpage is free as in freedom software, it is offered to you with the hope it will be useful, but without any warranty,
you can find the source code at <a href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl">my gitea</a> with docs to setup your own
webpage like this, this software is licensed under the AGPLv3 license which means you MUST convey the source code in a human readable form
if you distribute this software or use it as an service to users of service or distributees.<p>
<p>I hope that if you find a non supported url which should be supported, a bug, or a feature you would like this webpage to have you file an issue in
<a href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues">https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues</a>
to help this software improve since I find tracking users a pretty bad way to discover bugs and potential good features.</p>
<p>This webpage may load third party resources depending on the url you give to it which may put cookies in your browser, you are
encouraged to frecuently delete your browser cookies to avoid those third parties tracking you on internet, Firefox offers you an
option to delete cookies as soon as you close the browser which may be a good idea to enable in the orwellian internet of today.</p>
</div>
<div id="poping-notice-container-bar">
<a id="close-poping-notice" href="#">X</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,13 @@
{
"name": "default-theme",
"version": "1.0.0",
"private": true,
"license": "MIT",
"scripts": {
"build": "webpack"
},
"dependencies": {
"webpack": "^5.15.0",
"webpack-cli": "^4.3.1"
}
}

View File

@ -0,0 +1,106 @@
"use strict";
import { PopingNotice } from './view/poping_notice.js';
import { DownloadForm } from './view/download_form.js';
import { LoadingModal } from './view/loading_modal.js';
import { VideoContainer } from './view/video_container.js';
import { FormatSelector } from './view/format_selector.js';
class Application {
constructor() {
this.poping_notice = new PopingNotice();
this.download_form = new DownloadForm(this.onDownloadFormGot.bind(this));
this.loading_modal = new LoadingModal();
this.video_container = new VideoContainer();
this.format_selector = new FormatSelector();
}
init() {
this.popingNotice.setVisible(true);
}
onDownloadFormGot(url) {
this.dispatchURL(url);
}
dispatchURL(url, format) {
this.loadingModal.setVisible(true);
let error_str;
let success = this.queryAPI(url, format).then( (response) => {
if ( response.options !== undefined && response.options.list_formats !== undefined && response.options.list_formats ) {
if ( response.formats === undefined
|| response.formats.audio_formats === undefined
|| response.formats.video_formats === undefined ) {
throw 'Format object is not valid.';
}
this.formatSelector.prepareFormatSelector(
response.title, response.description,
response.formats.audio_formats, response.formats.video_formats,
( id ) => {
this.loadingModal.setVisible(true);
this.dispatchURL(url, id);
});
this.formatSelector.setVisible(true);
this.loadingModal.setVisible(false);
} else {
this.videoContainer.onCanPlay(this.onCanPlayVideoContainer.bind(this));
this.videoContainer.setURLVideo(response.url);
this.videoContainer.setFilename(response.filename);
}
}).catch( (error) => {
error_str = error.toString();
this.loadingModal.setVisible(false);
let input_url = document.createElement('a');
input_url.href = url;
input_url.innerText = url;
let issues_url = document.createElement('a');
issues_url.href = 'https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues';
issues_url.innerText = 'here';
this.popingNotice.setMessage( [ 'The url ', input_url, ' is not supported, the error was: ', error_str , ' if you think this is an error, report it ', issues_url, '.' ]);
this.popingNotice.setVisible(true);
});
}
onCanPlayVideoContainer() {
this.videoContainer.setVisible(true);
this.loadingModal.setVisible(false);
}
async queryAPI(url, format) {
let request = { url: url };
if (format !== undefined)
request.format = format;
const response = await fetch('/api', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request),
});
return response.json();
}
get formatSelector() {
return this.format_selector;
}
get videoContainer() {
return this.video_container;
}
get downloadForm() {
return this.download_form;
}
get popingNotice() {
return this.poping_notice;
}
get loadingModal() {
return this.loading_modal;
}
}
export { Application };

View File

@ -0,0 +1,10 @@
"use strict";
import { Application } from './application.js';
window.addEventListener('load', (event) => {
let application = new Application();
application.init();
});

View File

@ -0,0 +1,35 @@
"use strict";
class DownloadForm {
constructor(callback) {
this.query_selector = '#download-form';
this.callback = (event) => {
event.preventDefault();
callback(this.downloadFormUrl.value);
};
this.addEventListeners();
}
addEventListeners() {
this.downloadFormButton.addEventListener('click', this.callback);
this.element.addEventListener('submit', this.callback);
}
get downloadFormButton() {
return this.element.querySelector('#download-form-button');
}
get downloadFormUrl() {
return this.element.querySelector('#download-form-url');
}
get querySelector() {
return this.query_selector;
}
get element() {
return document.querySelector(this.querySelector);
}
}
export { DownloadForm };

View File

@ -0,0 +1,120 @@
"use strict";
class FormatSelector {
constructor() {
this.query_selector = '#modal-format-selector';
this.addEventListeners();
}
appendFormat(container, object, is_video, callback) {
let a = document.createElement('a');
let br = function() { return document.createElement('br') };
let inner_text = [];
let muted_video = false;
if ( is_video ) {
if ( object.audioSampleRate === undefined ) {
muted_video = true;
}
inner_text.push('Id: ' + object.id);
inner_text.push(br());
inner_text.push('Format: ' + object.mimeType);
inner_text.push(br());
inner_text.push('QualityLabel: ' + object.qualityLabel + "p");
inner_text.push(br());
inner_text.push('Bitrate: ' + object.bitrate);
inner_text.push(br());
inner_text.push( ( muted_video) ?
"No audio. " :
'AudioSampleRate: ' + object.audioSampleRate
);
} else {
inner_text.push('Id: ' + object.id + "\n");
inner_text.push(br());
inner_text.push('Format: ' + object.mimeType);
inner_text.push(br());
inner_text.push('AudioSampleRate: ' + object.audioSampleRate);
inner_text.push(br());
inner_text.push('Bitrate: ' + object.bitrate);
}
a.addEventListener( 'click', (event) => {
callback(object.id);
});
if (muted_video) {
let img_muted_video = document.createElement('img');
img_muted_video.classList.add('mute_img');
img_muted_video.classList.add('scale');
img_muted_video.src = 'img/audio_muted.svg';
inner_text.push(img_muted_video);
}
for (let text of inner_text) {
if (typeof text === "string"
|| text instanceof String) {
text = document.createTextNode(text);
a.appendChild(text);
} else if ( text instanceof Node) {
a.appendChild(text);
} else {
throw ('Text is not a instance of Node nor a String');
}
}
container.appendChild(a);
}
prepareFormatSelector(title, description, audio_formats, video_formats, callback) {
this.titleFormatSelector.innerText = title;
this.descriptionFormatSelector.innerText = description;
this.videoFormats.innerHTML = '';
this.audioFormats.innerHTML = '';
for ( let x of audio_formats) {
this.appendFormat(this.audioFormats, x, false, callback);
}
for ( let x of video_formats ) {
this.appendFormat(this.videoFormats, x, true, callback);
}
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.element.classList.remove('active');
}
}
addEventListeners() {
this.closeFormatSelector.addEventListener('click', (event) => { this.setVisible(false); });
}
get videoFormats() {
return this.element.querySelector('.video-formats');
}
get audioFormats() {
return this.element.querySelector('.audio-formats');
}
get titleFormatSelector() {
return this.element.querySelector('h2');
}
get descriptionFormatSelector() {
return this.element.querySelector('p');
}
get closeFormatSelector() {
return this.element.querySelector('#close-modal-format-selector');
}
get element() {
return document.querySelector(this.querySelector);
}
get querySelector() {
return this.query_selector;
}
}
export { FormatSelector };

View File

@ -0,0 +1,25 @@
"use strict";
class LoadingModal {
constructor() {
this.query_selector = '#modal-loading';
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.element.classList.remove('active');
}
}
get element() {
return document.querySelector(this.querySelector);
}
get querySelector() {
return this.query_selector;
}
}
export { LoadingModal };

View File

@ -0,0 +1,56 @@
"use strict";
class PopingNotice {
constructor() {
this.query_selector = '#poping-notice';
this.closePopingNotice.addEventListener('click', (event) => {
this.setVisible(false);
});
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.element.classList.remove('active');
}
}
setMessage(message) {
if (!message instanceof Array)
throw 'Message is not instance of Array.';
let p = document.createElement('p');
for (let node of message) {
if (typeof node === "string"
|| node instanceof String) {
node = document.createTextNode(node);
p.appendChild(node);
} else if ( node instanceof Node) {
p.appendChild(node);
} else {
throw ('Node is not a instance of Node nor a String');
}
}
this.popingNoticeContent.innerHTML = '';
this.popingNoticeContent.appendChild(p);
}
get querySelector() {
return this.query_selector;
}
get element() {
return document.querySelector(this.querySelector);
}
get popingNoticeContent() {
return this.element.querySelector('#poping-notice-content');
}
get closePopingNotice() {
return this.element.querySelector('#close-poping-notice');
}
}
export { PopingNotice };

View File

@ -0,0 +1,113 @@
"use strict";
class VideoContainer {
constructor() {
this.query_selector = '#modal-video-container';
this.addEventListeners();
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.video.pause();
this.element.classList.remove('active');
this.downloadVideoPrepare.classList.add('active');
this.downloadVideoLoading.classList.remove('active');
this.downloadVideo.classList.remove('active');
}
}
addEventListeners() {
this.downloadVideoPrepare.addEventListener('click', this.downloadPrepareHandler.bind(this));
this.closeAndResetVideoContainer.addEventListener('click', (event) => {
this.setVisible(false);
});
}
downloadPrepareHandler(event) {
this.downloadVideoPrepare.classList.remove('active');
this.downloadVideoLoading.classList.add('active');
this.generateBlobVideo(this.URLVideo).then( blob => {
this.downloadVideo.href = URL.createObjectURL(blob);
this.downloadVideo.download = this.filename;
this.downloadVideoLoading.classList.remove('active');
this.downloadVideo.classList.add('active');
});
}
async generateBlobVideo(url) {
const blob = await fetch(url, { mode: 'cors', })
.then(res => res.blob())
.catch( err => this.generateBlobVideoByProxy(url) );
return blob;
}
async generateBlobVideoByProxy(url) {
const blob = await fetch( '/proxy_to_get', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({url: url}),
}
).then(res => res.blob());
return blob;
}
onCanPlay(callback) {
video.addEventListener('canplay', (event) => {
callback();
});
}
setURLVideo(url) {
this.url_video = url;
video.src = url;
}
setFilename(filename) {
this.filename = filename;
}
get closeAndResetVideoContainer() {
return this.element.querySelector('#close-and-reset-video-container');
}
get downloadVideo() {
return this.element.querySelector('#download-video');
}
get URLVideo() {
return this.url_video;
}
get closeAndResetVideoContainer() {
return this.element.querySelector('#close-and-reset-video-container');
}
get downloadVideoLoading() {
return this.element.querySelector('#download-video-loading');
}
get downloadVideoPrepare() {
return this.element.querySelector('#download-video-prepare');
}
get video() {
return this.element.querySelector('#video');
}
get element() {
return document.querySelector(this.querySelector);
}
get querySelector() {
return this.query_selector;
}
}
export { VideoContainer };

View File

@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
entry: './src/index.js',
devtool: 'source-map',
output: {
filename: 'peertube-dl-web.js',
path: path.resolve(__dirname, 'dist/js'),
},
};

View File

@ -0,0 +1,327 @@
body {
height: 98vh;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
background-color: #111827;
}
.application-container {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
border-radius: 0.3rem;
background-color: #1f2937;
padding: 1.5rem 3rem 1.5rem 3rem;
}
.application-container form {
display: flex;
flex-flow: column;
justify-content: center;
width: 100%;
padding: 1rem;
}
.application-container h2 {
color: #ffffff;
font-weight: 400;
font-size: 1.6rem;
}
.application-container input {
padding: 1rem;
color: #ffffff;
background-color: #374151;
border-radius: 0.2rem;
border: 1px solid transparent;
}
.application-container button {
margin-top: 1rem;
color: #ffffff;
font-weight: bold;
background-color: #059669;
border-radius: 0.2rem;
border: 1px solid transparent;
padding: 0.5rem 1rem 0.5rem 1rem;
}
.application-container button:hover,.application-container button:focus {
background-color: #059;
}
.application-container button,
.application-container input {
font-size: 0.9rem;
}
.application-container.active {
display: flex;
}
#poping-notice-content a {
text-decoration: none;
color: #10b981;
}
#poping-notice-content a:hover,#poping-notice-content a:focus {
color: #0ae;
}
#poping-notice-container-bar {
display: flex;
justify-content: center;
}
#close-poping-notice {
background-color: #059669;
padding: 0.5rem 5rem 0.5rem 5rem;
border-radius: 0.3rem;
text-decoration: none;
font-weight: bolder;
color: white;
}
#close-poping-notice:hover,#close-poping-notice:focus {
background-color: #059;
}
.modal {
position: fixed;
display: none;
color: white;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#poping-notice {
padding: 3rem;
border-radius: 0.3rem;
background-color: #374151;
color: white;
overflow-y: scroll;
width: 94%;
max-height: 70%;
flex-direction: column;
scrollbar-width: none;
}
#modal-video-container,#modal-format-selector {
height: 100%;
width: 100%;
flex-flow: column;
align-items: center;
background-color: #111827;
}
#modal-format-selector h2 {
width: 100%;
}
#modal-format-selector p {
width: 100%;
}
.video-container-bar {
width: 99%;
height: 2rem;
padding-top: 1rem;
padding-bottom: 1rem;
display: flex;
justify-content: end;
margin-bottom: 1rem;
}
.video-container-bar a {
padding: 1rem;
margin-right: 1rem;
border-radius: 0.3rem;
display: flex;
align-items: center;
justify-content: center;
background-color: #dc2626;
}
.video-container-bar a:hover,.video-container-bar a:focus {
color: white;
background-color: grey;
}
#modal-video-container > #block {
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
}
#download-video-container {
padding: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
.button-download {
display: none;
border-radius: 0.3rem;
background-color: #059669;
text-decoration: none;
color: white;
user-select: none;
cursor: pointer;
height: 30px;
padding: 0 2rem;
}
.button-download:hover,.button-download:focus {
background-color: #059;
}
.button-download embed {
height: 100%;
}
#download-video-loading.active {
height: 2rem;
}
#download-video-container .button-download.active {
display: flex;
align-items: center;
}
#modal-loading {
display: none;
}
.modal {
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
overflow-y: scroll;
}
#poping-notice.active {
display: flex;
}
.modal.active {
display: flex;
scrollbar-width: none;
}
.modal.active.active::-webkit-scrollbar {
display: none;
}
#modal-loading {
display: none;
position: fixed;
top: 50%;
left: 50%;
height: 100%;
width: 100%;
transform: translate(-50%, -50%);
border: black 1px solid;
justify-content: right;
align-items: center;
}
#modal-loading.active {
display: flex;
}
#modal-loading embed {
width: 10%;
}
#video {
width: 100%;
max-width: 636px;
}
#video-container {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.format-list > div {
display: grid;
grid-column-gap: 10px;
grid-row-gap: 10px;
grid-template-areas: "a";
grid-auto-columns: 99%;
margin: 1rem;
}
.format-list > div > a {
border: 1px solid black;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
padding-right: 5%;
padding-left: 5%;
text-decoration: none;
border-radius: 0.5rem;
background-color: #059669;
overflow-wrap: anywhere;
}
.format-list > div > a:focus,.format-list > div > a:hover {
background-color: #059;
}
.format-list > div > a:after {
padding-bottom: 100%;
display: block;
content: "";
}
.scale {
height: 1em;
width; 1em;
}
@media (min-width: 600px) {
.format-list > div {
grid-template-areas: "a a";
grid-auto-columns: 49.75%;
}
}
@media (min-width: 805px) {
.format-list > div {
grid-template-areas: "a a a";
grid-auto-columns: 33%;
}
}
@media (min-width: 812px) {
#poping-notice {
width: 40%;
}
}

View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<g transform="matrix(.13811 0 0 .13811 4.964 5.832)" fill-opacity=".8">
<g transform="matrix(6.5167 0 0 6.5167-28.01-34.23)">
<g transform="matrix(1.17633 0 0 1.17633 1 1.589)" stroke-opacity=".85" fill="#000" stroke="none" fill-opacity="0" stroke-width=".73">
<path d="m11.243 12.993c-.192 0-.384-.073-.53-.22-.293-.293-.293-.768 0-1.061 2.047-2.047 2.047-5.378 0-7.425-.293-.293-.293-.768 0-1.061.293-.293.768-.293 1.061 0 1.275 1.275 1.977 2.97 1.977 4.773 0 1.803-.702 3.498-1.977 4.773-.146.146-.338.22-.53.22z"/>
<path d="m8.578 11.578c-.192 0-.384-.073-.53-.22-.293-.293-.293-.768 0-1.061 1.267-1.267 1.267-3.329 0-4.596-.293-.293-.293-.768 0-1.061.293-.293.768-.293 1.061 0 1.852 1.852 1.852 4.865 0 6.718-.146.146-.338.22-.53.22z"/>
</g>
<g transform="translate(16 4)">
<g fill="#f2f2f2" fill-opacity="0">
<path d="m-1.773 12.874c-.226 0-.452-.086-.623-.259-.345-.345-.345-.903 0-1.248 2.408-2.408 2.408-6.326 0-8.734-.345-.345-.345-.903 0-1.248.345-.345.903-.345 1.248 0 1.5 1.5 2.326 3.494 2.326 5.615 0 2.121-.826 4.115-2.326 5.615-.172.172-.398.259-.623.259z"/>
<path d="m-4.908 11.209c-.226 0-.452-.086-.623-.259-.345-.345-.345-.903 0-1.248 1.49-1.49 1.49-3.916 0-5.406-.345-.345-.345-.903 0-1.248.345-.345.903-.345 1.248 0 2.179 2.179 2.179 5.723 0 7.903-.172.172-.398.259-.623.259z"/>
</g>
<path d="m-7.353 15.235c-.153 0-.303-.06-.416-.172l-4.534-4.534h-2.109c-.325 0-.588-.263-.588-.588v-5.882c0-.325.263-.588.588-.588h2.109l4.534-4.534c.168-.168.421-.219.641-.127.22.092.363.306.363.543v15.292c0 .238-.144.453-.363.543-.073.031-.149.045-.225.045" fill="#4d4d4d" fill-opacity="1"/>
</g>
</g>
<path d="m14.75 9.674v1.326h-1.326l-1.674-1.674-1.674 1.674h-1.326v-1.326l1.674-1.674-1.674-1.674v-1.326h1.326l1.674 1.674 1.674-1.674h1.326v1.326l-1.674 1.674 1.674 1.674" fill="#4d4d4d" fill-opacity="1" transform="matrix(8.22579 0 0 8.22579-32.543-28.351)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,76 @@
<html>
<head>
<script src="js/peertube-dl-web.js?v=1"></script>
<link rel="stylesheet" href="css/index.css?v=4"></script>
<link rel="icon" type="image/png" href="img/peertube-dl-logo.png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Peertube-dl Web</title>
<meta name="description" content="Webpage intended to download videos from various webpages for fair use."/>
<meta property="og:type" content="website" />
<meta property="og:title" content="Peertube-dl Web"/>
<meta property="og:site_name" content="Peertube-dl"/>
<meta property="og:description" content="Webpage intended to download videos from various webpages for fair use."/>
<meta property="og:image" content="./img/peertube-dl-logo-fullsize.png"/>
</head>
<body>
<div class="application-container">
<h2>Peertube-dl Web Application</h2>
<form id="download-form">
<input class="block" type="text" id="download-form-url" placeholder="Introduce the url you want to download."/>
<button class="block" id="download-form-button" >Fetch from api</button>
</form>
</div>
<div id="modal-format-selector" class="modal">
<div class="video-container-bar">
<a id="close-modal-format-selector">x</a>
</div>
<h2>Example video</h2>
<p>Example description</p>
<div class="format-list">
<h3>Video Formats.</h3>
<div class="video-formats">
</div>
<h3>Audio Formats.</h3>
<div class="audio-formats">
</div>
</div>
</div>
<div id="modal-loading">
<embed src="img/spinner.svg"/>
</div>
<div id="modal-video-container" class="modal">
<div class="video-container-bar">
<a id="close-and-reset-video-container">x</a>
</div>
<div id="video-container">
<div class="block">
<video id="video" controls="controls"></video>
</div>
<div id="download-video-container" class="block">
<a id="download-video-prepare" class="button-download active">Prepare download</a>
<a id="download-video-loading" class="button-download"><embed src="img/spinner.svg"/></a>
<a id="download-video" class="button-download">Download</a>
</div>
</div>
</div>
<div id="poping-notice" class="modal">
<div id="poping-notice-content">
<p>This webpage is free as in freedom software, it is offered to you with the hope it will be useful, but without any warranty,
you can find the source code at <a href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl">my gitea</a> with docs to setup your own
webpage like this, this software is licensed under the AGPLv3 license which means you MUST convey the source code in a human readable form
if you distribute this software or use it as an service to users of service or distributees.<p>
<p>I hope that if you find a non supported url which should be supported, a bug, or a feature you would like this webpage to have you file an issue in
<a href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues">https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues</a>
to help this software improve since I find tracking users a pretty bad way to discover bugs and potential good features.</p>
<p>This webpage may load third party resources depending on the url you give to it which may put cookies in your browser, you are
encouraged to frecuently delete your browser cookies to avoid those third parties tracking you on internet, Firefox offers you an
option to delete cookies as soon as you close the browser which may be a good idea to enable in the orwellian internet of today.</p>
</div>
<div id="poping-notice-container-bar">
<a id="close-poping-notice" href="#">X</a>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,13 @@
{
"name": "default-theme",
"version": "1.0.0",
"private": true,
"license": "MIT",
"scripts": {
"build": "webpack"
},
"dependencies": {
"webpack": "^5.15.0",
"webpack-cli": "^4.3.1"
}
}

View File

@ -0,0 +1,106 @@
"use strict";
import { PopingNotice } from './view/poping_notice.js';
import { DownloadForm } from './view/download_form.js';
import { LoadingModal } from './view/loading_modal.js';
import { VideoContainer } from './view/video_container.js';
import { FormatSelector } from './view/format_selector.js';
class Application {
constructor() {
this.poping_notice = new PopingNotice();
this.download_form = new DownloadForm(this.onDownloadFormGot.bind(this));
this.loading_modal = new LoadingModal();
this.video_container = new VideoContainer();
this.format_selector = new FormatSelector();
}
init() {
this.popingNotice.setVisible(true);
}
onDownloadFormGot(url) {
this.dispatchURL(url);
}
dispatchURL(url, format) {
this.loadingModal.setVisible(true);
let error_str;
let success = this.queryAPI(url, format).then( (response) => {
if ( response.options !== undefined && response.options.list_formats !== undefined && response.options.list_formats ) {
if ( response.formats === undefined
|| response.formats.audio_formats === undefined
|| response.formats.video_formats === undefined ) {
throw 'Format object is not valid.';
}
this.formatSelector.prepareFormatSelector(
response.title, response.description,
response.formats.audio_formats, response.formats.video_formats,
( id ) => {
this.loadingModal.setVisible(true);
this.dispatchURL(url, id);
});
this.formatSelector.setVisible(true);
this.loadingModal.setVisible(false);
} else {
this.videoContainer.onCanPlay(this.onCanPlayVideoContainer.bind(this));
this.videoContainer.setURLVideo(response.url);
this.videoContainer.setFilename(response.filename);
}
}).catch( (error) => {
error_str = error.toString();
this.loadingModal.setVisible(false);
let input_url = document.createElement('a');
input_url.href = url;
input_url.innerText = url;
let issues_url = document.createElement('a');
issues_url.href = 'https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues';
issues_url.innerText = 'here';
this.popingNotice.setMessage( [ 'The url ', input_url, ' is not supported, the error was: ', error_str , ' if you think this is an error, report it ', issues_url, '.' ]);
this.popingNotice.setVisible(true);
});
}
onCanPlayVideoContainer() {
this.videoContainer.setVisible(true);
this.loadingModal.setVisible(false);
}
async queryAPI(url, format) {
let request = { url: url };
if (format !== undefined)
request.format = format;
const response = await fetch('/api', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request),
});
return response.json();
}
get formatSelector() {
return this.format_selector;
}
get videoContainer() {
return this.video_container;
}
get downloadForm() {
return this.download_form;
}
get popingNotice() {
return this.poping_notice;
}
get loadingModal() {
return this.loading_modal;
}
}
export { Application };

View File

@ -0,0 +1,10 @@
"use strict";
import { Application } from './application.js';
window.addEventListener('load', (event) => {
let application = new Application();
application.init();
});

View File

@ -0,0 +1,35 @@
"use strict";
class DownloadForm {
constructor(callback) {
this.query_selector = '#download-form';
this.callback = (event) => {
event.preventDefault();
callback(this.downloadFormUrl.value);
};
this.addEventListeners();
}
addEventListeners() {
this.downloadFormButton.addEventListener('click', this.callback);
this.element.addEventListener('submit', this.callback);
}
get downloadFormButton() {
return this.element.querySelector('#download-form-button');
}
get downloadFormUrl() {
return this.element.querySelector('#download-form-url');
}
get querySelector() {
return this.query_selector;
}
get element() {
return document.querySelector(this.querySelector);
}
}
export { DownloadForm };

View File

@ -0,0 +1,120 @@
"use strict";
class FormatSelector {
constructor() {
this.query_selector = '#modal-format-selector';
this.addEventListeners();
}
appendFormat(container, object, is_video, callback) {
let a = document.createElement('a');
let br = function() { return document.createElement('br') };
let inner_text = [];
let muted_video = false;
if ( is_video ) {
if ( object.audioSampleRate === undefined ) {
muted_video = true;
}
inner_text.push('Id: ' + object.id);
inner_text.push(br());
inner_text.push('Format: ' + object.mimeType);
inner_text.push(br());
inner_text.push('QualityLabel: ' + object.qualityLabel + "p");
inner_text.push(br());
inner_text.push('Bitrate: ' + object.bitrate);
inner_text.push(br());
inner_text.push( ( muted_video) ?
"No audio. " :
'AudioSampleRate: ' + object.audioSampleRate
);
} else {
inner_text.push('Id: ' + object.id + "\n");
inner_text.push(br());
inner_text.push('Format: ' + object.mimeType);
inner_text.push(br());
inner_text.push('AudioSampleRate: ' + object.audioSampleRate);
inner_text.push(br());
inner_text.push('Bitrate: ' + object.bitrate);
}
a.addEventListener( 'click', (event) => {
callback(object.id);
});
if (muted_video) {
let img_muted_video = document.createElement('img');
img_muted_video.classList.add('mute_img');
img_muted_video.classList.add('scale');
img_muted_video.src = 'img/audio_muted.svg';
inner_text.push(img_muted_video);
}
for (let text of inner_text) {
if (typeof text === "string"
|| text instanceof String) {
text = document.createTextNode(text);
a.appendChild(text);
} else if ( text instanceof Node) {
a.appendChild(text);
} else {
throw ('Text is not a instance of Node nor a String');
}
}
container.appendChild(a);
}
prepareFormatSelector(title, description, audio_formats, video_formats, callback) {
this.titleFormatSelector.innerText = title;
this.descriptionFormatSelector.innerText = description;
this.videoFormats.innerHTML = '';
this.audioFormats.innerHTML = '';
for ( let x of audio_formats) {
this.appendFormat(this.audioFormats, x, false, callback);
}
for ( let x of video_formats ) {
this.appendFormat(this.videoFormats, x, true, callback);
}
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.element.classList.remove('active');
}
}
addEventListeners() {
this.closeFormatSelector.addEventListener('click', (event) => { this.setVisible(false); });
}
get videoFormats() {
return this.element.querySelector('.video-formats');
}
get audioFormats() {
return this.element.querySelector('.audio-formats');
}
get titleFormatSelector() {
return this.element.querySelector('h2');
}
get descriptionFormatSelector() {
return this.element.querySelector('p');
}
get closeFormatSelector() {
return this.element.querySelector('#close-modal-format-selector');
}
get element() {
return document.querySelector(this.querySelector);
}
get querySelector() {
return this.query_selector;
}
}
export { FormatSelector };

View File

@ -0,0 +1,25 @@
"use strict";
class LoadingModal {
constructor() {
this.query_selector = '#modal-loading';
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.element.classList.remove('active');
}
}
get element() {
return document.querySelector(this.querySelector);
}
get querySelector() {
return this.query_selector;
}
}
export { LoadingModal };

View File

@ -0,0 +1,56 @@
"use strict";
class PopingNotice {
constructor() {
this.query_selector = '#poping-notice';
this.closePopingNotice.addEventListener('click', (event) => {
this.setVisible(false);
});
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.element.classList.remove('active');
}
}
setMessage(message) {
if (!message instanceof Array)
throw 'Message is not instance of Array.';
let p = document.createElement('p');
for (let node of message) {
if (typeof node === "string"
|| node instanceof String) {
node = document.createTextNode(node);
p.appendChild(node);
} else if ( node instanceof Node) {
p.appendChild(node);
} else {
throw ('Node is not a instance of Node nor a String');
}
}
this.popingNoticeContent.innerHTML = '';
this.popingNoticeContent.appendChild(p);
}
get querySelector() {
return this.query_selector;
}
get element() {
return document.querySelector(this.querySelector);
}
get popingNoticeContent() {
return this.element.querySelector('#poping-notice-content');
}
get closePopingNotice() {
return this.element.querySelector('#close-poping-notice');
}
}
export { PopingNotice };

View File

@ -0,0 +1,113 @@
"use strict";
class VideoContainer {
constructor() {
this.query_selector = '#modal-video-container';
this.addEventListeners();
}
setVisible(option) {
if (option) {
this.element.classList.add('active');
} else {
this.video.pause();
this.element.classList.remove('active');
this.downloadVideoPrepare.classList.add('active');
this.downloadVideoLoading.classList.remove('active');
this.downloadVideo.classList.remove('active');
}
}
addEventListeners() {
this.downloadVideoPrepare.addEventListener('click', this.downloadPrepareHandler.bind(this));
this.closeAndResetVideoContainer.addEventListener('click', (event) => {
this.setVisible(false);
});
}
downloadPrepareHandler(event) {
this.downloadVideoPrepare.classList.remove('active');
this.downloadVideoLoading.classList.add('active');
this.generateBlobVideo(this.URLVideo).then( blob => {
this.downloadVideo.href = URL.createObjectURL(blob);
this.downloadVideo.download = this.filename;
this.downloadVideoLoading.classList.remove('active');
this.downloadVideo.classList.add('active');
});
}
async generateBlobVideo(url) {
const blob = await fetch(url, { mode: 'cors', })
.then(res => res.blob())
.catch( err => this.generateBlobVideoByProxy(url) );
return blob;
}
async generateBlobVideoByProxy(url) {
const blob = await fetch( '/proxy_to_get', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({url: url}),
}
).then(res => res.blob());
return blob;
}
onCanPlay(callback) {
video.addEventListener('canplay', (event) => {
callback();
});
}
setURLVideo(url) {
this.url_video = url;
video.src = url;
}
setFilename(filename) {
this.filename = filename;
}
get closeAndResetVideoContainer() {
return this.element.querySelector('#close-and-reset-video-container');
}
get downloadVideo() {
return this.element.querySelector('#download-video');
}
get URLVideo() {
return this.url_video;
}
get closeAndResetVideoContainer() {
return this.element.querySelector('#close-and-reset-video-container');
}
get downloadVideoLoading() {
return this.element.querySelector('#download-video-loading');
}
get downloadVideoPrepare() {
return this.element.querySelector('#download-video-prepare');
}
get video() {
return this.element.querySelector('#video');
}
get element() {
return document.querySelector(this.querySelector);
}
get querySelector() {
return this.query_selector;
}
}
export { VideoContainer };

View File

@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
entry: './src/index.js',
devtool: 'source-map',
output: {
filename: 'peertube-dl-web.js',
path: path.resolve(__dirname, 'dist/js'),
},
};