123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530 |
- from django.contrib.auth.models import User
- from .util import *
- import pytz
- from django.db import models
- from googleapiclient.discovery import build
- import googleapiclient.errors
- from django.db.models import Q, Sum
- import logging
- logger = logging.getLogger(__name__)
- def get_message_from_httperror(e):
- return e.error_details[0]['message']
- class PlaylistManager(models.Manager):
- # def getCredentials(self, user):
- # app = SocialApp.objects.get(provider='google')
- # credentials = Credentials(
- # token=user.profile.access_token,
- # refresh_token=user.profile.refresh_token,
- # token_uri='https://oauth2.googleapis.com/token',
- # client_id=app.client_id,
- # client_secret=app.secret,
- # scopes=['https://www.googleapis.com/auth/youtube']
- # )
- #
- # if not credentials.valid:
- # credentials.refresh(Request())
- # user.profile.access_token = credentials.token
- # user.profile.refresh_token = credentials.refresh_token
- # user.save()
- #
- # return credentials
- def getPlaylistId(self, playlist_link):
- if '?' not in playlist_link:
- return playlist_link
- temp = playlist_link.split('?')[-1].split('&')
- for el in temp:
- if 'list=' in el:
- return el.split('list=')[-1]
- # Used to check if the user has a vaild YouTube channel
- # Will return -1 if user does not have a YouTube channel
- def getUserYTChannelID(self, user):
- user_profile = user.profile
- if user_profile.yt_channel_id != '':
- return 0
- credentials = user_profile.get_credentials()
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.channels().list(
- part=
- 'id,topicDetails,status,statistics,snippet,localizations,contentOwnerDetails,contentDetails,brandingSettings',
- mine=True # get playlist details for this user's playlists
- )
- pl_response = pl_request.execute()
- logger.debug(pl_response)
- if pl_response['pageInfo']['totalResults'] == 0:
- logger.warning(
- 'Looks like do not have a channel on youtube. Create one to import all of your playlists. Retry?'
- )
- return -1
- else:
- user_profile.yt_channel_id = pl_response['items'][0]['id']
- user_profile.save(update_fields=['yt_channel_id'])
- return 0
- # Set pl_id as None to retrive all the playlists from authenticated user. Playlists already imported will be skipped by default.
- # Set pl_id = <valid playlist id>, to import that specific playlist into the user's account
- def initializePlaylist(self, user, pl_id=None):
- """
- Retrieves all of user's playlists from YT and stores them in the Playlist model. Note: only stores
- the few of the columns of each playlist in every row, and has is_in_db column as false as no videos will be
- saved yet.
- :param user: django User object
- :param pl_id:
- :return:
- """
- result = {
- 'status': 0,
- 'num_of_playlists': 0,
- 'first_playlist_name': 'N/A',
- 'error_message': '',
- 'playlist_ids': []
- }
- credentials = user.profile.get_credentials()
- playlist_ids = []
- with build('youtube', 'v3', credentials=credentials) as youtube:
- if pl_id is not None:
- pl_request = youtube.playlists().list(
- part='contentDetails, snippet, id, player, status',
- id=pl_id, # get playlist details for this playlist id
- maxResults=50
- )
- else:
- logger.debug('GETTING ALL USER AUTH PLAYLISTS')
- pl_request = youtube.playlists().list(
- part='contentDetails, snippet, id, player, status',
- mine=True, # get playlist details for this playlist id
- maxResults=50
- )
- # execute the above request, and store the response
- try:
- pl_response = pl_request.execute()
- except googleapiclient.errors.HttpError as e:
- logger.debug('YouTube channel not found if mine=True')
- logger.debug('YouTube playlist not found if id=playlist_id')
- result['status'] = -1
- result['error_message'] = get_message_from_httperror(e)
- return result
- logger.debug(pl_response)
- if pl_response['pageInfo']['totalResults'] == 0:
- logger.warning('No playlists created yet on youtube.')
- result['status'] = -2
- return result
- playlist_items = []
- for item in pl_response['items']:
- playlist_items.append(item)
- if pl_id is None:
- while True:
- try:
- pl_request = youtube.playlists().list_next(pl_request, pl_response)
- pl_response = pl_request.execute()
- for item in pl_response['items']:
- playlist_items.append(item)
- except AttributeError:
- break
- result['num_of_playlists'] = len(playlist_items)
- result['first_playlist_name'] = playlist_items[0]['snippet']['title']
- for item in playlist_items:
- playlist_id = item['id']
- playlist_ids.append(playlist_id)
- # check if this playlist already exists in user's untube collection
- if user.playlists.filter(Q(playlist_id=playlist_id) & Q(is_in_db=True)).exists():
- playlist = user.playlists.get(playlist_id=playlist_id)
- logger.debug(f'PLAYLIST {playlist.name} ({playlist_id}) ALREADY EXISTS IN DB')
- # POSSIBLE CASES:
- # 1. PLAYLIST HAS DUPLICATE VIDEOS, DELETED VIDS, UNAVAILABLE VIDS
- # check if playlist count changed on youtube
- if playlist.video_count != item['contentDetails']['itemCount']:
- playlist.has_playlist_changed = True
- playlist.save(update_fields=['has_playlist_changed'])
- if pl_id is not None:
- result['status'] = -3
- return result
- else: # no such playlist in database
- logger.debug(f'CREATING {item["snippet"]["title"]} ({playlist_id})')
- if user.playlists.filter(Q(playlist_id=playlist_id) & Q(is_in_db=False)).exists():
- unimported_playlist = user.playlists.filter(Q(playlist_id=playlist_id) & Q(is_in_db=False)).first()
- unimported_playlist.delete()
- # MAKE THE PLAYLIST AND LINK IT TO CURRENT_USER
- playlist = Playlist( # create the playlist and link it to current user
- playlist_id=playlist_id,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- published_at=item['snippet']['publishedAt'],
- thumbnail_url=getThumbnailURL(item['snippet']['thumbnails']),
- channel_id=item['snippet']['channelId'] if 'channelId' in
- item['snippet'] else '',
- channel_name=item['snippet']['channelTitle'] if 'channelTitle' in
- item[
- 'snippet'] else '',
- video_count=item['contentDetails']['itemCount'],
- is_private_on_yt=True if item['status']['privacyStatus'] == 'private' else False,
- playlist_yt_player_HTML=item['player']['embedHtml'],
- untube_user=user,
- is_user_owned=True if item['snippet']['channelId'] == user.profile.yt_channel_id else False,
- is_yt_mix=True if ('My Mix' in item['snippet']['title'] or 'Mix -' in item['snippet']['title']) and
- (item['snippet']['channelId'] == 'UCBR8-60-B28hp2BmDPdntcQ') else False
- )
- playlist.save()
- result['playlist_ids'] = playlist_ids
- return result
- def getAllVideosForPlaylist(self, user, playlist_id):
- credentials = user.profile.get_credentials()
- playlist = user.playlists.get(playlist_id=playlist_id)
- # GET ALL VIDEO IDS FROM THE PLAYLIST
- video_ids = [] # stores list of all video ids for a given playlist
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlistItems().list(
- part='contentDetails, snippet, status',
- playlistId=playlist_id, # get all playlist videos details for this playlist id
- maxResults=50
- )
- # execute the above request, and store the response
- pl_response = pl_request.execute()
- for item in pl_response['items']:
- playlist_item_id = item['id']
- video_id = item['contentDetails']['videoId']
- video_ids.append(video_id)
- # video DNE in user's untube:
- # 1. create and save the video in user's untube
- # 2. add it to playlist
- # 3. make a playlist item which is linked to the video
- if not user.videos.filter(video_id=video_id).exists():
- if item['snippet']['title'] == 'Deleted video' or item['snippet'][
- 'description'] == 'This video is unavailable.' or item['snippet']['title'] == 'Private video' or \
- item['snippet']['description'] == 'This video is private.':
- video = Video(
- video_id=video_id,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- is_unavailable_on_yt=True,
- untube_user=user
- )
- video.save()
- else:
- video = Video(
- video_id=video_id,
- published_at=item['contentDetails']['videoPublishedAt']
- if 'videoPublishedAt' in item['contentDetails'] else None,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- thumbnail_url=getThumbnailURL(item['snippet']['thumbnails']),
- channel_id=item['snippet']['videoOwnerChannelId'],
- channel_name=item['snippet']['videoOwnerChannelTitle'],
- untube_user=user
- )
- video.save()
- playlist.videos.add(video)
- playlist_item = PlaylistItem(
- playlist_item_id=playlist_item_id,
- published_at=item['snippet']['publishedAt'] if 'publishedAt' in item['snippet'] else None,
- channel_id=item['snippet']['channelId'],
- channel_name=item['snippet']['channelTitle'],
- video_position=item['snippet']['position'],
- playlist=playlist,
- video=video
- )
- playlist_item.save()
- else: # video found in user's db
- if playlist.playlist_items.filter(playlist_item_id=playlist_item_id).exists():
- logger.debug('PLAYLIST ITEM ALREADY EXISTS')
- continue
- video = user.videos.get(video_id=video_id)
- # if video already in playlist.videos
- is_duplicate = False
- if playlist.videos.filter(video_id=video_id).exists():
- is_duplicate = True
- else:
- playlist.videos.add(video)
- playlist_item = PlaylistItem(
- playlist_item_id=playlist_item_id,
- published_at=item['snippet']['publishedAt'] if 'publishedAt' in item['snippet'] else None,
- channel_id=item['snippet']['channelId'] if 'channelId' in item['snippet'] else None,
- channel_name=item['snippet']['channelTitle'] if 'channelTitle' in item['snippet'] else None,
- video_position=item['snippet']['position'],
- playlist=playlist,
- video=video,
- is_duplicate=is_duplicate
- )
- playlist_item.save()
- # check if the video became unavailable on youtube
- if not video.is_unavailable_on_yt and not video.was_deleted_on_yt and (
- item['snippet']['title'] == 'Deleted video' or
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' or
- item['snippet']['description'] == 'This video is private.'
- ):
- video.was_deleted_on_yt = True
- video.save(update_fields=['was_deleted_on_yt'])
- while True:
- try:
- pl_request = youtube.playlistItems().list_next(pl_request, pl_response)
- pl_response = pl_request.execute()
- for item in pl_response['items']:
- playlist_item_id = item['id']
- video_id = item['contentDetails']['videoId']
- video_ids.append(video_id)
- # video DNE in user's untube:
- # 1. create and save the video in user's untube
- # 2. add it to playlist
- # 3. make a playlist item which is linked to the video
- if not user.videos.filter(video_id=video_id).exists():
- if item['snippet']['title'] == 'Deleted video' or item['snippet'][
- 'description'] == 'This video is unavailable.' or item['snippet'][
- 'title'] == 'Private video' or \
- item['snippet']['description'] == 'This video is private.':
- video = Video(
- video_id=video_id,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- is_unavailable_on_yt=True,
- untube_user=user
- )
- video.save()
- else:
- video = Video(
- video_id=video_id,
- published_at=item['contentDetails']['videoPublishedAt']
- if 'videoPublishedAt' in item['contentDetails'] else None,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- thumbnail_url=getThumbnailURL(item['snippet']['thumbnails']),
- channel_id=item['snippet']['videoOwnerChannelId'],
- channel_name=item['snippet']['videoOwnerChannelTitle'],
- untube_user=user
- )
- video.save()
- playlist.videos.add(video)
- playlist_item = PlaylistItem(
- playlist_item_id=playlist_item_id,
- published_at=item['snippet']['publishedAt']
- if 'publishedAt' in item['snippet'] else None,
- channel_id=item['snippet']['channelId'],
- channel_name=item['snippet']['channelTitle'],
- video_position=item['snippet']['position'],
- playlist=playlist,
- video=video
- )
- playlist_item.save()
- else: # video found in user's db
- video = user.videos.get(video_id=video_id)
- # if video already in playlist.videos
- is_duplicate = False
- if playlist.videos.filter(video_id=video_id).exists():
- is_duplicate = True
- else:
- playlist.videos.add(video)
- playlist_item = PlaylistItem(
- playlist_item_id=playlist_item_id,
- published_at=item['snippet']['publishedAt']
- if 'publishedAt' in item['snippet'] else None,
- channel_id=item['snippet']['channelId'] if 'channelId' in item['snippet'] else None,
- channel_name=item['snippet']['channelTitle']
- if 'channelTitle' in item['snippet'] else None,
- video_position=item['snippet']['position'],
- playlist=playlist,
- video=video,
- is_duplicate=is_duplicate
- )
- playlist_item.save()
- # check if the video became unavailable on youtube
- if not video.is_unavailable_on_yt and not video.was_deleted_on_yt and (
- item['snippet']['title'] == 'Deleted video' or
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' or
- item['snippet']['description'] == 'This video is private.'
- ):
- video.was_deleted_on_yt = True
- video.save(update_fields=['was_deleted_on_yt'])
- except AttributeError:
- break
- # API expects the video ids to be a string of comma seperated values, not a python list
- video_ids_strings = getVideoIdsStrings(video_ids)
- # store duration of all the videos in the playlist
- vid_durations = []
- for video_ids_string in video_ids_strings:
- # query the videos resource using API with the string above
- vid_request = youtube.videos().list(
- part='contentDetails,player,snippet,statistics', # get details of eac video
- id=video_ids_string,
- maxResults=50,
- )
- vid_response = vid_request.execute()
- for item in vid_response['items']:
- duration = item['contentDetails']['duration']
- vid = playlist.videos.get(video_id=item['id'])
- if playlist_id == 'LL':
- vid.liked = True
- vid.name = item['snippet']['title']
- vid.description = item['snippet']['description']
- vid.thumbnail_url = getThumbnailURL(item['snippet']['thumbnails'])
- vid.duration = duration.replace('PT', '')
- vid.duration_in_seconds = calculateDuration([duration])
- vid.has_cc = True if item['contentDetails']['caption'].lower() == 'true' else False
- vid.view_count = item['statistics']['viewCount'] if 'viewCount' in item['statistics'] else -1
- vid.like_count = item['statistics']['likeCount'] if 'likeCount' in item['statistics'] else -1
- vid.dislike_count = item['statistics']['dislikeCount'] if 'dislikeCount' in item['statistics'
- ] else -1
- vid.comment_count = item['statistics']['commentCount'] if 'commentCount' in item['statistics'
- ] else -1
- vid.yt_player_HTML = item['player']['embedHtml'] if 'embedHtml' in item['player'] else ''
- vid.save()
- vid_durations.append(duration)
- playlist_duration_in_seconds = calculateDuration(vid_durations)
- playlist.playlist_duration_in_seconds = playlist_duration_in_seconds
- playlist.playlist_duration = getHumanizedTimeString(playlist_duration_in_seconds)
- playlist.is_in_db = True
- playlist.last_accessed_on = datetime.datetime.now(pytz.utc)
- playlist.save()
- # Returns True if the video count for a playlist on UnTube and video count on same playlist on YouTube is different
- def checkIfPlaylistChangedOnYT(self, user, pl_id):
- """
- If full_scan is true, the whole playlist (i.e each and every video from the PL on YT and PL on UT, is scanned and compared)
- is scanned to see if there are any missing/deleted/newly added videos. This will be only be done
- weekly by looking at the playlist.last_full_scan_at
- If full_scan is False, only the playlist count difference on YT and UT is checked on every visit
- to the playlist page. This is done everytime.
- """
- credentials = user.profile.get_credentials()
- playlist = user.playlists.get(playlist_id=pl_id)
- # if its been a week since the last full scan, do a full playlist scan
- # basically checks all the playlist video for any updates
- if playlist.last_full_scan_at + datetime.timedelta(minutes=1) < datetime.datetime.now(pytz.utc):
- logger.debug('DOING A FULL SCAN')
- current_playlist_item_ids = [
- playlist_item.playlist_item_id for playlist_item in playlist.playlist_items.all()
- ]
- deleted_videos, unavailable_videos, added_videos = 0, 0, 0
- # GET ALL VIDEO IDS FROM THE PLAYLIST
- video_ids = [] # stores list of all video ids for a given playlist
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlistItems().list(
- part='contentDetails, snippet, status',
- playlistId=pl_id, # get all playlist videos details for this playlist id
- maxResults=50
- )
- # execute the above request, and store the response
- try:
- pl_response = pl_request.execute()
- except googleapiclient.errors.HttpError as e:
- if e.status_code == 404: # playlist not found
- return [-1, 'Playlist not found!']
- for item in pl_response['items']:
- playlist_item_id = item['id']
- video_id = item['contentDetails']['videoId']
- if not playlist.playlist_items.filter(
- playlist_item_id=playlist_item_id
- ).exists(): # if playlist item DNE in playlist, a new vid added to playlist
- added_videos += 1
- video_ids.append(video_id)
- else: # playlist_item found in playlist
- if playlist_item_id in current_playlist_item_ids:
- video_ids.append(video_id)
- current_playlist_item_ids.remove(playlist_item_id)
- video = playlist.videos.get(video_id=video_id)
- # check if the video became unavailable on youtube
- if not video.is_unavailable_on_yt and not video.was_deleted_on_yt:
- if (
- item['snippet']['title'] == 'Deleted video' or
- item['snippet']['description'] == 'This video is unavailable.' or
- item['snippet']['title'] == 'Private video' or
- item['snippet']['description'] == 'This video is private.'
- ):
- unavailable_videos += 1
- while True:
- try:
- pl_request = youtube.playlistItems().list_next(pl_request, pl_response)
- pl_response = pl_request.execute()
- for item in pl_response['items']:
- playlist_item_id = item['id']
- video_id = item['contentDetails']['videoId']
- if not playlist.playlist_items.filter(
- playlist_item_id=playlist_item_id
- ).exists(): # if playlist item DNE in playlist, a new vid added to playlist
- added_videos += 1
- video_ids.append(video_id)
- else: # playlist_item found in playlist
- if playlist_item_id in current_playlist_item_ids:
- video_ids.append(video_id)
- current_playlist_item_ids.remove(playlist_item_id)
- video = playlist.videos.get(video_id=video_id)
- # check if the video became unavailable on youtube
- if not video.is_unavailable_on_yt and not video.was_deleted_on_yt:
- if (
- item['snippet']['title'] == 'Deleted video' or
- item['snippet']['description'] == 'This video is unavailable.' or
- item['snippet']['title'] == 'Private video' or
- item['snippet']['description'] == 'This video is private.'
- ):
- unavailable_videos += 1
- except AttributeError:
- break
- # playlist.last_full_scan_at = datetime.datetime.now(pytz.utc)
- playlist.save()
- deleted_videos = len(current_playlist_item_ids) # left out video ids
- return [1, deleted_videos, unavailable_videos, added_videos]
- else:
- logger.warning(
- 'YOU CAN DO A FULL SCAN AGAIN IN',
- str(datetime.datetime.now(pytz.utc) - (playlist.last_full_scan_at + datetime.timedelta(minutes=1)))
- )
- """
- print_('DOING A SMOL SCAN')
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlists().list(
- part='contentDetails, snippet, id, status',
- id=pl_id, # get playlist details for this playlist id
- maxResults=50
- )
- # execute the above request, and store the response
- try:
- pl_response = pl_request.execute()
- except googleapiclient.errors.HttpError:
- print_('YouTube channel not found if mine=True')
- print_('YouTube playlist not found if id=playlist_id')
- return -1
- print_('PLAYLIST', pl_response)
- playlist_items = []
- for item in pl_response['items']:
- playlist_items.append(item)
- while True:
- try:
- pl_request = youtube.playlists().list_next(pl_request, pl_response)
- pl_response = pl_request.execute()
- for item in pl_response['items']:
- playlist_items.append(item)
- except AttributeError:
- break
- for item in playlist_items:
- playlist_id = item['id']
- # check if this playlist already exists in database
- if user.playlists.filter(playlist_id=playlist_id).exists():
- playlist = user.playlists.get(playlist_id__exact=playlist_id)
- print_(f'PLAYLIST {playlist.name} ALREADY EXISTS IN DB')
- # POSSIBLE CASES:
- # 1. PLAYLIST HAS DUPLICATE VIDEOS, DELETED VIDS, UNAVAILABLE VIDS
- # check if playlist changed on youtube
- if playlist.video_count != item['contentDetails']['itemCount']:
- playlist.has_playlist_changed = True
- playlist.save()
- return [-1, item['contentDetails']['itemCount']]
- """
- return [0, 'no change']
- def updatePlaylist(self, user, playlist_id):
- credentials = user.profile.get_credentials()
- playlist = user.playlists.get(playlist_id__exact=playlist_id)
- # current_video_ids = [playlist_item.video.video_id for playlist_item in playlist.playlist_items.all()]
- current_playlist_item_ids = [playlist_item.playlist_item_id for playlist_item in playlist.playlist_items.all()]
- updated_playlist_video_count = 0
- deleted_playlist_item_ids, unavailable_videos, added_videos = [], [], []
- # GET ALL VIDEO IDS FROM THE PLAYLIST
- video_ids = [] # stores list of all video ids for a given playlist
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlistItems().list(
- part='contentDetails, snippet, status',
- playlistId=playlist_id, # get all playlist videos details for this playlist id
- maxResults=50
- )
- # execute the above request, and store the response
- try:
- pl_response = pl_request.execute()
- except googleapiclient.errors.HttpError:
- logger.info('Playist was deleted on YouTube')
- return [-1, [], [], []]
- logger.debug('ESTIMATED VIDEO IDS FROM RESPONSE', len(pl_response['items']))
- updated_playlist_video_count += len(pl_response['items'])
- for item in pl_response['items']:
- playlist_item_id = item['id']
- video_id = item['contentDetails']['videoId']
- video_ids.append(video_id)
- # check if new playlist item added
- if not playlist.playlist_items.filter(playlist_item_id=playlist_item_id).exists():
- # if video dne in user's db at all, create and save it
- if not user.videos.filter(video_id=video_id).exists():
- if (
- item['snippet']['title'] == 'Deleted video' and
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' and
- item['snippet']['description'] == 'This video is private.'
- ):
- video = Video(
- video_id=video_id,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- is_unavailable_on_yt=True,
- untube_user=user
- )
- video.save()
- else:
- video = Video(
- video_id=video_id,
- published_at=item['contentDetails']['videoPublishedAt']
- if 'videoPublishedAt' in item['contentDetails'] else None,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- thumbnail_url=getThumbnailURL(item['snippet']['thumbnails']),
- channel_id=item['snippet']['videoOwnerChannelId'],
- channel_name=item['snippet']['videoOwnerChannelTitle'],
- untube_user=user
- )
- video.save()
- video = user.videos.get(video_id=video_id)
- # check if the video became unavailable on youtube
- if not video.is_unavailable_on_yt and not video.was_deleted_on_yt and (
- item['snippet']['title'] == 'Deleted video' and
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' and
- item['snippet']['description'] == 'This video is private.'
- ):
- video.was_deleted_on_yt = True
- is_duplicate = False
- if not playlist.videos.filter(video_id=video_id).exists():
- playlist.videos.add(video)
- else:
- is_duplicate = True
- playlist_item = PlaylistItem(
- playlist_item_id=playlist_item_id,
- published_at=item['snippet']['publishedAt'] if 'publishedAt' in item['snippet'] else None,
- channel_id=item['snippet']['channelId'] if 'channelId' in item['snippet'] else None,
- channel_name=item['snippet']['channelTitle'] if 'channelTitle' in item['snippet'] else None,
- video_position=item['snippet']['position'],
- playlist=playlist,
- video=video,
- is_duplicate=is_duplicate
- )
- playlist_item.save()
- video.video_details_modified = True
- video.video_details_modified_at = datetime.datetime.now(tz=pytz.utc)
- video.save(
- update_fields=['video_details_modified', 'video_details_modified_at', 'was_deleted_on_yt']
- )
- added_videos.append(video)
- else: # if playlist item already in playlist
- current_playlist_item_ids.remove(playlist_item_id)
- playlist_item = playlist.playlist_items.get(playlist_item_id=playlist_item_id)
- playlist_item.video_position = item['snippet']['position']
- playlist_item.save(update_fields=['video_position'])
- # check if the video became unavailable on youtube
- if not playlist_item.video.is_unavailable_on_yt and not playlist_item.video.was_deleted_on_yt:
- if (
- item['snippet']['title'] == 'Deleted video' and
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' and
- item['snippet']['description'] == 'This video is private.'
- ):
- playlist_item.video.was_deleted_on_yt = True # video went private on YouTube
- playlist_item.video.video_details_modified = True
- playlist_item.video.video_details_modified_at = datetime.datetime.now(tz=pytz.utc)
- playlist_item.video.save(
- update_fields=[
- 'was_deleted_on_yt', 'video_details_modified', 'video_details_modified_at'
- ]
- )
- unavailable_videos.append(playlist_item.video)
- while True:
- try:
- pl_request = youtube.playlistItems().list_next(pl_request, pl_response)
- pl_response = pl_request.execute()
- updated_playlist_video_count += len(pl_response['items'])
- for item in pl_response['items']:
- playlist_item_id = item['id']
- video_id = item['contentDetails']['videoId']
- video_ids.append(video_id)
- # check if new playlist item added
- if not playlist.playlist_items.filter(playlist_item_id=playlist_item_id).exists():
- # if video dne in user's db at all, create and save it
- if not user.videos.filter(video_id=video_id).exists():
- if (
- item['snippet']['title'] == 'Deleted video' and
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' and
- item['snippet']['description'] == 'This video is private.'
- ):
- video = Video(
- video_id=video_id,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- is_unavailable_on_yt=True,
- untube_user=user
- )
- video.save()
- else:
- video = Video(
- video_id=video_id,
- published_at=item['contentDetails']['videoPublishedAt']
- if 'videoPublishedAt' in item['contentDetails'] else None,
- name=item['snippet']['title'],
- description=item['snippet']['description'],
- thumbnail_url=getThumbnailURL(item['snippet']['thumbnails']),
- channel_id=item['snippet']['videoOwnerChannelId'],
- channel_name=item['snippet']['videoOwnerChannelTitle'],
- untube_user=user
- )
- video.save()
- video = user.videos.get(video_id=video_id)
- # check if the video became unavailable on youtube
- if not video.is_unavailable_on_yt and not video.was_deleted_on_yt and (
- item['snippet']['title'] == 'Deleted video' and
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' and
- item['snippet']['description'] == 'This video is private.'
- ):
- video.was_deleted_on_yt = True
- is_duplicate = False
- if not playlist.videos.filter(video_id=video_id).exists():
- playlist.videos.add(video)
- else:
- is_duplicate = True
- playlist_item = PlaylistItem(
- playlist_item_id=playlist_item_id,
- published_at=item['snippet']['publishedAt']
- if 'publishedAt' in item['snippet'] else None,
- channel_id=item['snippet']['channelId'] if 'channelId' in item['snippet'] else None,
- channel_name=item['snippet']['channelTitle']
- if 'channelTitle' in item['snippet'] else None,
- video_position=item['snippet']['position'],
- playlist=playlist,
- video=video,
- is_duplicate=is_duplicate
- )
- playlist_item.save()
- video.video_details_modified = True
- video.video_details_modified_at = datetime.datetime.now(tz=pytz.utc)
- video.save(
- update_fields=[
- 'video_details_modified', 'video_details_modified_at', 'was_deleted_on_yt'
- ]
- )
- added_videos.append(video)
- else: # if playlist item already in playlist
- current_playlist_item_ids.remove(playlist_item_id)
- playlist_item = playlist.playlist_items.get(playlist_item_id=playlist_item_id)
- playlist_item.video_position = item['snippet']['position']
- playlist_item.save(update_fields=['video_position'])
- # check if the video became unavailable on youtube
- if not playlist_item.video.is_unavailable_on_yt and not playlist_item.video.was_deleted_on_yt:
- if (
- item['snippet']['title'] == 'Deleted video' and
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' and
- item['snippet']['description'] == 'This video is private.'
- ):
- playlist_item.video.was_deleted_on_yt = True # video went private on YouTube
- playlist_item.video.video_details_modified = True
- playlist_item.video.video_details_modified_at = datetime.datetime.now(tz=pytz.utc)
- playlist_item.video.save(
- update_fields=[
- 'was_deleted_on_yt', 'video_details_modified', 'video_details_modified_at'
- ]
- )
- unavailable_videos.append(playlist_item.video)
- except AttributeError:
- break
- # API expects the video ids to be a string of comma seperated values, not a python list
- video_ids_strings = getVideoIdsStrings(video_ids)
- # store duration of all the videos in the playlist
- vid_durations = []
- for video_ids_string in video_ids_strings:
- # query the videos resource using API with the string above
- vid_request = youtube.videos().list(
- part='contentDetails,player,snippet,statistics', # get details of eac video
- id=video_ids_string,
- maxResults=50
- )
- vid_response = vid_request.execute()
- for item in vid_response['items']:
- duration = item['contentDetails']['duration']
- vid = playlist.videos.get(video_id=item['id'])
- if (
- item['snippet']['title'] == 'Deleted video' or
- item['snippet']['description'] == 'This video is unavailable.'
- ) or (
- item['snippet']['title'] == 'Private video' or
- item['snippet']['description'] == 'This video is private.'
- ):
- vid_durations.append(duration)
- vid.video_details_modified = True
- vid.video_details_modified_at = datetime.datetime.now(tz=pytz.utc)
- vid.save(
- update_fields=[
- 'video_details_modified', 'video_details_modified_at', 'was_deleted_on_yt',
- 'is_unavailable_on_yt'
- ]
- )
- continue
- vid.name = item['snippet']['title']
- vid.description = item['snippet']['description']
- vid.thumbnail_url = getThumbnailURL(item['snippet']['thumbnails'])
- vid.duration = duration.replace('PT', '')
- vid.duration_in_seconds = calculateDuration([duration])
- vid.has_cc = True if item['contentDetails']['caption'].lower() == 'true' else False
- vid.view_count = item['statistics']['viewCount'] if 'viewCount' in item['statistics'] else -1
- vid.like_count = item['statistics']['likeCount'] if 'likeCount' in item['statistics'] else -1
- vid.dislike_count = item['statistics']['dislikeCount'] if 'dislikeCount' in item['statistics'
- ] else -1
- vid.comment_count = item['statistics']['commentCount'] if 'commentCount' in item['statistics'
- ] else -1
- vid.yt_player_HTML = item['player']['embedHtml'] if 'embedHtml' in item['player'] else ''
- vid.save()
- vid_durations.append(duration)
- playlist_duration_in_seconds = calculateDuration(vid_durations)
- playlist.playlist_duration_in_seconds = playlist_duration_in_seconds
- playlist.playlist_duration = getHumanizedTimeString(playlist_duration_in_seconds)
- playlist.has_playlist_changed = False
- playlist.video_count = updated_playlist_video_count
- playlist.has_new_updates = True
- playlist.last_full_scan_at = datetime.datetime.now(pytz.utc)
- playlist.save()
- deleted_playlist_item_ids = current_playlist_item_ids # left out playlist_item_ids
- return [0, deleted_playlist_item_ids, unavailable_videos, added_videos]
- def deletePlaylistFromYouTube(self, user, playlist_id):
- """
- Takes in playlist itemids for the videos in a particular playlist
- """
- credentials = user.profile.get_credentials()
- playlist = user.playlists.get(playlist_id=playlist_id)
- # new_playlist_duration_in_seconds = playlist.playlist_duration_in_seconds
- # new_playlist_video_count = playlist.video_count
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlists().delete(id=playlist_id)
- try:
- pl_response = pl_request.execute()
- logger.debug(pl_response)
- except googleapiclient.errors.HttpError as e: # failed to delete playlist
- # possible causes:
- # playlistForbidden (403)
- # playlistNotFound (404)
- # playlistOperationUnsupported (400)
- logger.debug(e.error_details, e.status_code)
- return [-1, get_message_from_httperror(e), e.status_code]
- # playlistItem was successfully deleted if no HttpError, so delete it from db
- video_ids = [video.video_id for video in playlist.videos.all()]
- playlist.delete()
- for video_id in video_ids:
- video = user.videos.get(video_id=video_id)
- if video.playlists.all().count() == 0:
- video.delete()
- return [0]
- def deletePlaylistItems(self, user, playlist_id, playlist_item_ids):
- """
- Takes in playlist itemids for the videos in a particular playlist
- """
- credentials = user.profile.get_credentials()
- playlist = user.playlists.get(playlist_id=playlist_id)
- playlist_items = user.playlists.get(playlist_id=playlist_id).playlist_items.select_related('video').filter(
- playlist_item_id__in=playlist_item_ids
- )
- new_playlist_duration_in_seconds = playlist.playlist_duration_in_seconds
- new_playlist_video_count = playlist.video_count
- with build('youtube', 'v3', credentials=credentials) as youtube:
- for playlist_item in playlist_items:
- pl_request = youtube.playlistItems().delete(id=playlist_item.playlist_item_id)
- logger.debug(pl_request)
- try:
- pl_response = pl_request.execute()
- logger.debug(pl_response)
- except googleapiclient.errors.HttpError as e: # failed to delete playlist item
- # possible causes:
- # playlistItemsNotAccessible (403)
- # playlistItemNotFound (404)
- # playlistOperationUnsupported (400)
- logger.debug(e, e.error_details, e.status_code)
- continue
- # playlistItem was successfully deleted if no HttpError, so delete it from db
- video = playlist_item.video
- playlist_item.delete()
- if not playlist.playlist_items.filter(video__video_id=video.video_id).exists():
- playlist.videos.remove(video)
- # if video.playlists.all().count() == 0: # also delete the video if it is not found in other playlists
- # video.delete()
- if playlist_id == 'LL':
- video.liked = False
- video.save(update_fields=['liked'])
- new_playlist_video_count -= 1
- new_playlist_duration_in_seconds -= video.duration_in_seconds
- playlist.video_count = new_playlist_video_count
- if new_playlist_video_count == 0:
- playlist.thumbnail_url = ''
- playlist.playlist_duration_in_seconds = new_playlist_duration_in_seconds
- playlist.playlist_duration = getHumanizedTimeString(new_playlist_duration_in_seconds)
- playlist.save(
- update_fields=['video_count', 'playlist_duration', 'playlist_duration_in_seconds', 'thumbnail_url']
- )
- # time.sleep(2)
- playlist_items = playlist.playlist_items.select_related('video').order_by('video_position')
- counter = 0
- videos = []
- for playlist_item in playlist_items:
- playlist_item.video_position = counter
- is_duplicate = False
- if playlist_item.video_id in videos:
- is_duplicate = True
- else:
- videos.append(playlist_item.video_id)
- playlist_item.is_duplicate = is_duplicate
- playlist_item.save(update_fields=['video_position', 'is_duplicate'])
- counter += 1
- def deleteSpecificPlaylistItems(self, user, playlist_id, command):
- playlist = user.playlists.get(playlist_id=playlist_id)
- playlist_items = []
- if command == 'duplicate':
- playlist_items = playlist.playlist_items.filter(is_duplicate=True)
- elif command == 'unavailable':
- playlist_items = playlist.playlist_items.filter(
- Q(video__is_unavailable_on_yt=True) & Q(video__was_deleted_on_yt=False)
- )
- playlist_item_ids = []
- for playlist_item in playlist_items:
- playlist_item_ids.append(playlist_item.playlist_item_id)
- self.deletePlaylistItems(user, playlist_id, playlist_item_ids)
- def createNewPlaylist(self, user, playlist_name, playlist_description):
- """
- Takes in playlist details and creates a new private playlist in the user's account
- """
- credentials = user.profile.get_credentials()
- result = {'status': 0, 'playlist_id': None}
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlists().insert(
- part='snippet,status',
- body={
- 'snippet': {
- 'title': playlist_name,
- 'description': playlist_description,
- 'defaultLanguage': 'en'
- },
- 'status': {
- 'privacyStatus': 'private'
- }
- }
- )
- try:
- pl_response = pl_request.execute()
- except googleapiclient.errors.HttpError as e: # failed to create playlist
- logger.debug(e.status_code, e.error_details)
- if e.status_code == 400: # maxPlaylistExceeded
- result['status'] = 400
- result['status'] = -1
- result['playlist_id'] = pl_response['id']
- return result
- def updatePlaylistDetails(self, user, playlist_id, details):
- """
- Takes in playlist itemids for the videos in a particular playlist
- """
- credentials = user.profile.get_credentials()
- playlist = user.playlists.get(playlist_id=playlist_id)
- with build('youtube', 'v3', credentials=credentials) as youtube:
- pl_request = youtube.playlists().update(
- part='id,snippet,status',
- body={
- 'id': playlist_id,
- 'snippet': {
- 'title': details['title'],
- 'description': details['description'],
- },
- 'status': {
- 'privacyStatus': 'private' if details['privacyStatus'] else 'public'
- }
- },
- )
- logger.debug(details['description'])
- try:
- pl_response = pl_request.execute()
- except googleapiclient.errors.HttpError as e: # failed to update playlist details
- # possible causes:
- # playlistItemsNotAccessible (403)
- # playlistItemNotFound (404)
- # playlistOperationUnsupported (400)
- # errors i ran into:
- # runs into HttpError 400 'Invalid playlist snippet.' when the description contains <, >
- logger.debug('ERROR UPDATING PLAYLIST DETAILS', e, e.status_code, e.error_details)
- return -1
- logger.debug(pl_response)
- playlist.name = pl_response['snippet']['title']
- playlist.description = pl_response['snippet']['description']
- playlist.is_private_on_yt = True if pl_response['status']['privacyStatus'] == 'private' else False
- playlist.save(update_fields=['name', 'description', 'is_private_on_yt'])
- return 0
- def moveCopyVideosFromPlaylist(self, user, from_playlist_id, to_playlist_ids, playlist_item_ids, action='copy'):
- """
- Takes in playlist itemids for the videos in a particular playlist
- """
- credentials = user.profile.get_credentials()
- playlist_items = user.playlists.get(
- playlist_id=from_playlist_id
- ).playlist_items.select_related('video').filter(playlist_item_id__in=playlist_item_ids)
- result = {
- 'status': 0,
- 'num_moved_copied': 0,
- 'playlistContainsMaximumNumberOfVideos': False,
- }
- with build('youtube', 'v3', credentials=credentials) as youtube:
- for playlist_id in to_playlist_ids:
- for playlist_item in playlist_items:
- pl_request = youtube.playlistItems().insert(
- part='snippet',
- body={
- 'snippet': {
- 'playlistId': playlist_id,
- 'position': 0,
- 'resourceId': {
- 'kind': 'youtube#video',
- 'videoId': playlist_item.video.video_id,
- }
- },
- }
- )
- try:
- # pl_response = pl_request.execute()
- pl_request.execute()
- except googleapiclient.errors.HttpError as e: # failed to update playlist details
- # possible causes:
- # playlistItemsNotAccessible (403)
- # playlistItemNotFound (404) - I ran into 404 while trying to copy an unavailable video into another playlist
- # playlistOperationUnsupported (400)
- # errors i ran into:
- # runs into HttpError 400 'Invalid playlist snippet.' when the description contains <, >
- logger.debug('ERROR UPDATING PLAYLIST DETAILS', e.status_code, e.error_details)
- if e.status_code == 400:
- pl_request = youtube.playlistItems().insert(
- part='snippet',
- body={
- 'snippet': {
- 'playlistId': playlist_id,
- 'resourceId': {
- 'kind': 'youtube#video',
- 'videoId': playlist_item.video.video_id,
- }
- },
- }
- )
- try:
- # pl_response = pl_request.execute()
- pl_request.execute()
- except googleapiclient.errors.HttpError as e:
- logger.debug(e)
- result['status'] = -1
- elif e.status_code == 403:
- result['playlistContainsMaximumNumberOfVideos'] = True
- else:
- result['status'] = -1
- result['num_moved_copied'] += 1
- if action == 'move': # delete from the current playlist
- self.deletePlaylistItems(user, from_playlist_id, playlist_item_ids)
- return result
- def addVideosToPlaylist(self, user, playlist_id, video_ids):
- """
- Takes in playlist itemids for the videos in a particular playlist
- """
- credentials = user.profile.get_credentials()
- result = {
- 'num_added': 0,
- 'playlistContainsMaximumNumberOfVideos': False,
- }
- added = 0
- with build('youtube', 'v3', credentials=credentials) as youtube:
- for video_id in video_ids:
- pl_request = youtube.playlistItems().insert(
- part='snippet',
- body={
- 'snippet': {
- 'playlistId': playlist_id,
- 'position': 0,
- 'resourceId': {
- 'kind': 'youtube#video',
- 'videoId': video_id,
- }
- },
- }
- )
- try:
- # pl_response = pl_request.execute()
- pl_request.execute()
- except googleapiclient.errors.HttpError as e: # failed to update add video to playlis
- logger.debug('ERROR ADDDING VIDEOS TO PLAYLIST', e.status_code, e.error_details)
- if e.status_code == 400: # manualSortRequired - see errors https://developers.google.com/youtube/v3/docs/playlistItems/insert
- pl_request = youtube.playlistItems().insert(
- part='snippet',
- body={
- 'snippet': {
- 'playlistId': playlist_id,
- 'resourceId': {
- 'kind': 'youtube#video',
- 'videoId': video_id,
- }
- },
- }
- )
- try:
- # pl_response = pl_request.execute()
- pl_request.execute()
- except googleapiclient.errors.HttpError as e: # failed to update playlist details
- logger.debug(e)
- pass
- elif e.status_code == 403:
- result['playlistContainsMaximumNumberOfVideos'] = True
- continue
- added += 1
- result['num_added'] = added
- try:
- playlist = user.playlists.get(playlist_id=playlist_id)
- if added > 0:
- playlist.has_playlist_changed = True
- playlist.save(update_fields=['has_playlist_changed'])
- except Exception:
- pass
- return result
- class Tag(models.Model):
- name = models.CharField(max_length=69)
- created_by = models.ForeignKey(User, related_name='playlist_tags', on_delete=models.CASCADE, null=True)
- times_viewed = models.IntegerField(default=0)
- times_viewed_per_week = models.IntegerField(default=0)
- # type = models.CharField(max_length=10) # either 'playlist' or 'video'
- last_views_reset = models.DateTimeField(default=datetime.datetime.now)
- created_at = models.DateTimeField(auto_now_add=True)
- updated_at = models.DateTimeField(auto_now=True)
- class Video(models.Model):
- untube_user = models.ForeignKey(User, related_name='videos', on_delete=models.CASCADE, null=True)
- # video details
- video_id = models.CharField(max_length=100)
- name = models.CharField(max_length=100, blank=True)
- duration = models.CharField(max_length=100, blank=True)
- duration_in_seconds = models.BigIntegerField(default=0)
- thumbnail_url = models.TextField(blank=True)
- published_at = models.DateTimeField(blank=True, null=True)
- description = models.TextField(default='')
- has_cc = models.BooleanField(default=False, blank=True, null=True)
- liked = models.BooleanField(default=False) # whether this video liked on YouTube by user or not
- # video stats
- public_stats_viewable = models.BooleanField(default=True)
- view_count = models.BigIntegerField(default=0)
- like_count = models.BigIntegerField(default=0)
- dislike_count = models.BigIntegerField(default=0)
- comment_count = models.BigIntegerField(default=0)
- yt_player_HTML = models.TextField(blank=True)
- # video is made by this channel
- # channel = models.ForeignKey(Channel, related_name='videos', on_delete=models.CASCADE)
- channel_id = models.TextField(blank=True)
- channel_name = models.TextField(blank=True)
- # which playlist this video belongs to, and position of that video in the playlist (i.e ALL videos belong to some pl)
- # playlist = models.ForeignKey(Playlist, related_name='videos', on_delete=models.CASCADE)
- # (moved to playlistItem)
- # is_duplicate = models.BooleanField(default=False) # True if the same video exists more than once in the playlist
- # video_position = models.IntegerField(blank=True)
- # NOTE: For a video in db:
- # 1.) if both is_unavailable_on_yt and was_deleted_on_yt are true,
- # that means the video was originally fine, but then went unavailable when updatePlaylist happened
- # 2.) if only is_unavailable_on_yt is true and was_deleted_on_yt is false,
- # then that means the video was an unavaiable video when initPlaylist was happening
- # 3.) if both is_unavailable_on_yt and was_deleted_on_yt are false, the video is fine, ie up on Youtube
- is_unavailable_on_yt = models.BooleanField(
- default=False
- ) # True if the video was unavailable (private/deleted) when the API call was first made
- was_deleted_on_yt = models.BooleanField(default=False) # True if video became unavailable on a subsequent API call
- is_planned_to_watch = models.BooleanField(default=False) # mark video as plan to watch later
- is_marked_as_watched = models.BooleanField(default=False) # mark video as watched
- is_favorite = models.BooleanField(default=False, blank=True) # mark video as favorite
- num_of_accesses = models.IntegerField(default=0) # tracks num of times this video was clicked on by user
- user_label = models.CharField(max_length=100, blank=True) # custom user given name for this video
- user_notes = models.TextField(blank=True) # user can take notes on the video and save them
- created_at = models.DateTimeField(auto_now_add=True)
- updated_at = models.DateTimeField(auto_now=True)
- # for new videos added/modified/deleted in the playlist
- video_details_modified = models.BooleanField(
- default=False
- ) # is true for videos whose details changed after playlist update
- video_details_modified_at = models.DateTimeField(auto_now_add=True) # to set the above false after a day
- class Playlist(models.Model):
- tags = models.ManyToManyField(Tag, related_name='playlists')
- untube_user = models.ForeignKey(User, related_name='playlists', on_delete=models.CASCADE, null=True)
- # playlist is made by this channel
- channel_id = models.TextField(blank=True)
- channel_name = models.TextField(blank=True)
- # playlist details
- is_yt_mix = models.BooleanField(default=False)
- playlist_id = models.CharField(max_length=150)
- name = models.CharField(max_length=150, blank=True) # YT PLAYLIST NAMES CAN ONLY HAVE MAX OF 150 CHARS
- thumbnail_url = models.TextField(blank=True)
- description = models.TextField(default='No description')
- video_count = models.IntegerField(default=0)
- published_at = models.DateTimeField(blank=True)
- is_private_on_yt = models.BooleanField(default=False)
- videos = models.ManyToManyField(Video, related_name='playlists')
- # eg. '<iframe width=\'640\' height=\'360\' src=\'http://www.youtube.com/embed/videoseries?list=PLFuZstFnF1jFwMDffUhV81h0xeff0TXzm\'
- # frameborder=\'0\' allow=\'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\' allowfullscreen></iframe>'
- playlist_yt_player_HTML = models.TextField(blank=True)
- playlist_duration = models.CharField(max_length=69, blank=True) # string version of playlist dureation
- playlist_duration_in_seconds = models.BigIntegerField(default=0)
- # watch playlist details
- # watch_time_left = models.CharField(max_length=150, default='')
- started_on = models.DateTimeField(auto_now_add=True, null=True)
- last_watched = models.DateTimeField(auto_now_add=True, null=True)
- # manage playlist
- user_notes = models.TextField(default='') # user can take notes on the playlist and save them
- user_label = models.CharField(max_length=100, default='') # custom user given name for this playlist
- marked_as = models.CharField(
- default='none', max_length=100
- ) # can be set to 'none', 'watching', 'on-hold', 'plan-to-watch'
- is_favorite = models.BooleanField(default=False, blank=True) # to mark playlist as fav
- num_of_accesses = models.IntegerField(default='0') # tracks num of times this playlist was opened by user
- last_accessed_on = models.DateTimeField(default=datetime.datetime.now)
- is_user_owned = models.BooleanField(default=True) # represents YouTube playlist owned by user
- # set playlist manager
- objects = PlaylistManager()
- # playlist settings (moved to global preferences)
- # hide_unavailable_videos = models.BooleanField(default=False)
- # confirm_before_deleting = models.BooleanField(default=True)
- auto_check_for_updates = models.BooleanField(default=False)
- # for import
- is_in_db = models.BooleanField(default=False) # is true when all the videos of a playlist have been imported
- created_at = models.DateTimeField(auto_now_add=True)
- updated_at = models.DateTimeField(auto_now=True)
- # for updates
- last_full_scan_at = models.DateTimeField(auto_now_add=True)
- has_playlist_changed = models.BooleanField(default=False) # determines whether playlist was modified online or not
- has_new_updates = models.BooleanField(default=False) # meant to keep track of newly added/unavailable videos
- def __str__(self):
- return str(self.playlist_id)
- def has_unavailable_videos(self):
- if self.playlist_items.filter(Q(video__is_unavailable_on_yt=True) &
- Q(video__was_deleted_on_yt=False)).exists():
- return True
- return False
- def has_duplicate_videos(self):
- if self.playlist_items.filter(is_duplicate=True).exists():
- return True
- return False
- def get_channels_list(self):
- channels_list = []
- num_channels = 0
- for video in self.videos.all():
- channel = video.channel_name
- if channel not in channels_list:
- channels_list.append(channel)
- num_channels += 1
- return [num_channels, channels_list]
- # def generate_playlist_thumbnail_url(self):
- # """
- # Generates a playlist thumnail url based on the playlist name
- # """
- # pl_name = self.name
- # response = requests.get(
- # f'https://api.unsplash.com/search/photos/?client_id={SECRETS['UNSPLASH_API_ACCESS_KEY']}&page=1&query={pl_name}')
- # image = response.json()['results'][0]['urls']['small']
- #
- # print_(image)
- #
- # return image
- def get_playlist_thumbnail_url(self):
- playlist_items = self.playlist_items.filter(
- Q(video__was_deleted_on_yt=False) & Q(video__is_unavailable_on_yt=False)
- )
- if playlist_items.exists():
- return playlist_items.first().video.thumbnail_url
- else:
- return 'https://i.ytimg.com/vi/9219YrnwDXE/maxresdefault.jpg'
- def get_unavailable_videos_count(self):
- return self.video_count - self.get_watchable_videos_count()
- def get_duplicate_videos_count(self):
- return self.playlist_items.filter(is_duplicate=True).count()
- # return count of watchable videos, i.e # videos that are not private or deleted in the playlist
- def get_watchable_videos_count(self):
- return self.playlist_items.filter(
- Q(is_duplicate=False) & Q(video__is_unavailable_on_yt=False) & Q(video__was_deleted_on_yt=False)
- ).count()
- def get_watched_videos_count(self):
- return self.playlist_items.filter(
- Q(is_duplicate=False) & Q(video__is_marked_as_watched=True) & Q(video__is_unavailable_on_yt=False) &
- Q(video__was_deleted_on_yt=False)
- ).count()
- # diff of time from when playlist was first marked as watched and playlist reached 100% completion
- def get_finish_time(self):
- return self.last_watched - self.started_on
- def get_watch_time_left(self):
- unwatched_playlist_items_secs = self.playlist_items.filter(
- Q(is_duplicate=False) & Q(video__is_marked_as_watched=False) & Q(video__is_unavailable_on_yt=False) &
- Q(video__was_deleted_on_yt=False)
- ).aggregate(Sum('video__duration_in_seconds'))['video__duration_in_seconds__sum']
- watch_time_left = getHumanizedTimeString(
- unwatched_playlist_items_secs
- ) if unwatched_playlist_items_secs is not None else getHumanizedTimeString(0)
- return watch_time_left
- # return 0 if playlist empty or all videos in playlist are unavailable
- def get_percent_complete(self):
- total_playlist_video_count = self.get_watchable_videos_count()
- watched_videos = self.playlist_items.filter(
- Q(is_duplicate=False) & Q(video__is_marked_as_watched=True) & Q(video__is_unavailable_on_yt=False) &
- Q(video__was_deleted_on_yt=False)
- )
- num_videos_watched = watched_videos.count()
- percent_complete = round((num_videos_watched / total_playlist_video_count) *
- 100, 1) if total_playlist_video_count != 0 else 0
- return percent_complete
- def all_videos_unavailable(self):
- all_vids_unavailable = False
- if self.videos.filter(Q(is_unavailable_on_yt=True) | Q(was_deleted_on_yt=True)).count() == self.video_count:
- all_vids_unavailable = True
- return all_vids_unavailable
- class PlaylistItem(models.Model):
- playlist = models.ForeignKey(
- Playlist, related_name='playlist_items', on_delete=models.CASCADE, null=True
- ) # playlist this pl item belongs to
- video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True)
- # details
- playlist_item_id = models.CharField(max_length=100) # the item id of the playlist this video beo
- video_position = models.IntegerField(blank=True) # video position in the playlist
- published_at = models.DateTimeField(
- default=datetime.datetime.now
- ) # snippet.publishedAt - The date and time that the item was added to the playlist
- channel_id = models.CharField(
- null=True, max_length=250
- ) # snippet.channelId - The ID that YouTube uses to uniquely identify the user that added the item to the playlist.
- channel_name = models.CharField(
- null=True, max_length=250
- ) # snippet.channelTitle - The channel title of the channel that the playlist item belongs to.
- # video_owner_channel_id = models.CharField(max_length=100)
- # video_owner_channel_title = models.CharField(max_length=100)
- is_duplicate = models.BooleanField(default=False) # True if the same video exists more than once in the playlist
- is_marked_as_watched = models.BooleanField(default=False, blank=True) # mark video as watched
- num_of_accesses = models.IntegerField(default=0) # tracks num of times this video was clicked on by user
- # for new videos added/modified/deleted in the playlist
- # video_details_modified = models.BooleanField(
- # default=False) # is true for videos whose details changed after playlist update
- # video_details_modified_at = models.DateTimeField(auto_now_add=True) # to set the above false after a day
- created_at = models.DateTimeField(auto_now_add=True)
- updated_at = models.DateTimeField(auto_now=True)
- class Pin(models.Model):
- untube_user = models.ForeignKey(
- User, related_name='pins', on_delete=models.CASCADE, null=True
- ) # untube user this pin is linked to
- kind = models.CharField(max_length=100) # 'playlist', 'video'
- playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE, null=True)
- video = models.ForeignKey(Video, on_delete=models.CASCADE, null=True)
|