From bb42798c6983a587439bfa788ed33eb5daf03cc4 Mon Sep 17 00:00:00 2001 From: lrnselfreliance <33263708+lrnselfreliance@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:37:38 -0600 Subject: [PATCH] videos: a collaboration video's channel directory conflict killed the download (#677) * videos: a collaboration video's channel directory conflict killed the download YouTube collaboration videos appear in every collaborator's feed but their metadata names only the primary uploader's channel. When that channel's computed directory already belonged to an existing Channel that no longer matched by source_id/url/name, channel creation raised ChannelDirectoryConflict and the download failed unrecoverably. Now the channel that owns the directory is used instead of creating a conflicting one. Co-Authored-By: Claude Fable 5 * review: trim comments that restated the code Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- modules/videos/downloader.py | 9 +++++++++ modules/videos/test/test_downloader.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/modules/videos/downloader.py b/modules/videos/downloader.py index 14e0fd1b..1cd6708f 100755 --- a/modules/videos/downloader.py +++ b/modules/videos/downloader.py @@ -1361,6 +1361,15 @@ def get_or_create_channel(session: Session, source_id: str = None, url: str = No channel_directory = get_absolute_media_path(destination) else: channel_directory = format_videos_destination(name, tag_name, url) + + # Reuse the Channel that already owns this directory instead of creating a conflicting one. + try: + channel = get_channel(session, directory=channel_directory, return_dict=False) + channel.dict() + return channel + except UnknownChannel: + pass + if not channel_directory.is_dir(): channel_directory.mkdir(parents=True) data = ChannelPostRequest( diff --git a/modules/videos/test/test_downloader.py b/modules/videos/test/test_downloader.py index 834e14b0..cc8438ff 100644 --- a/modules/videos/test/test_downloader.py +++ b/modules/videos/test/test_downloader.py @@ -365,6 +365,29 @@ async def test_get_or_create_channel_destination_nested_conflict(async_client, t assert result.id == channel.id +@pytest.mark.asyncio +async def test_get_or_create_channel_computed_directory_conflict(async_client, test_session, test_directory): + """When no existing channel matches by source_id/url/name, but the directory that would be created + already belongs to a channel, that channel should be returned instead of raising. + + YouTube collaboration videos report the primary uploader's channel, which may not match any local + channel by name (e.g. the local channel was renamed) even though its directory is already taken.""" + from wrolpi.collections import Collection + + existing_dir = test_directory / 'videos/Uploader' + existing_dir.mkdir(parents=True) + collection = Collection(name='Uploader (old name)', kind='channel', directory=existing_dir) + test_session.add(collection) + test_session.flush([collection]) + channel = Channel(collection_id=collection.id, source_id='UCold', url='https://www.youtube.com/@olduploader') + test_session.add(channel) + test_session.commit() + + result = get_or_create_channel(test_session, source_id='UCnew', + url='https://www.youtube.com/channel/UCnew', name='Uploader') + assert result.id == channel.id + + @pytest.mark.asyncio async def test_get_or_create_channel_existing_ignores_destination(async_client, test_session, test_directory): """When a channel already exists (matched by source_id), the destination parameter should be ignored."""