Skip to content

Commit

Permalink
feat(client): add FFmpeg setup and improve file transcoding
Browse files Browse the repository at this point in the history
- Add FFmpeg setup step in GitHub Actions workflow
- Enhance transcode_file method with codec detection and handling
- Implement get_podcasts_info method
- Add tests for new functionality and error cases
  • Loading branch information
bendikrb committed Oct 21, 2024
1 parent 1dbdab9 commit 7c510d2
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
with:
python-version: ${{ matrix.python }}
cache: "poetry"
- name: 🏗 Set up FFmpeg
id: setup-ffmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
- name: 🏗 Install workflow dependencies
run: |
poetry config virtualenvs.create true
Expand Down
2 changes: 1 addition & 1 deletion podme_api/auth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def get_credentials(self) -> dict | None:
"""Get the current credentials as a dictionary, or None if not set."""
if self._credentials is not None:
return self._credentials.to_dict()
return None
return None # pragma: no cover

def set_credentials(self, credentials: SchibstedCredentials | dict | str):
"""Set the credentials.
Expand Down
52 changes: 35 additions & 17 deletions podme_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,8 @@ async def _get_pages(

return data

@staticmethod
async def transcode_file(
self,
input_file: PathLike | str,
output_file: PathLike | str | None = None,
transcode_options: dict[str, str] | None = None,
Expand All @@ -340,29 +340,47 @@ async def transcode_file(
if not input_file.is_file():
raise PodMeApiError("File not found")

if output_file is None:
if output_file is None: # pragma: no cover
output_file = input_file.with_stem(f"{input_file.stem}_out")

transcode_options = transcode_options or {}

ffmpeg = (
FFmpeg()
.option("y")
.input(input_file.as_posix())
.output(
output_file.as_posix(),
{
"c": "copy",
"map": "0",
"brand": "isomiso2mp41",
**transcode_options,
},
)
)
try:
ffprobe = FFmpeg(executable="ffprobe").input(
input_file,
print_format="json",
show_streams=None,
)
media = json.loads(await ffprobe.execute())

codec_name = media["streams"][0]["codec_name"]
codec_tag_string = media["streams"][0]["codec_tag_string"]
if codec_name == "aac" and codec_tag_string[:3] == "mp4":
output_file = output_file.with_suffix(".mp4")
transcode_options.update(
{
"codec": "copy",
"map": "0",
"brand": "isomiso2mp41",
}
)

elif codec_name == "mp3":
return input_file

_LOGGER.info("Transcoding file: %s to %s", input_file, output_file)
ffmpeg = (
FFmpeg()
.option("y")
.input(input_file)
.output(
output_file,
transcode_options,
)
)
await ffmpeg.execute()
except FileNotFoundError as err:
_LOGGER.warning("Error occurred while transcoding file: %s", err)
_LOGGER.warning("Please install ffmpeg to enable transcoding: %s", err)
return input_file
except FFmpegError as err:
_LOGGER.warning("Error occurred while transcoding file: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/stream_m3u8.json

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,13 +459,19 @@ async def test_get_podcast_info(aresponses: ResponsesMockServer, podme_client, p
f"{PODME_API_PATH}/podcast/slug/{podcast_slug}",
"GET",
json_response(data=fixture),
repeat=2,
)

async with podme_client() as client:
client: PodMeClient
result = await client.get_podcast_info(podcast_slug)
assert isinstance(result, PodMePodcast)

results = await client.get_podcasts_info([podcast_slug])
assert isinstance(results, list)
assert len(results) == 1
assert all(isinstance(r, PodMePodcast) for r in results)


@pytest.mark.parametrize(
"episode_id",
Expand Down Expand Up @@ -605,6 +611,29 @@ async def test_get_home_screen(aresponses: ResponsesMockServer, podme_client):


async def test_download_episode_files(aresponses: ResponsesMockServer, podme_client):
episodes_fixture = load_fixture_json("episode_currentlyplaying")
setup_stream_mocks(aresponses, episodes_fixture)
download_urls = [
(e["id"], URL(e["streamUrl"]).with_name("audio_128_pkg.mp4"))
for e in episodes_fixture
if "m3u8" in e["streamUrl"]
]

async with podme_client() as client:
client: PodMeClient
with tempfile.TemporaryDirectory() as d:
dir_path = Path(d)

def get_file_ending(url: URL) -> str:
return url.name.rsplit(".").pop()

download_infos = [
(url, dir_path / f"{episode_id}.{get_file_ending(url)}") for episode_id, url in download_urls
]
await client.download_files(download_infos)


async def test_download_episode_files_with_callbacks(aresponses: ResponsesMockServer, podme_client):
episodes_fixture = load_fixture_json("episode_currentlyplaying")
setup_stream_mocks(aresponses, episodes_fixture)
async with podme_client() as client:
Expand Down Expand Up @@ -712,6 +741,29 @@ def get_file_ending(url: URL) -> str:
await client.download_files(download_infos, on_progress, on_finished)


async def test_transcode_file_error(podme_client):
non_existing_file = Path("non_existing_file.mp3")

async with podme_client() as client:
client: PodMeClient
with pytest.raises(PodMeApiError):
await client.transcode_file(non_existing_file)

with tempfile.NamedTemporaryFile(suffix=".mp3") as f:
invalid_file = Path(f.name)
await client.transcode_file(invalid_file)


async def test_transcode_no_ffmpeg(monkeypatch, podme_client):
monkeypatch.setenv("PATH", "")
async with podme_client() as client:
client: PodMeClient
with tempfile.NamedTemporaryFile(suffix=".mp3") as f:
input_path = Path(f.name)
saved_path = await client.transcode_file(input_path)
assert saved_path == input_path


async def test_no_content(aresponses: ResponsesMockServer, podme_client):
"""Test HTTP 201 response handling."""
aresponses.add(
Expand Down

0 comments on commit 7c510d2

Please sign in to comment.