wrolpi/modules/videos/__init__.py
Roland 58de8c7c5b
Upgrading Sanic and yt-dlp. Major changes occurred because Sanic changed how multiprocessing works.
Python 3.11 is now required, old versions were causing unpredictability in tests.  (Sanic does not yet
support 3.12)

Sanic has been upgraded to 23.6.0, which is the latest version that avoids this bug:
 https://github.com/sanic-org/sanic/issues/2921

New strategy for multiprocessing is to create all multiprocessing tools in one process, then fork
to other processes.  The previous strategy was to declare multiprocessing tools at the top of every
file, or wherever they were needed at import/creation.  Now all multiprocessing tools are attached to the
app.shared_ctx.  This means `api_app` is imported in many, many places.

This forced a change in how the DownloadManager works.  Previously, it would continually run
download workers which would pull downloads from a multiprocessing.Queue.  Now, a single worker checks
for new downloads and sends a Sanic signal.

Flags have been reworked to use the `api_app`.  I removed the `which` flag functionality because the
`which` are called at import and needed their own multiprocessing.Event.
2024-04-30 10:55:25 -06:00

85 lines
3 KiB
Python

import asyncio
from typing import List, Tuple
from modules.videos.models import Video, Channel
from wrolpi.common import logger, limit_concurrent, register_modeler, register_refresh_cleanup
from wrolpi.db import get_db_curs, get_db_session
from wrolpi.files.models import FileGroup
from wrolpi.vars import PYTEST
from .downloader import video_downloader # Import downloaders so they are registered.
logger = logger.getChild(__name__)
__all__ = ['video_modeler']
VIDEO_PROCESSING_LIMIT = 20
@register_modeler
async def video_modeler():
while True:
with get_db_session(commit=True) as session:
file_groups = session.query(FileGroup, Video).filter(
FileGroup.indexed != True,
FileGroup.mimetype.like('video/%'),
).outerjoin(Video, Video.file_group_id == FileGroup.id) \
.limit(VIDEO_PROCESSING_LIMIT)
file_groups: List[Tuple[FileGroup, Video]] = list(file_groups)
processed = 0
for file_group, video in file_groups:
processed += 1
video_id = None
try:
if not video:
video = Video(file_group=file_group)
session.add(video)
session.flush([video])
video_id = video.id
# Extract ffprobe data.
await video.get_ffprobe_json()
# Validate and index subtitles.
video.validate()
except Exception as e:
if PYTEST:
raise
i = video.file_group.primary_path if video.file_group else video_id
logger.error(f'Unable to model Video: {str(i)}', exc_info=e)
file_group.indexed = True
session.commit()
if processed < VIDEO_PROCESSING_LIMIT:
# Did not reach limit, do not query again.
break
logger.debug(f'Modeled {processed} videos')
# Sleep to catch cancel.
await asyncio.sleep(0)
@register_refresh_cleanup
@limit_concurrent(1)
def video_cleanup():
logger.info('Claiming Videos for their Channels')
with get_db_curs(commit=True) as curs:
# Delete all Videos if the FileModel no longer contains a video.
curs.execute('''
WITH deleted AS
(UPDATE file_group SET model=null WHERE model='video' AND mimetype NOT LIKE 'video/%' RETURNING id)
DELETE FROM video WHERE file_group_id = ANY(select id from deleted)
''')
# Claim all Videos in a Channel's directory for that Channel. But, only if they have not yet been claimed.
curs.execute('''
UPDATE video v
SET
channel_id = c.id
FROM channel c
LEFT JOIN file_group fg ON fg.primary_path LIKE c.directory || '/%'::VARCHAR
WHERE
v.channel_id IS NULL
AND fg.id = v.file_group_id
''')