diff --git a/modules/videos/lib.py b/modules/videos/lib.py index 68e6fe47..c06989e2 100644 --- a/modules/videos/lib.py +++ b/modules/videos/lib.py @@ -1054,7 +1054,11 @@ async def get_statistics(): NAME_PARSER = re.compile( r'(?:(.*?)_)?((?:\d+?)|(?:NA))_(?:(.{5,25})_)?(.*)' - r'\.(jpg|webp|flv|mp4|part|info\.json|description|webm|..\.srt|..\.vtt)', # the file suffix + # The file suffix. Every media suffix a Video FileGroup can lead with belongs here: + # audio is Channel content too, and a suffix missing from this list leaves the file + # without a published date, sorted to the end of its Channel. + r'\.(jpg|webp|flv|mp4|m4v|mkv|webm|mp3|flac|opus|ogg|m4a|wav|aac|wma' + r'|part|info\.json|description|..\.srt|..\.vtt)', re.IGNORECASE) diff --git a/modules/videos/test/test_audio_modeling.py b/modules/videos/test/test_audio_modeling.py index 443ba53e..aaa2a66c 100644 --- a/modules/videos/test/test_audio_modeling.py +++ b/modules/videos/test/test_audio_modeling.py @@ -33,6 +33,27 @@ async def test_uploaded_audio_becomes_a_channel_video(async_client, test_session assert video.channel_id == channel.id, 'the Video must belong to the Channel it was uploaded into' +@pytest.mark.asyncio +async def test_uploaded_audio_gets_published_datetime_from_filename(async_client, test_session, + channel_factory): + """An uploaded audio file named in WROLPi's format gets its published date from the name. + + The Channel page sorts by published date; without one the file sorts to the very end of the + Channel, which reads as missing. + """ + channel = channel_factory(name='The Tech Prepper') + audio_path = channel.directory / 'The Tech Prepper_20260820_WRMI Interview.mp3' + shutil.copy(PROJECT_DIR / 'test/big_buck_bunny.mp3', audio_path) + + await upsert_file(audio_path) + + file_group = test_session.query(FileGroup).filter_by(primary_path=str(audio_path)).one() + assert file_group.published_datetime is not None, \ + 'the published date must be parsed from the file name' + assert file_group.published_datetime.strftime('%Y%m%d') == '20260820' + assert file_group.title == 'WRMI Interview' + + @pytest.mark.asyncio async def test_audio_playlist_is_not_a_video(async_client, test_session, test_directory): """A playlist with an audio/* mimetype is a text list of URLs, not media. diff --git a/modules/videos/test/test_lib.py b/modules/videos/test/test_lib.py index 84c36c7e..4206ca1f 100644 --- a/modules/videos/test/test_lib.py +++ b/modules/videos/test/test_lib.py @@ -34,7 +34,18 @@ from wrolpi.vars import PROJECT_DIR ('Learning Self-Reliance_20170529_p_Beekeeping 2017 Part 6 - Merging Hives.mp4', ('Learning Self-Reliance', '20170529', None, 'p_Beekeeping 2017 Part 6 - Merging Hives')), ('Learning Self-Reliance_20240722_clyx8ark92qk70860vtl997n5_Long Source Id File with _.mp4', - ('Learning Self-Reliance', '20240722', 'clyx8ark92qk70860vtl997n5', 'Long Source Id File with _')) + ('Learning Self-Reliance', '20240722', 'clyx8ark92qk70860vtl997n5', 'Long Source Id File with _')), + # Audio files and other media containers carry the same name format; a suffix missing from + # the parser leaves the file with no published date, sorted to the end of its Channel. + ('The Tech Prepper_20260820_WRMI S2 Underground Shortwave Interview.flac', + ('The Tech Prepper', '20260820', None, 'WRMI S2 Underground Shortwave Interview')), + ('channel_20000101_12345678910_ some title.mp3', ('channel', '20000101', '12345678910', 'some title')), + ('channel_20000101_12345678910_ some title.mkv', ('channel', '20000101', '12345678910', 'some title')), + ('channel_20000101_some title.opus', ('channel', '20000101', None, 'some title')), + ('channel_20000101_some title.ogg', ('channel', '20000101', None, 'some title')), + ('channel_20000101_some title.m4a', ('channel', '20000101', None, 'some title')), + ('channel_20000101_some title.m4v', ('channel', '20000101', None, 'some title')), + ('channel_20000101_some title.wav', ('channel', '20000101', None, 'some title')), ]) def test_parse_video_file_name(file_name, expected): """