diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2215f9c..5ac36ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,8 +58,6 @@ jobs: needs: quality-checks if: github.event_name == 'push' runs-on: ubuntu-latest - outputs: - ghcr_repo: ${{ steps.ghcr-repo.outputs.ghcr_repo }} steps: - name: Get current date @@ -175,11 +173,12 @@ jobs: id: release_body env: DOCKERHUB_REPO: ${{ secrets.DOCKERHUB_REPOSITORY }} - GHCR_REPO: ${{ needs.dockerhub-build-push.outputs.ghcr_repo }} + GHCR_REPO_RAW: ghcr.io/${{ github.repository }} DATE: ${{ steps.date.outputs.date }} HAS_COMMITS: ${{ steps.commits.outputs.has_commits }} COMMITS: ${{ steps.commits.outputs.commits }} run: | + GHCR_REPO=$(echo "$GHCR_REPO_RAW" | tr '[:upper:]' '[:lower:]') { echo '## Docker Images' echo '' diff --git a/app/tests/test_ytdl_utils.py b/app/tests/test_ytdl_utils.py index 0f2e66d..5c05a05 100644 --- a/app/tests/test_ytdl_utils.py +++ b/app/tests/test_ytdl_utils.py @@ -38,8 +38,10 @@ sys.modules.setdefault("yt_dlp.utils", fake_utils) from ytdl import ( DownloadInfo, + _codec_suffix_value, _compact_persisted_entry, _convert_srt_to_txt_file, + _quality_suffix_value, _resolve_outtmpl_fields, _sanitize_entry_for_pickle, _sanitize_path_component, @@ -181,6 +183,74 @@ Second line self.assertIn("Second line", content) +class QualitySuffixValueTests(unittest.TestCase): + def _download_info(self, **kwargs): + info = DownloadInfo( + id="id1", + title="t", + url="http://example.com/v", + quality=kwargs.pop("quality", "best"), + download_type=kwargs.pop("download_type", "video"), + codec=kwargs.pop("codec", "auto"), + format=kwargs.pop("format", "any"), + folder="", + custom_name_prefix="", + error=None, + entry=None, + playlist_item_limit=0, + split_by_chapters=False, + chapter_template="", + ) + for key, value in kwargs.items(): + setattr(info, key, value) + return info + + def test_video_best_uses_actual_height_template(self): + self.assertEqual(_quality_suffix_value(self._download_info()), "%(height)sp") + + def test_video_numeric_quality_gets_p_suffix(self): + self.assertEqual(_quality_suffix_value(self._download_info(quality="2160")), "2160p") + + def test_audio_best_stays_best(self): + info = self._download_info(download_type="audio", quality="best") + self.assertEqual(_quality_suffix_value(info), "best") + + +class CodecSuffixValueTests(unittest.TestCase): + def _download_info(self, **kwargs): + return DownloadInfo( + id="id1", + title="t", + url="http://example.com/v", + quality="best", + download_type="video", + codec=kwargs.pop("codec", "auto"), + format="any", + folder="", + custom_name_prefix="", + error=None, + entry=kwargs.pop("entry", None), + playlist_item_limit=0, + split_by_chapters=False, + chapter_template="", + ) + + def test_explicit_codec_passes_through(self): + self.assertEqual(_codec_suffix_value(self._download_info(codec="h265")), "h265") + + def test_auto_codec_uses_entry_vcodec(self): + info = self._download_info(entry={"vcodec": "avc1.640028"}) + self.assertEqual(_codec_suffix_value(info), "h264") + + def test_auto_codec_uses_requested_formats(self): + info = self._download_info(entry={"requested_formats": [{"vcodec": "vp09.00.51.08"}]}) + self.assertEqual(_codec_suffix_value(info), "vp9") + + def test_unknown_auto_codec_is_sanitized(self): + info = self._download_info(entry={"vcodec": "some codec/name"}) + self.assertEqual(_codec_suffix_value(info), "some_codec_name") + + class DownloadInfoSetstateTests(unittest.TestCase): def _base_state(self, **kwargs): base = { diff --git a/app/ytdl.py b/app/ytdl.py index 6672c1a..7e2fdf7 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -374,6 +374,60 @@ def _download_info_from_record(record: dict[str, Any]) -> DownloadInfo: info.error = None return info + +def _quality_suffix_value(dl: DownloadInfo) -> str: + quality = str(getattr(dl, 'quality', '') or '') + if getattr(dl, 'download_type', '') == 'video': + if quality == 'best': + return '%(height)sp' + if re.fullmatch(r'\d+', quality): + return f'{quality}p' + return quality + + +def _codec_suffix_value(dl: DownloadInfo) -> str: + codec_value = str(getattr(dl, 'codec', '') or '') + if codec_value.lower() != 'auto': + return codec_value + + entry = getattr(dl, 'entry', None) or {} + resolved = None + if isinstance(entry, dict): + vc = entry.get('vcodec') + ac = entry.get('acodec') + if vc: + resolved = vc + elif ac: + resolved = ac + else: + for fld in ('requested_formats', 'formats'): + lst = entry.get(fld) or [] + if isinstance(lst, list): + for it in lst: + if isinstance(it, dict): + if it.get('vcodec'): + resolved = it.get('vcodec') + break + if it.get('acodec'): + resolved = it.get('acodec') + break + if resolved: + break + + if not resolved: + return codec_value + + resolved_value = str(resolved).lower() + if 'avc' in resolved_value or 'h264' in resolved_value: + return 'h264' + if 'hevc' in resolved_value or 'h265' in resolved_value or 'h.265' in resolved_value: + return 'h265' + if 'av01' in resolved_value or 'av1' in resolved_value: + return 'av1' + if 'vp9' in resolved_value or 'vp09' in resolved_value: + return 'vp9' + return re.sub(r'[^0-9a-zA-Z]+', '_', resolved_value) + class Download: manager = None @@ -922,48 +976,12 @@ class DownloadQueue: suffix = None if not getattr(dl, 'custom_name_prefix', None): parts = [] - if getattr(dl, 'quality', ''): - parts.append(str(dl.quality)) - codec_value = getattr(dl, 'codec', '') - if str(codec_value).lower() == 'auto': - entry = getattr(dl, 'entry', None) or {} - resolved = None - if isinstance(entry, dict): - vc = entry.get('vcodec') - ac = entry.get('acodec') - if vc: - resolved = vc - elif ac: - resolved = ac - else: - for fld in ('requested_formats', 'formats'): - lst = entry.get(fld) or [] - if isinstance(lst, list): - for it in lst: - if isinstance(it, dict): - if it.get('vcodec'): - resolved = it.get('vcodec') - break - if it.get('acodec'): - resolved = it.get('acodec') - break - if resolved: - break - if resolved: - s = str(resolved).lower() - if 'avc' in s or 'h264' in s: - codec_value = 'h264' - elif 'hevc' in s or 'h265' in s or 'h.265' in s: - codec_value = 'h265' - elif 'av01' in s or 'av1' in s: - codec_value = 'av1' - elif 'vp9' in s or 'vp09' in s: - codec_value = 'vp9' - else: - codec_value = re.sub(r'[^0-9a-zA-Z]+', '_', s) - - if codec_value: - parts.append(str(codec_value)) + quality_suffix = _quality_suffix_value(dl) + if quality_suffix: + parts.append(quality_suffix) + codec_suffix = _codec_suffix_value(dl) + if codec_suffix: + parts.append(codec_suffix) if parts: suffix = '_'.join(_sanitize_path_component(p) for p in parts if p)