diff --git a/modules/videos/common.py b/modules/videos/common.py index 74cdb77b..9bbf9f84 100644 --- a/modules/videos/common.py +++ b/modules/videos/common.py @@ -434,6 +434,11 @@ def get_video_duration(video_path: Path) -> int: """ Get the duration of a video in seconds. Do this using ffprobe. """ + if not isinstance(video_path, Path): + video_path = Path(video_path) + if not video_path.is_file(): + raise FileNotFoundError(f'{video_path} does not exist!') + cmd = ['/usr/bin/ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', str(video_path)] @@ -458,12 +463,20 @@ async def get_bulk_video_info_json(video_ids: List[int]): for video_id in video_ids: video = session.query(Video).filter_by(id=video_id).one() logger.debug(f'Getting video info_json data: {video}') - video_path = get_absolute_video_path(video) try: info_json = video.get_info_json() - video.view_count = info_json.get('view_count') if info_json else None - video.duration = info_json.get('duration') if info_json else get_video_duration(video_path) + if info_json: + if not video.view_count and info_json.get('view_count'): + video.view_count = info_json['view_count'] + if not video.duration: + # Get duration from info_json without reading video file. + video.duration = info_json.get('duration') + elif not video.duration: + # As a last resort, get duration from the video file. + video_path = get_absolute_video_path(video) + video.duration = get_video_duration(video_path) + except Exception: logger.warning(f'Unable to get meta data of {video}', exc_info=True) logger.info('Done getting video info_json meta data.') diff --git a/modules/videos/lib.py b/modules/videos/lib.py index 9d3d0c08..64892532 100644 --- a/modules/videos/lib.py +++ b/modules/videos/lib.py @@ -80,8 +80,14 @@ def refresh_channel_info_json() -> bool: Fill in Video columns that are extracted from the info_json. """ with get_db_curs() as curs: - query = 'SELECT id FROM video WHERE video_path IS NOT NULL AND ' \ - '(duration IS NULL OR view_count IS NULL)' + query = ''' + SELECT v.id + FROM video v + WHERE + v.video_path IS NOT NULL + AND v.info_json_path IS NOT NULL + AND (v.duration IS NULL OR v.view_count IS NULL) + ''' curs.execute(query) missing_duration = [i for (i,) in curs.fetchall()] diff --git a/modules/videos/test/test_common.py b/modules/videos/test/test_common.py index ccfc80dd..aaa40a5e 100644 --- a/modules/videos/test/test_common.py +++ b/modules/videos/test/test_common.py @@ -1,4 +1,5 @@ import pathlib +import subprocess import tempfile from unittest import mock from unittest.mock import Mock @@ -11,8 +12,9 @@ from modules.videos.test.common import create_channel_structure from wrolpi.common import get_absolute_media_path from wrolpi.db import get_db_session from wrolpi.test.common import ExtendedTestCase, build_test_directories, wrap_test_db +from wrolpi.vars import PROJECT_DIR from ..common import get_matching_directories, convert_image, bulk_validate_posters, remove_duplicate_video_paths, \ - update_view_count + update_view_count, get_video_duration class TestCommon(ExtendedTestCase): @@ -53,6 +55,22 @@ class TestCommon(ExtendedTestCase): matches = get_matching_directories(temp_dir / 'foo') assert matches == [str(temp_dir / 'foo/qux')] + def test_get_video_duration(self): + """ + Video duration can be retrieved from the video file. + """ + video_path = PROJECT_DIR / 'test/videos/wrolpi/big_buck_bunny_720p_1mb.mp4' + self.assertEqual(get_video_duration(video_path), 5) + + video_path = PROJECT_DIR / 'test/videos/wrolpi/does not exist.mp4' + self.assertRaises(FileNotFoundError, get_video_duration, video_path) + + video_path = str(PROJECT_DIR / 'test/videos/wrolpi/does not exist.mp4') + self.assertRaises(FileNotFoundError, get_video_duration, video_path) + + with tempfile.NamedTemporaryFile() as fh: + self.assertRaises(subprocess.CalledProcessError, get_video_duration, fh.name) + @pytest.mark.parametrize( 'paths,expected', @@ -251,3 +269,11 @@ def test_update_view_count(tempdir: pathlib.Path): # All videos are updated. update_view_count(channel3.id) check_view_counts({'vid1.mp4': 10, 'vid2.mp4': 11, 'vid3.mp4': 13, 'vid4.mp4': 14}) + + # An outdated view count will be overwritten. + with get_db_session(commit=True) as session: + vid = session.query(Video).filter_by(id=1).one() + vid.view_count = 8 + check_view_counts({'vid1.mp4': 8, 'vid2.mp4': 11, 'vid3.mp4': 13, 'vid4.mp4': 14}) + update_view_count(channel1.id) + check_view_counts({'vid1.mp4': 10, 'vid2.mp4': 11, 'vid3.mp4': 13, 'vid4.mp4': 14})