Current thumbnail check schedule:
{% if config.scheduler.thumbnail_check %}
diff --git a/tubearchivist/home/views.py b/tubearchivist/home/views.py
index b81895d..6ef8de5 100644
--- a/tubearchivist/home/views.py
+++ b/tubearchivist/home/views.py
@@ -111,6 +111,8 @@ class ArchivistResultsView(ArchivistViewConfig):
"likes": "stats.like_count",
"downloaded": "date_downloaded",
"published": "published",
+ "duration": "player.duration",
+ "filesize": "media_size",
}
sort_by = sort_by_map[self.context["sort_by"]]
diff --git a/tubearchivist/requirements.txt b/tubearchivist/requirements.txt
index a36184c..d5aee90 100644
--- a/tubearchivist/requirements.txt
+++ b/tubearchivist/requirements.txt
@@ -1,8 +1,8 @@
apprise==1.6.0
-celery==5.3.4
+celery==5.3.5
Django==4.2.7
django-auth-ldap==4.6.0
-django-cors-headers==4.3.0
+django-cors-headers==4.3.1
djangorestframework==3.14.0
Pillow==10.1.0
redis==5.0.1
@@ -10,4 +10,4 @@ requests==2.31.0
ryd-client==0.0.6
uWSGI==2.0.23
whitenoise==6.6.0
-yt-dlp==2023.10.13
+yt-dlp==2023.11.16
diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css
index fbd84f6..5b15bf1 100644
--- a/tubearchivist/static/css/style.css
+++ b/tubearchivist/static/css/style.css
@@ -180,6 +180,10 @@ button:hover {
text-decoration: underline;
}
+.footer .boxed-content {
+ text-align: center;
+}
+
/* toggle on-off */
.toggle {
display: flex;
diff --git a/tubearchivist/static/stats.js b/tubearchivist/static/stats.js
index d575ea4..4200d74 100644
--- a/tubearchivist/static/stats.js
+++ b/tubearchivist/static/stats.js
@@ -5,29 +5,333 @@
/* globals apiRequest */
function primaryStats() {
- let apiEndpoint = '/api/stats/primary/';
+ let apiVideoEndpoint = '/api/stats/video/';
+ let responseData = apiRequest(apiVideoEndpoint, 'GET');
+
+ let activeBox = document.getElementById('activeBox');
+ clearLoading(activeBox);
+
+ let totalTile = buildTotalVideoTile(responseData);
+ activeBox.appendChild(totalTile);
+ let activeTile = buildActiveVideoTile(responseData);
+ activeBox.appendChild(activeTile);
+ let inActiveTile = buildInActiveVideoTile(responseData);
+ activeBox.appendChild(inActiveTile);
+
+ let videoTypeBox = document.getElementById('videoTypeBox');
+ clearLoading(videoTypeBox);
+
+ let videosTypeTile = buildVideosTypeTile(responseData);
+ videoTypeBox.appendChild(videosTypeTile);
+ let shortsTypeTile = buildShortsTypeTile(responseData);
+ videoTypeBox.appendChild(shortsTypeTile);
+ let streamsTypeTile = buildStreamsTypeTile(responseData);
+ videoTypeBox.appendChild(streamsTypeTile);
+}
+
+function secondaryStats() {
+ let apiChannelEndpoint = '/api/stats/channel/';
+ let channelResponseData = apiRequest(apiChannelEndpoint, 'GET');
+ let secondaryBox = document.getElementById('secondaryBox');
+ clearLoading(secondaryBox);
+ let channelTile = buildChannelTile(channelResponseData);
+ secondaryBox.appendChild(channelTile);
+
+ let apiPlaylistEndpoint = '/api/stats/playlist/';
+ let playlistResponseData = apiRequest(apiPlaylistEndpoint, 'GET');
+ let playlistTile = buildPlaylistTile(playlistResponseData);
+ secondaryBox.appendChild(playlistTile);
+
+ let apiDownloadEndpoint = '/api/stats/download/';
+ let downloadResponseData = apiRequest(apiDownloadEndpoint, 'GET');
+ let downloadTile = buildDownloadTile(downloadResponseData);
+ secondaryBox.appendChild(downloadTile);
+}
+
+function buildTotalVideoTile(responseData) {
+ const totalCount = responseData.doc_count || 0;
+ const totalSize = humanFileSize(responseData.media_size || 0);
+ const content = {
+ Items: `${totalCount}`,
+ 'Media Size': `${totalSize}`,
+ Duration: responseData.duration_str,
+ };
+ const tile = buildTile('All: ');
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+ return tile;
+}
+
+function buildActiveVideoTile(responseData) {
+ const activeCount = responseData?.active_true?.doc_count || 0;
+ const activeSize = humanFileSize(responseData?.active_true?.media_size || 0);
+ const duration = responseData?.active_true?.duration_str || 'NA';
+ const content = {
+ Items: `${activeCount}`,
+ 'Media Size': `${activeSize}`,
+ Duration: duration,
+ };
+ const tile = buildTile('Active: ');
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+ return tile;
+}
+
+function buildInActiveVideoTile(responseData) {
+ const inActiveCount = responseData?.active_false?.doc_count || 0;
+ const inActiveSize = humanFileSize(responseData?.active_false?.media_size || 0);
+ const duration = responseData?.active_false?.duration_str || 'NA';
+ const content = {
+ Items: `${inActiveCount}`,
+ 'Media Size': `${inActiveSize}`,
+ Duration: duration,
+ };
+ const tile = buildTile('Inactive: ');
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+ return tile;
+}
+
+function buildVideosTypeTile(responseData) {
+ const videosCount = responseData?.type_videos?.doc_count || 0;
+ const videosSize = humanFileSize(responseData?.type_videos?.media_size || 0);
+ const duration = responseData?.type_videos?.duration_str || 'NA';
+ const content = {
+ Items: `${videosCount}`,
+ 'Media Size': `${videosSize}`,
+ Duration: duration,
+ };
+ const tile = buildTile('Regular Videos: ');
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+ return tile;
+}
+
+function buildShortsTypeTile(responseData) {
+ const shortsCount = responseData?.type_shorts?.doc_count || 0;
+ const shortsSize = humanFileSize(responseData?.type_shorts?.media_size || 0);
+ const duration = responseData?.type_shorts?.duration_str || 'NA';
+ const content = {
+ Items: `${shortsCount}`,
+ 'Media Size': `${shortsSize}`,
+ Duration: duration,
+ };
+ const tile = buildTile('Shorts: ');
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+ return tile;
+}
+
+function buildStreamsTypeTile(responseData) {
+ const streamsCount = responseData?.type_streams?.doc_count || 0;
+ const streamsSize = humanFileSize(responseData?.type_streams?.media_size || 0);
+ const duration = responseData?.type_streams?.duration_str || 'NA';
+ const content = {
+ Items: `${streamsCount}`,
+ 'Media Size': `${streamsSize}`,
+ Duration: duration,
+ };
+ const tile = buildTile('Streams: ');
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+ return tile;
+}
+
+function buildChannelTile(responseData) {
+ let tile = buildTile('Channels: ');
+ const total = responseData.doc_count || 0;
+ const subscribed = responseData.subscribed_true || 0;
+ const active = responseData.active_true || 0;
+ const content = {
+ Subscribed: subscribed,
+ Active: active,
+ Total: total,
+ };
+ const table = buildTileContenTable(content, 3);
+ tile.appendChild(table);
+
+ return tile;
+}
+
+function buildPlaylistTile(responseData) {
+ let tile = buildTile('Playlists: ');
+ const total = responseData.doc_count || 0;
+ const subscribed = responseData.subscribed_true || 0;
+ const active = responseData.active_true || 0;
+ const content = {
+ Subscribed: subscribed,
+ Active: active,
+ Total: total,
+ };
+ const table = buildTileContenTable(content, 2);
+ tile.appendChild(table);
+
+ return tile;
+}
+
+function buildDownloadTile(responseData) {
+ const pendingTotal = responseData.pending || 0;
+ let tile = buildTile(`Downloads Pending: ${pendingTotal}`);
+ const pendingVideos = responseData.pending_videos || 0;
+ const pendingShorts = responseData.pending_shorts || 0;
+ const pendingStreams = responseData.pending_streams || 0;
+ const content = {
+ Videos: pendingVideos,
+ Shorts: pendingShorts,
+ Streams: pendingStreams,
+ };
+ const table = buildTileContenTable(content, 3);
+ tile.appendChild(table);
+
+ return tile;
+}
+
+function watchStats() {
+ let apiEndpoint = '/api/stats/watch/';
let responseData = apiRequest(apiEndpoint, 'GET');
- let primaryBox = document.getElementById('primaryBox');
+ let watchBox = document.getElementById('watchBox');
+ clearLoading(watchBox);
- clearLoading(primaryBox);
+ let watchedTile = buildWatchTile('watched', responseData.watched);
+ watchBox.appendChild(watchedTile);
- let videoTile = buildVideoTile(responseData);
- primaryBox.appendChild(videoTile);
+ let unwatchedTile = buildWatchTile('unwatched', responseData.unwatched);
+ watchBox.appendChild(unwatchedTile);
+}
- let channelTile = buildChannelTile(responseData);
- primaryBox.appendChild(channelTile);
+function buildWatchTile(title, watchDetail) {
+ const items = watchDetail?.items ?? 0;
+ const duration = watchDetail?.duration ?? 0;
+ const duration_str = watchDetail?.duration_str ?? '0s';
+ const hasProgess = !!watchDetail?.progress;
+ const progress = (Number(watchDetail?.progress) * 100).toFixed(2) ?? '0';
- let playlistTile = buildPlaylistTile(responseData);
- primaryBox.appendChild(playlistTile);
+ let titleCapizalized = capitalizeFirstLetter(title);
- let downloadTile = buildDownloadTile(responseData);
- primaryBox.appendChild(downloadTile);
+ if (hasProgess) {
+ titleCapizalized = `${progress}% ` + titleCapizalized;
+ }
+
+ let tile = buildTile(titleCapizalized);
+
+ const content = {
+ Videos: items,
+ Seconds: duration,
+ Duration: duration_str,
+ };
+
+ const table = buildTileContenTable(content, 3);
+
+ tile.appendChild(table);
+
+ return tile;
+}
+
+function downloadHist() {
+ let apiEndpoint = '/api/stats/downloadhist/';
+ let responseData = apiRequest(apiEndpoint, 'GET');
+ let histBox = document.getElementById('downHistBox');
+ clearLoading(histBox);
+ if (responseData.length === 0) {
+ let tile = buildTile('No recent downloads');
+ histBox.appendChild(tile);
+ return;
+ }
+
+ for (let i = 0; i < responseData.length; i++) {
+ const dailyStat = responseData[i];
+ let tile = buildDailyStat(dailyStat);
+ histBox.appendChild(tile);
+ }
+}
+
+function buildDailyStat(dailyStat) {
+ let tile = buildTile(dailyStat.date);
+ let message = document.createElement('p');
+ const isExactlyOne = dailyStat.count === 1;
+
+ let text = 'Videos';
+ if (isExactlyOne) {
+ text = 'Video';
+ }
+
+ message.innerText = `+${dailyStat.count} ${text}
+ ${humanFileSize(dailyStat.media_size)}`;
+
+ tile.appendChild(message);
+ return tile;
+}
+
+function buildChannelRow(id, name, value) {
+ let tableRow = document.createElement('tr');
+
+ tableRow.innerHTML = `
+
${name} |
+
${value} |
+ `;
+
+ return tableRow;
+}
+
+function addBiggestChannelByDocCount() {
+ let tBody = document.getElementById('biggestChannelTableVideos');
+
+ let apiEndpoint = '/api/stats/biggestchannels/?order=doc_count';
+ const responseData = apiRequest(apiEndpoint, 'GET');
+
+ for (let i = 0; i < responseData.length; i++) {
+ const { id, name, doc_count } = responseData[i];
+
+ let tableRow = buildChannelRow(id, name, doc_count);
+
+ tBody.appendChild(tableRow);
+ }
+}
+
+function addBiggestChannelByDuration() {
+ const tBody = document.getElementById('biggestChannelTableDuration');
+
+ let apiEndpoint = '/api/stats/biggestchannels/?order=duration';
+ const responseData = apiRequest(apiEndpoint, 'GET');
+
+ for (let i = 0; i < responseData.length; i++) {
+ const { id, name, duration_str } = responseData[i];
+
+ let tableRow = buildChannelRow(id, name, duration_str);
+
+ tBody.appendChild(tableRow);
+ }
+}
+
+function addBiggestChannelByMediaSize() {
+ let tBody = document.getElementById('biggestChannelTableMediaSize');
+
+ let apiEndpoint = '/api/stats/biggestchannels/?order=media_size';
+ const responseData = apiRequest(apiEndpoint, 'GET');
+
+ for (let i = 0; i < responseData.length; i++) {
+ const { id, name, media_size } = responseData[i];
+
+ let tableRow = buildChannelRow(id, name, humanFileSize(media_size));
+
+ tBody.appendChild(tableRow);
+ }
}
function clearLoading(dashBox) {
dashBox.querySelector('#loading').remove();
}
+function capitalizeFirstLetter(string) {
+ // source: https://stackoverflow.com/a/1026087
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+function humanFileSize(size) {
+ let i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
+ return (size / Math.pow(1024, i)).toFixed(1) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
+}
+
function buildTile(titleText) {
let tile = document.createElement('div');
tile.classList.add('info-box-item');
@@ -89,226 +393,6 @@ function buildTileContenTable(content, rowsWanted) {
return table;
}
-function buildVideoTile(responseData) {
- let tile = buildTile(`Video types: `);
-
- const total = responseData.videos.total || 0;
- const videos = responseData.videos.videos || 0;
- const shorts = responseData.videos.shorts || 0;
- const streams = responseData.videos.streams || 0;
-
- const content = {
- Videos: `${videos}/${total}`,
- Shorts: `${shorts}/${total}`,
- Streams: `${streams}/${total}`,
- };
-
- const table = buildTileContenTable(content, 3);
-
- tile.appendChild(table);
-
- return tile;
-}
-
-function buildChannelTile(responseData) {
- let tile = buildTile(`Channels: `);
-
- const total = responseData.channels.total || 0;
- const subscribed = responseData.channels.sub_true || 0;
-
- const content = {
- Subscribed: `${subscribed}/${total}`,
- };
-
- const table = buildTileContenTable(content, 3);
-
- tile.appendChild(table);
-
- return tile;
-}
-
-function buildPlaylistTile(responseData) {
- let tile = buildTile(`Playlists:`);
-
- const total = responseData.playlists.total || 0;
- const subscribed = responseData.playlists.sub_true || 0;
-
- const content = {
- Subscribed: `${subscribed}/${total}`,
- };
-
- const table = buildTileContenTable(content, 3);
-
- tile.appendChild(table);
-
- return tile;
-}
-
-function buildDownloadTile(responseData) {
- let tile = buildTile('Downloads');
-
- const pending = responseData.downloads.pending || 0;
- const ignored = responseData.downloads.ignore || 0;
-
- const content = {
- Pending: pending,
- Ignored: ignored,
- };
-
- const table = buildTileContenTable(content, 3);
-
- tile.appendChild(table);
-
- return tile;
-}
-
-function watchStats() {
- let apiEndpoint = '/api/stats/watch/';
- let responseData = apiRequest(apiEndpoint, 'GET');
- let watchBox = document.getElementById('watchBox');
- clearLoading(watchBox);
-
- const { total, watched, unwatched } = responseData;
-
- let firstCard = buildWatchTile('total', total);
- watchBox.appendChild(firstCard);
-
- let secondCard = buildWatchTile('watched', watched);
- watchBox.appendChild(secondCard);
-
- let thirdCard = buildWatchTile('unwatched', unwatched);
- watchBox.appendChild(thirdCard);
-}
-
-function capitalizeFirstLetter(string) {
- // source: https://stackoverflow.com/a/1026087
- return string.charAt(0).toUpperCase() + string.slice(1);
-}
-
-function buildWatchTile(title, watchDetail) {
- const items = watchDetail?.items ?? 0;
- const duration = watchDetail?.duration ?? 0;
- const duration_str = watchDetail?.duration_str ?? '0s';
- const hasProgess = !!watchDetail?.progress;
- const progress = (Number(watchDetail?.progress) * 100).toFixed(2) ?? '0';
-
- let titleCapizalized = capitalizeFirstLetter(title);
-
- if (hasProgess) {
- titleCapizalized = `${progress}% ` + titleCapizalized;
- }
-
- let tile = buildTile(titleCapizalized);
-
- const content = {
- Videos: items,
- Seconds: duration,
- Playback: duration_str,
- };
-
- const table = buildTileContenTable(content, 3);
-
- tile.appendChild(table);
-
- return tile;
-}
-
-function downloadHist() {
- let apiEndpoint = '/api/stats/downloadhist/';
- let responseData = apiRequest(apiEndpoint, 'GET');
- let histBox = document.getElementById('downHistBox');
- clearLoading(histBox);
- if (responseData.length === 0) {
- let tile = buildTile('No recent downloads');
- histBox.appendChild(tile);
- return;
- }
-
- for (let i = 0; i < responseData.length; i++) {
- const dailyStat = responseData[i];
- let tile = buildDailyStat(dailyStat);
- histBox.appendChild(tile);
- }
-}
-
-function buildDailyStat(dailyStat) {
- let tile = buildTile(dailyStat.date);
- let message = document.createElement('p');
- const isExactlyOne = dailyStat.count === 1;
-
- let text = 'Videos';
- if (isExactlyOne) {
- text = 'Video';
- }
-
- message.innerText = `+${dailyStat.count} ${text}
- ${humanFileSize(dailyStat.media_size)}`;
-
- tile.appendChild(message);
- return tile;
-}
-
-function humanFileSize(size) {
- let i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
- return (size / Math.pow(1024, i)).toFixed(1) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
-}
-
-function buildChannelRow(id, name, value) {
- let tableRow = document.createElement('tr');
-
- tableRow.innerHTML = `
-
${name} |
-
${value} |
- `;
-
- return tableRow;
-}
-
-function addBiggestChannelByDocCount() {
- let tBody = document.getElementById('biggestChannelTableVideos');
-
- let apiEndpoint = '/api/stats/biggestchannels/?order=doc_count';
- const responseData = apiRequest(apiEndpoint, 'GET');
-
- for (let i = 0; i < responseData.length; i++) {
- const { id, name, doc_count } = responseData[i];
-
- let tableRow = buildChannelRow(id, name, doc_count);
-
- tBody.appendChild(tableRow);
- }
-}
-
-function addBiggestChannelByDuration() {
- const tBody = document.getElementById('biggestChannelTableDuration');
-
- let apiEndpoint = '/api/stats/biggestchannels/?order=duration';
- const responseData = apiRequest(apiEndpoint, 'GET');
-
- for (let i = 0; i < responseData.length; i++) {
- const { id, name, duration_str } = responseData[i];
-
- let tableRow = buildChannelRow(id, name, duration_str);
-
- tBody.appendChild(tableRow);
- }
-}
-
-function addBiggestChannelByMediaSize() {
- let tBody = document.getElementById('biggestChannelTableMediaSize');
-
- let apiEndpoint = '/api/stats/biggestchannels/?order=media_size';
- const responseData = apiRequest(apiEndpoint, 'GET');
-
- for (let i = 0; i < responseData.length; i++) {
- const { id, name, media_size } = responseData[i];
-
- let tableRow = buildChannelRow(id, name, humanFileSize(media_size));
-
- tBody.appendChild(tableRow);
- }
-}
-
function biggestChannel() {
addBiggestChannelByDocCount();
addBiggestChannelByDuration();
@@ -317,6 +401,7 @@ function biggestChannel() {
async function buildStats() {
primaryStats();
+ secondaryStats();
watchStats();
downloadHist();
biggestChannel();