mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
163 lines
5.6 KiB
Python
163 lines
5.6 KiB
Python
import json
|
|
import pathlib
|
|
from abc import ABC
|
|
from typing import List, Dict
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from modules.videos.models import Video, Channel
|
|
from wrolpi.captions import read_captions, extract_captions
|
|
from wrolpi.common import logger, register_modeler, register_after_refresh, limit_concurrent, split_lines_by_length
|
|
from wrolpi.db import get_db_curs
|
|
from wrolpi.files.indexers import Indexer, register_indexer
|
|
from wrolpi.files.lib import split_path_stem_and_suffix
|
|
from wrolpi.files.models import File
|
|
from wrolpi.vars import PYTEST
|
|
from .downloader import video_downloader # Import downloaders so they are registered.
|
|
|
|
logger = logger.getChild(__name__)
|
|
|
|
__all__ = ['video_modeler', 'VideoIndexer']
|
|
|
|
|
|
def find_video_file_in_group(group: List[File]):
|
|
return next((i for i in group if i.mimetype.startswith('video/') and i.path.suffix != '.part'), None)
|
|
|
|
|
|
@register_modeler
|
|
def video_modeler(groups: Dict[str, List[File]], session: Session):
|
|
new_videos = []
|
|
|
|
# Search all groups for video files.
|
|
video_files = {stem: video_file for stem, group in groups.items() if
|
|
(video_file := find_video_file_in_group(group))}
|
|
if not video_files:
|
|
# No videos in these groups.
|
|
return
|
|
# Get all matching Video records (if any) in one query.
|
|
video_paths = [i.path for i in video_files.values()]
|
|
video_records = {i.video_path: i for i in session.query(Video).filter(Video.video_path.in_(video_paths))}
|
|
|
|
for stem, video_file in video_files.items():
|
|
group = groups[stem]
|
|
|
|
session.flush(group)
|
|
poster_file = info_json_file = caption_file = None
|
|
for file in group:
|
|
if file.mimetype.startswith('image/'):
|
|
poster_file = file
|
|
elif file.path.name.endswith('.info.json'):
|
|
info_json_file = file
|
|
# Prefer WebVTT over SRT. (SRT cannot be displayed for HTML video).
|
|
elif file.path.name.endswith('.en.vtt'):
|
|
caption_file = file
|
|
elif file.path.name.endswith('.en.srt'):
|
|
caption_file = caption_file or file
|
|
|
|
if poster_file:
|
|
poster_file.associated = True
|
|
poster_file.do_stats()
|
|
if caption_file:
|
|
caption_file.associated = True
|
|
caption_file.do_stats()
|
|
if info_json_file:
|
|
info_json_file.associated = True
|
|
info_json_file.do_stats()
|
|
|
|
video: Video = video_records.get(video_file.path)
|
|
if not video:
|
|
video = Video(video_file=video_file)
|
|
session.add(video)
|
|
|
|
size = video_file.path.stat().st_size
|
|
|
|
if poster_file != video.poster_file or \
|
|
caption_file != video.caption_file or \
|
|
info_json_file != video.info_json_file or \
|
|
video.size != size:
|
|
# Files might have been changed. Re-index.
|
|
video.video_file.indexed = False
|
|
|
|
video.poster_file = poster_file
|
|
video.caption_file = caption_file
|
|
video.info_json_file = info_json_file
|
|
video.size = size
|
|
video_file.model = Video.__tablename__
|
|
|
|
new_videos.append(video)
|
|
|
|
if new_videos:
|
|
session.flush(new_videos)
|
|
for video in new_videos:
|
|
video.video_file.do_index()
|
|
video.validate(session)
|
|
|
|
# Remove this group, it will not be processed by other modelers.
|
|
stem, _ = split_path_stem_and_suffix(video.video_path)
|
|
del groups[stem]
|
|
|
|
|
|
@register_after_refresh
|
|
@limit_concurrent(1)
|
|
def video_cleanup():
|
|
# Claim all Videos in a Channel's directory for that Channel. But, only if they have not yet been claimed.
|
|
logger.info('Claiming Videos for their Channels')
|
|
with get_db_curs(commit=True) as curs:
|
|
curs.execute('''
|
|
UPDATE video v
|
|
SET channel_id = c.id
|
|
FROM channel c
|
|
WHERE
|
|
v.video_path LIKE c.directory || '/%'::VARCHAR
|
|
AND v.channel_id IS NULL
|
|
''')
|
|
|
|
|
|
EXTRACT_SUBTITLES = False
|
|
|
|
|
|
@register_indexer('video')
|
|
class VideoIndexer(Indexer, ABC):
|
|
"""Handles video files like mp4/ogg."""
|
|
|
|
@staticmethod
|
|
def get_description(file):
|
|
video_path: pathlib.Path = file.path.path if hasattr(file.path, 'path') else file.path
|
|
info_json_path = video_path.with_suffix('.info.json')
|
|
if info_json_path.is_file():
|
|
with info_json_path.open('rt') as fh:
|
|
try:
|
|
return json.load(fh)['description']
|
|
except Exception:
|
|
if not PYTEST:
|
|
logger.warning(f'Video info json file exists, but cannot get description. {file}')
|
|
return None
|
|
|
|
@classmethod
|
|
def create_index(cls, file):
|
|
"""
|
|
Index the video file and it's associated files.
|
|
|
|
a = title.
|
|
b = <empty>
|
|
c = description from info json.
|
|
d = captions from info json, or extracted from video file.
|
|
"""
|
|
a = cls.get_title(file)
|
|
c = cls.get_description(file)
|
|
|
|
# Detect the associated caption files.
|
|
d = None
|
|
if (en_vtt := file.path.with_suffix('.en.vtt')).is_file():
|
|
d = read_captions(en_vtt)
|
|
elif (vtt := file.path.with_suffix('.vtt')).is_file():
|
|
d = read_captions(vtt)
|
|
elif (en_srt := file.path.with_suffix('.en.srt')).is_file():
|
|
d = read_captions(en_srt)
|
|
elif (srt := file.path.with_suffix('.srt')).is_file():
|
|
d = read_captions(srt)
|
|
elif EXTRACT_SUBTITLES:
|
|
d = extract_captions(file.path) or ''
|
|
|
|
d = split_lines_by_length(d)
|
|
return a, None, c, d
|