Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: differentiate shorts, lives and long videos #371

Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Diffrentiate user uploaded shorts, lives, & long videos (#367)
arjitcodes marked this conversation as resolved.
Show resolved Hide resolved
- corrected the short video resolution in the UI (#366)

### Changed

- Raise exception if there are no videos in the playlists (#347)
Expand Down
3 changes: 3 additions & 0 deletions scraper/src/youtube2zim/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@
joined_date: str
collection_type: str
main_playlist: str | None = None
user_long_uploads_playlist: str | None=None
user_short_uploads_playlist: str | None=None
user_lives_playlist: str | None=None

Check warning on line 112 in scraper/src/youtube2zim/schemas.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/schemas.py#L110-L112

Added lines #L110 - L112 were not covered by tests
playlist_count: int


Expand Down
57 changes: 44 additions & 13 deletions scraper/src/youtube2zim/scraper.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@
# process-related
self.playlists = []
self.uploads_playlist_id = None
self.user_long_uploads_playlist_id = None
self.user_short_uploads_playlist_id = None
self.user_lives_playlist_id = None

Check warning on line 184 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L182-L184

Added lines #L182 - L184 were not covered by tests
self.videos_ids = []
self.video_ids_count = 0
self.videos_processed = 0
Expand Down Expand Up @@ -590,6 +593,9 @@
self.playlists,
self.main_channel_id,
self.uploads_playlist_id,
self.user_long_uploads_playlist_id,
self.user_short_uploads_playlist_id,
self.user_lives_playlist_id,
) = extract_playlists_details_from(self.collection_type, self.youtube_id)

def extract_videos_list(self):
Expand Down Expand Up @@ -1077,6 +1083,7 @@
author = videos_channels[video_id]
subtitles_list = get_subtitles(video_id)
channel_data = get_channel_json(author["channelId"])

return Video(
id=video_id,
title=video["snippet"]["title"],
Expand Down Expand Up @@ -1187,6 +1194,9 @@
home_playlist_list = []

main_playlist_slug = None
user_long_uploads_playlist_slug = None
user_short_uploads_playlist_slug = None
user_lives_playlist_slug = None

Check warning on line 1199 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1197-L1199

Added lines #L1197 - L1199 were not covered by tests
if len(self.playlists) > 0:
main_playlist_slug = get_playlist_slug(
self.playlists[0]
Expand Down Expand Up @@ -1216,6 +1226,16 @@
# modify playlist object for preview on homepage
playlist_obj.videos = playlist_obj.videos[:12]

if playlist.playlist_id == self.user_long_uploads_playlist_id:
user_long_uploads_playlist_slug = (playlist_slug)

Check warning on line 1230 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1230

Added line #L1230 was not covered by tests

if playlist.playlist_id == self.user_short_uploads_playlist_id:
user_short_uploads_playlist_slug = (playlist_slug)

Check warning on line 1233 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1233

Added line #L1233 was not covered by tests

if playlist.playlist_id == self.user_lives_playlist_id:
user_lives_playlist_slug= (playlist_slug)

Check warning on line 1236 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1236

Added line #L1236 was not covered by tests


if playlist.playlist_id == self.uploads_playlist_id:
main_playlist_slug = (
playlist_slug # set uploads playlist as main playlist
Expand Down Expand Up @@ -1251,22 +1271,33 @@

# write channel.json file
channel_data = get_channel_json(self.main_channel_id)
channel_data_dict = {

Check warning on line 1274 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1274

Added line #L1274 was not covered by tests
benoit74 marked this conversation as resolved.
Show resolved Hide resolved
"id":str(self.main_channel_id),
"title":str(self.title),
"description":str(self.description),
"channel_name":channel_data["snippet"]["title"],
"channel_description":channel_data["snippet"]["description"],
"profile_path":"profile.jpg",
"banner_path":"banner.jpg",
"collection_type":self.collection_type,
"main_playlist":main_playlist_slug,
"playlist_count":len(self.playlists),
"joined_date":channel_data["snippet"]["publishedAt"],
}

if user_long_uploads_playlist_slug is not None :
channel_data_dict["user_long_uploads_playlist"] = user_long_uploads_playlist_slug

Check warning on line 1289 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1289

Added line #L1289 was not covered by tests

if user_short_uploads_playlist_slug is not None :
channel_data_dict["user_short_uploads_playlist"] = user_short_uploads_playlist_slug

Check warning on line 1292 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1292

Added line #L1292 was not covered by tests

if user_lives_playlist_slug is not None :
channel_data_dict["user_lives_playlist"] = user_lives_playlist_slug

Check warning on line 1295 in scraper/src/youtube2zim/scraper.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/scraper.py#L1295

Added line #L1295 was not covered by tests

self.zim_file.add_item_for(
path="channel.json",
title=self.title,
content=Channel(
id=str(self.main_channel_id),
title=str(self.title),
description=str(self.description),
channel_name=channel_data["snippet"]["title"],
channel_description=channel_data["snippet"]["description"],
profile_path="profile.jpg",
banner_path="banner.jpg",
collection_type=self.collection_type,
main_playlist=main_playlist_slug,
playlist_count=len(self.playlists),
joined_date=channel_data["snippet"]["publishedAt"],
).model_dump_json(by_alias=True, indent=2),
content = Channel(**channel_data_dict).model_dump_json(by_alias=True, indent=2, exclude_none=True),
mimetype="application/json",
is_front=False,
)
Expand Down
146 changes: 146 additions & 0 deletions scraper/src/youtube2zim/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# vim: ai ts=4 sts=4 et sw=4 nu

from http import HTTPStatus
from datetime import datetime

Check warning on line 5 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L5

Added line #L5 was not covered by tests

import requests
from dateutil import parser as dt_parser
Expand Down Expand Up @@ -80,6 +81,7 @@
return False



def get_channel_json(channel_id):
"""fetch or retieve-save and return the Youtube ChannelResult JSON"""
fname = f"channel_{channel_id}"
Expand Down Expand Up @@ -319,6 +321,128 @@
return dt_parser.parse(item["snippet"]["publishedAt"]).date() in date_range


def get_user_short_uploads_playlist_id(channel_id):

Check warning on line 324 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L324

Added line #L324 was not covered by tests
benoit74 marked this conversation as resolved.
Show resolved Hide resolved
'''Return the user's uploaded short playlist ID, or None if shorts are not available or if an error occurs'''

user_short_uploads_playlist_id = "UUSH" + channel_id[2:] # Generate the short playlist ID

Check warning on line 327 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L327

Added line #L327 was not covered by tests

'''Make the API request to get the playlist details to determine whether shorts are available on the channel'''

Check warning on line 329 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L329

Added line #L329 was not covered by tests

try:
req = requests.get(

Check warning on line 332 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L331-L332

Added lines #L331 - L332 were not covered by tests
PLAYLIST_API,
params={"id": user_short_uploads_playlist_id, "part": "snippet", "key": YOUTUBE.api_key},
timeout=REQUEST_TIMEOUT,
)

# Check for HTTP error response
if req.status_code >= HTTPStatus.BAD_REQUEST:
logger.error(f"HTTP {req.status_code} Error response: {req.text}")
req.raise_for_status() # Raises an HTTPError if the status code is 4xx or 5xx

Check warning on line 341 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L340-L341

Added lines #L340 - L341 were not covered by tests

# Parse the response
response_json = req.json()
total_results = response_json.get("pageInfo", {}).get("totalResults", 0)
playlist_items = response_json.get("items", [])

Check warning on line 346 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L344-L346

Added lines #L344 - L346 were not covered by tests

# Check if there are no items or totalResults is 0 if yes then shorts not available
if total_results == 0 or not playlist_items:
logger.error(f"Short Playlist `{user_short_uploads_playlist_id}`: Not Found or No Shorts Available")
return None

Check warning on line 351 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L350-L351

Added lines #L350 - L351 were not covered by tests

# If everything is successful, return the short playlist ID
return user_short_uploads_playlist_id

Check warning on line 354 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L354

Added line #L354 was not covered by tests

except IndexError:
logger.error(f"User short uploads Playlist `{user_short_uploads_playlist_id}`: Not Found or No uploaded Shorts Available")
return None

Check warning on line 358 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L357-L358

Added lines #L357 - L358 were not covered by tests

except requests.RequestException as e:
logger.error(f"Request failed: {e}")
return None

Check warning on line 362 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L360-L362

Added lines #L360 - L362 were not covered by tests

def get_user_long_uploads_playlist_id(channel_id):

Check warning on line 364 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L364

Added line #L364 was not covered by tests
'''Return the user's uploaded long videos playlist ID, or None if long videos are not available or if an error occurs'''

user_long_uploads_playlist_id = "UULF" + channel_id[2:] # Generate the long videos playlist ID

Check warning on line 367 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L367

Added line #L367 was not covered by tests

'''Make the API request to get the playlist details to determine whether long videos are available on the channel'''

Check warning on line 369 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L369

Added line #L369 was not covered by tests

try:
req = requests.get(

Check warning on line 372 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L371-L372

Added lines #L371 - L372 were not covered by tests
PLAYLIST_API,
params={"id": user_long_uploads_playlist_id, "part": "snippet", "key": YOUTUBE.api_key},
timeout=REQUEST_TIMEOUT,
)

# Check for HTTP error response
if req.status_code >= HTTPStatus.BAD_REQUEST:
logger.error(f"HTTP {req.status_code} Error response: {req.text}")
req.raise_for_status() # Raises an HTTPError if the status code is 4xx or 5xx

Check warning on line 381 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L380-L381

Added lines #L380 - L381 were not covered by tests


# Parse the response
response_json = req.json()
total_results = response_json.get("pageInfo", {}).get("totalResults", 0)
playlist_items = response_json.get("items", [])

Check warning on line 387 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L385-L387

Added lines #L385 - L387 were not covered by tests

# Check if there are no items or totalResults is 0 if yes then long videos not available
if total_results == 0 or not playlist_items:
logger.error(f"User Long uploads Playlist `{user_long_uploads_playlist_id}`: Not Found or No uploaded long videos Available")
return None

Check warning on line 392 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L391-L392

Added lines #L391 - L392 were not covered by tests

# If everything is successful, return the long videos playlist ID
return user_long_uploads_playlist_id

Check warning on line 395 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L395

Added line #L395 was not covered by tests

except IndexError:
logger.error(f"Long videos Playlist `{user_long_uploads_playlist_id}`: Not Found or No long videos Available")
return None

Check warning on line 399 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L398-L399

Added lines #L398 - L399 were not covered by tests

except requests.RequestException as e:
logger.error(f"Request failed: {e}")
return None

Check warning on line 403 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L401-L403

Added lines #L401 - L403 were not covered by tests

def get_user_lives_playlist_id(channel_id):

Check warning on line 405 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L405

Added line #L405 was not covered by tests
'''Return the user's lives playlist ID, or None if lives are not available or if an error occurs'''

user_lives_playlist_id = "UULV" + channel_id[2:] # Generate the lives playlist ID

Check warning on line 408 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L408

Added line #L408 was not covered by tests

'''Make the API request to get the playlist details to determine whether Lives are available on the channel'''

Check warning on line 410 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L410

Added line #L410 was not covered by tests

try:
req = requests.get(

Check warning on line 413 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L412-L413

Added lines #L412 - L413 were not covered by tests
PLAYLIST_API,
params={"id": user_lives_playlist_id, "part": "snippet", "key": YOUTUBE.api_key},
timeout=REQUEST_TIMEOUT,
)

# Check for HTTP error response
if req.status_code >= HTTPStatus.BAD_REQUEST:
logger.error(f"HTTP {req.status_code} Error response: {req.text}")
req.raise_for_status() # Raises an HTTPError if the status code is 4xx or 5xx

Check warning on line 422 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L421-L422

Added lines #L421 - L422 were not covered by tests

# Parse the response
response_json = req.json()
total_results = response_json.get("pageInfo", {}).get("totalResults", 0)
playlist_items = response_json.get("items", [])

Check warning on line 427 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L425-L427

Added lines #L425 - L427 were not covered by tests

# Check if there are no items or totalResults is 0 if yes then lives not available
if total_results == 0 or not playlist_items:
logger.error(f"User lives Playlist `{user_lives_playlist_id}`: Not Found or No lives Available")
return None

Check warning on line 432 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L431-L432

Added lines #L431 - L432 were not covered by tests

# If everything is successful, return the live playlist ID
return user_lives_playlist_id

Check warning on line 435 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L435

Added line #L435 was not covered by tests

except IndexError:
logger.error(f"Live Playlist `{user_lives_playlist_id}`: Not Found or No lives Available")
return None

Check warning on line 439 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L438-L439

Added lines #L438 - L439 were not covered by tests

except requests.RequestException as e:
logger.error(f"Request failed: {e}")
return None

Check warning on line 443 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L441-L443

Added lines #L441 - L443 were not covered by tests


def extract_playlists_details_from(collection_type, youtube_id):
"""prepare a list of Playlist from user request

Expand All @@ -335,6 +459,25 @@

# retrieve list of playlists for that channel
playlist_ids = [p["id"] for p in get_channel_playlists_json(main_channel_id)]

# Retrieve the shorts,long videos and lives playlist ID
user_long_uploads_playlist_id = get_user_long_uploads_playlist_id(main_channel_id)
benoit74 marked this conversation as resolved.
Show resolved Hide resolved
user_short_uploads_playlist_id = get_user_short_uploads_playlist_id(main_channel_id)
user_lives_playlist_id = get_user_lives_playlist_id(main_channel_id)

Check warning on line 466 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L464-L466

Added lines #L464 - L466 were not covered by tests


if user_long_uploads_playlist_id is not None:
benoit74 marked this conversation as resolved.
Show resolved Hide resolved
# include uploads long videos playlist (contains every long videos)
playlist_ids += [user_long_uploads_playlist_id]

Check warning on line 471 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L471

Added line #L471 was not covered by tests

if user_short_uploads_playlist_id is not None:
# include uploads short playlist (contains every shorts)
playlist_ids += [user_short_uploads_playlist_id]

Check warning on line 475 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L475

Added line #L475 was not covered by tests

if user_lives_playlist_id is not None:
# include lives playlist (contains every lives)
playlist_ids += [user_lives_playlist_id]

Check warning on line 479 in scraper/src/youtube2zim/youtube.py

View check run for this annotation

Codecov / codecov/patch

scraper/src/youtube2zim/youtube.py#L479

Added line #L479 was not covered by tests

# we always include uploads playlist (contains everything)
playlist_ids += [channel_json["contentDetails"]["relatedPlaylists"]["uploads"]]
uploads_playlist_id = playlist_ids[-1]
Expand All @@ -349,4 +492,7 @@
[Playlist.from_id(playlist_id) for playlist_id in dict.fromkeys(playlist_ids)],
main_channel_id,
uploads_playlist_id,
user_long_uploads_playlist_id,
user_short_uploads_playlist_id,
user_lives_playlist_id,
)
35 changes: 35 additions & 0 deletions zimui/src/assets/vjs-youtube.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,38 @@
.vjs-youtube .vjs-tech canvas {
border-radius: 8px;
}


.video-js.vjs-fluid,
.video-js.vjs-16-9,
.video-js.vjs-4-3,
video.video-js,
video.vjs-tech {
max-height: calc(100vh - 64px);
position: relative !important;
width: 100%;
height: auto !important;
max-width: 100% !important;
padding-top: 0 !important;
line-height: 0;
}
.vjs-control-bar {
line-height: 1;
}

/* Fullscreen styles */
.video-js.vjs-fullscreen {
display: flex;
align-items: center;
justify-content: center;
background-color: black;
text-align: center;
}

.video-js.vjs-fullscreen video {
margin: auto;
width: auto !important;
height: 100% !important;
max-height: 100vh;
object-fit: contain;
}
35 changes: 23 additions & 12 deletions zimui/src/components/channel/ChannelHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,34 @@ onMounted(async () => {
}
})

const tabs = [
{
id: 0,
title: 'Videos',
to: { name: 'videos' }
},
{
id: 1,
title: 'Playlists',
to: { name: 'playlists' }
// Computed tabs array based on store data
const tabs = computed(() => {
const baseTabs = [
{ id: 0, title: 'Home', to: { name: 'home' } }
];

if (main.channel?.userLongUploadsPlaylist) {
baseTabs.push({ id: 1, title: 'Videos', to: { name: 'videos' } });
}

if (main.channel?.userShortUploadsPlaylist) {
baseTabs.push({ id: 2, title: 'Shorts', to: { name: 'shorts' } });
}

if (main.channel?.userLivesPlaylist) {
baseTabs.push({ id: 3, title: 'Lives', to: { name: 'lives' } });
}
]

baseTabs.push({ id: 4, title: 'Playlists', to: { name: 'playlists' } });

return baseTabs;
});


// Hide tabs if there is only one playlist
const hideTabs = computed(() => main.channel?.playlistCount === 1)

const tab = ref<number>(tabs[0].id)
const tab = ref<number>(tabs.value[0]?.id || 0);
</script>

<template>
Expand Down
Loading
Loading