|
|
|
@db_task(delay=30, priority=80, queue=Val(TaskQueue.LIMIT)) |
|
def index_source(source_id): |
|
''' |
|
Indexes media available from a Source object. |
|
''' |
|
db.reset_queries() |
|
cleanup_completed_tasks() |
|
# deleting expired media should happen any time an index task is requested |
|
cleanup_old_media() |
|
try: |
|
source = Source.objects.get(pk=source_id) |
|
except Source.DoesNotExist as e: |
|
# Task triggered but the Source has been deleted, delete the task |
|
raise CancelExecution(_('no such source'), retry=False) from e |
|
# An inactive Source would return an empty list for videos anyway |
|
if not source.is_active: |
|
return False |
|
indexing_lock = huey_lock_task( |
|
f'source:{source.uuid}', |
|
queue=Val(TaskQueue.FS), |
|
) |
|
# be sure that this is locked |
|
if not indexing_lock.acquired: |
|
indexing_lock.acquired = True |
|
# update the target schedule column |
|
# ruff: ignore[B018] |
|
source.task_run_at_dt |
|
update_model(source, target_schedule=source.target_schedule) |
|
# Reset any errors |
|
source.has_failed = False |
|
# Index the source |
|
videos = source.index_media() |
|
if not videos: |
|
source.has_failed = True |
|
update_model(source, has_failed=source.has_failed) |
|
indexing_lock.acquired = False |
|
raise NoMediaException(f'Source "{source}" (ID: {source_id}) returned no ' |
|
f'media to index, is the source key valid? Check the ' |
|
f'source configuration is correct and that the source ' |
|
f'is reachable') |
|
# Got some media, update the last crawl timestamp |
|
source.last_crawl = timezone.now() |
|
update_model( |
|
source, |
|
has_failed=source.has_failed, |
|
last_crawl=source.last_crawl, |
|
) |
|
num_videos = len(videos) |
|
log.info(f'Found {num_videos} media items for source: {source}') |
|
tvn_format = '{:,}' + f'/{num_videos:,}' |
|
db_batch_data = queue(list(), maxlen=50) |
|
db_fields_data = frozenset(( |
|
'retrieved', |
|
'site', |
|
'value', |
|
)) |
|
db_batch_media = queue(list(), maxlen=10) |
|
db_fields_media = frozenset(( |
|
'duration', |
|
'published', |
|
'title', |
|
)) |
|
fields = lambda f, m: m.get_metadata_field(f) |
|
task = get_source_index_task(source_id) |
|
if task: |
|
task._verbose_name = remove_enclosed( |
|
task.verbose_name, '[', ']', ' ', |
|
valid='0123456789/,', |
|
end=task.verbose_name.find('Index'), |
|
) |
|
vn = 0 |
|
video_keys = set() |
|
while len(videos) > 0: |
|
vn += 1 |
|
video = videos.popleft() |
|
# Create or update each video as a Media object |
|
key = video.get(source.key_field, None) |
|
if not key: |
|
# Video has no unique key (ID), it can't be indexed |
|
continue |
|
video_keys.add(key) |
|
if len(db_batch_data) == db_batch_data.maxlen: |
|
save_db_batch(Metadata.objects, db_batch_data, db_fields_data) |
|
if len(db_batch_media) == db_batch_media.maxlen: |
|
save_db_batch(Media.objects, db_batch_media, db_fields_media) |
|
update_task_status(task, tvn_format.format(vn)) |
|
media_defaults = dict() |
|
# create a dummy instance to use its functions |
|
media = Media(source=source, key=key) |
|
media_defaults['duration'] = float(video.get(fields('duration', media), None) or 0) or None |
|
media_defaults['title'] = str(video.get(fields('title', media), ''))[:200] |
|
site = video.get(fields('ie_key', media), None) |
|
timestamp = video.get(fields('timestamp', media), None) |
|
try: |
|
published_dt = media.ts_to_dt(timestamp) |
|
except AssertionError: |
|
pass |
|
else: |
|
if published_dt: |
|
media_defaults['published'] = published_dt |
|
# Retrieve or create the actual media instance |
|
media, new_media = source.media_source.only( |
|
'uuid', |
|
'source', |
|
'key', |
|
*db_fields_media, |
|
).get_or_create(defaults=media_defaults, source=source, key=key) |
|
db_batch_media.append(media) |
|
data, new_data = source.videos.defer('value').filter( |
|
media__isnull=True, |
|
).get_or_create(source=source, key=key) |
|
if site: |
|
data.site = site |
|
data.retrieved = source.last_crawl |
|
data.value = { k: v for k,v in video.items() if v is not None } |
|
db_batch_data.append(data) |
|
migrating_lock = huey_lock_task( |
|
f'index_media:{media.uuid}', |
|
queue=Val(TaskQueue.FS), |
|
) |
|
if not migrating_lock.acquired: |
|
migrating_lock.acquired = True |
|
migrate_to_metadata(str(media.pk)) |
|
if not new_media: |
|
# update the existing media |
|
for key, value in media_defaults.items(): |
|
setattr(media, key, value) |
|
log.debug(f'Indexed media: {vn}: {source} / {media}') |
|
else: |
|
# log the new media instances |
|
log.info(f'Indexed new media: {source} / {media}') |
|
log.info(f'Scheduling tasks to download thumbnail for: {media.key}') |
|
thumbnail_fmt = 'https://i.ytimg.com/vi/{}/{}default.jpg' |
|
for num, prefix in enumerate(reversed(('hq', 'sd', 'maxres',))): |
|
thumbnail_url = thumbnail_fmt.format( |
|
media.key, |
|
prefix, |
|
) |
|
download_media_image.schedule( |
|
(str(media.pk), thumbnail_url,), |
|
priority=10+(5*num), |
|
delay=65-(30*num), |
|
) |
|
priority = download_media_metadata.settings.get('default_priority', 50) |
|
if source.download_media: |
|
priority += 5 |
|
else: |
|
priority -= 5 |
|
log.info(f'Scheduling task to download metadata for: {media.url}') |
|
TaskHistory.schedule( |
|
download_media_metadata, |
|
str(media.pk), |
|
priority=priority, |
|
remove_duplicates=True, |
|
vn_fmt=_('Downloading metadata for: "{}": {}'), |
|
vn_args=(media.key, media.name,), |
|
) |
|
# Reset task.verbose_name to the saved value |
|
update_task_status(task, None) |
|
# Update any remaining items in the batches |
|
save_db_batch(Metadata.objects, db_batch_data, db_fields_data) |
|
save_db_batch(Media.objects, db_batch_media, db_fields_media) |
|
# Cleanup of media no longer available from the source |
|
cleanup_removed_media(str(source.pk), video_keys) |
|
# Clear references to indexed data |
|
videos = video = None |
|
db_batch_data.clear() |
|
db_batch_media.clear() |
|
# Let the checking task run |
|
indexing_lock.acquired = False |
|
# Create the checking task |
|
TaskHistory.schedule( |
|
save_all_media_for_source, |
|
str(source.pk), |
|
remove_duplicates=True, |
|
vn_fmt = _('Checking all media for "{}"'), |
|
vn_args=( |
|
source.name, |
|
), |
|
) |
|
return True |
|
|
|
|
Currently there is duplication.
For new sources we are essentially running two indexing tasks.
tubesync/tubesync/sync/models/source.py
Lines 484 to 488 in 7ee8c96
tubesync/tubesync/sync/youtube.py
Lines 83 to 148 in 7ee8c96
We should instead index the source, moving the information needed for downloading images into the database immediately, then rework the download of source images to wait for the data to become available from the database.
tubesync/tubesync/sync/tasks.py
Lines 506 to 689 in 7ee8c96
tubesync/tubesync/sync/models/source.py
Lines 559 to 603 in 7ee8c96
tubesync/tubesync/sync/youtube.py
Lines 171 to 279 in 7ee8c96