Skip to content

Commit

Permalink
Adds thumbnails as episode-level images for podcasts (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
kieraneglin authored Apr 26, 2024
1 parent f65ebea commit 09cac46
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 5 deletions.
16 changes: 16 additions & 0 deletions lib/pinchflat/podcasts/rss_feed_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
end

defp build_media_item_xml(source, media_item, url_base) do
item_image_path = item_image_path(url_base, media_item)

"""
<item>
<guid isPermaLink="false">#{media_item.uuid}</guid>
Expand All @@ -91,6 +93,10 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
<itunes:author>#{safe(source.custom_name)}</itunes:author>
<itunes:subtitle>#{safe(media_item.title)}</itunes:subtitle>
<itunes:summary><![CDATA[#{media_item.description}]]></itunes:summary>
#{item_image_path && ~s(<itunes:image href="#{safe(item_image_path)}"></itunes:image>)}
#{item_image_path && ~s(<podcast:images srcset="#{safe(item_image_path)}" />)}
<itunes:explicit>false</itunes:explicit>
</item>
"""
Expand All @@ -117,6 +123,16 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do
end
end

def item_image_path(url_base, media_item) do
if media_item.thumbnail_filepath && File.exists?(media_item.thumbnail_filepath) do
extension = Path.extname(media_item.thumbnail_filepath)

Path.join(url_base, "#{podcast_route(:episode_image, media_item.uuid)}#{extension}")
else
nil
end
end

defp generate_upload_date(media_item) do
media_item.upload_date
|> DatetimeUtils.date_to_datetime()
Expand Down
15 changes: 14 additions & 1 deletion lib/pinchflat_web/controllers/podcasts/podcast_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ defmodule PinchflatWeb.Podcasts.PodcastController do
use PinchflatWeb, :controller

alias Pinchflat.Repo
alias Pinchflat.Media.MediaQuery
alias Pinchflat.Sources.Source
alias Pinchflat.Media.MediaItem
alias Pinchflat.Media.MediaQuery
alias Pinchflat.Podcasts.RssFeedBuilder
alias Pinchflat.Podcasts.PodcastHelpers

Expand Down Expand Up @@ -40,4 +41,16 @@ defmodule PinchflatWeb.Podcasts.PodcastController do
|> send_file(200, filepath)
end
end

def episode_image(conn, %{"uuid" => uuid}) do
media_item = Repo.get_by!(MediaItem, uuid: uuid)

if media_item.thumbnail_filepath && File.exists?(media_item.thumbnail_filepath) do
conn
|> put_resp_content_type(MIME.from_path(media_item.thumbnail_filepath))
|> send_file(200, media_item.thumbnail_filepath)
else
send_resp(conn, 404, "Image not found")
end
end
end
1 change: 1 addition & 0 deletions lib/pinchflat_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ defmodule PinchflatWeb.Router do

get "/sources/:uuid/feed", Podcasts.PodcastController, :rss_feed
get "/sources/:uuid/feed_image", Podcasts.PodcastController, :feed_image
get "/media/:uuid/episode_image", Podcasts.PodcastController, :episode_image

get "/media/:uuid/stream", MediaItems.MediaItemController, :stream
end
Expand Down
28 changes: 28 additions & 0 deletions test/pinchflat/podcasts/rss_feed_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,34 @@ defmodule Pinchflat.Podcasts.RssFeedBuilderTest do
assert String.contains?(item_xml, ~s(length="1234"))
assert String.contains?(item_xml, ~s(type="video/mp4"))
end

test "returns image tags if the media has a thumbnail", %{source: source} do
media_item = media_item_with_attachments(%{source_id: source.id, media_size_bytes: 1234})

res = RssFeedBuilder.build(source)
[_before, item_xml, _after] = String.split(res, ~r(</?item>))

assert String.contains?(
item_xml,
~s(<itunes:image href="http://localhost:8945/media/#{media_item.uuid}/episode_image.jpg"></itunes:image>)
)

assert String.contains?(
item_xml,
~s(<podcast:images srcset="http://localhost:8945/media/#{media_item.uuid}/episode_image.jpg" />)
)
end

test "does not return image tags if the media does not have a thumbnail", %{source: source} do
media_item = media_item_with_attachments(%{source_id: source.id})
File.rm!(media_item.thumbnail_filepath)

res = RssFeedBuilder.build(source)
[_before, item_xml, _after] = String.split(res, ~r(</?item>))

refute String.contains?(item_xml, ~s(itunes:image))
refute String.contains?(item_xml, ~s(podcast:images))
end
end

defp format_date(date) do
Expand Down
22 changes: 22 additions & 0 deletions test/pinchflat_web/controllers/podcast_controller_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule PinchflatWeb.PodcastControllerTest do
use PinchflatWeb.ConnCase

import Pinchflat.MediaFixtures
import Pinchflat.SourcesFixtures

describe "rss_feed" do
Expand Down Expand Up @@ -35,4 +36,25 @@ defmodule PinchflatWeb.PodcastControllerTest do
assert conn.resp_body == "Image not found"
end
end

describe "episode_image" do
test "returns an episode image if one can be found", %{conn: conn} do
media_item = media_item_with_attachments()

conn = get(conn, ~p"/media/#{media_item.uuid}/episode_image" <> ".jpg")

assert conn.status == 200
assert {"content-type", "image/jpeg; charset=utf-8"} in conn.resp_headers
assert conn.resp_body == File.read!(media_item.thumbnail_filepath)
end

test "returns 404 if an image cannot be found", %{conn: conn} do
media_item = media_item_fixture()

conn = get(conn, ~p"/media/#{media_item.uuid}/episode_image" <> ".jpg")

assert conn.status == 404
assert conn.resp_body == "Image not found"
end
end
end
16 changes: 12 additions & 4 deletions test/support/fixtures/media_fixtures.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,24 @@ defmodule Pinchflat.MediaFixtures do
end

def media_item_with_attachments(attrs \\ %{}) do
stored_media_filepath =
base_dir =
Path.join([
Application.get_env(:pinchflat, :media_directory),
"#{:rand.uniform(1_000_000)}",
"#{:rand.uniform(1_000_000)}_media.mp4"
"#{:rand.uniform(1_000_000)}"
])

stored_media_filepath = Path.join(base_dir, "#media.mp4")
thumbnail_filepath = Path.join(base_dir, "thumbnail.jpg")

FilesystemUtils.cp_p!(media_filepath_fixture(), stored_media_filepath)
FilesystemUtils.cp_p!(thumbnail_filepath_fixture(), thumbnail_filepath)

merged_attrs =
Map.merge(attrs, %{
media_filepath: stored_media_filepath,
thumbnail_filepath: thumbnail_filepath
})

merged_attrs = Map.merge(attrs, %{media_filepath: stored_media_filepath})
media_item_fixture(merged_attrs)
end

Expand Down

0 comments on commit 09cac46

Please sign in to comment.