• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

camatcode / basenji / 20b5d958196753eb8956905585facd08d7d502b0

08 Jul 2025 07:55PM UTC coverage: 69.521% (+0.4%) from 69.083%
20b5d958196753eb8956905585facd08d7d502b0

Pull #30

github

camatcode
feat(comics): stress testing
Pull Request #30: feat(comics): stress testing

20 of 21 new or added lines in 8 files covered. (95.24%)

479 of 689 relevant lines covered (69.52%)

501.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.89
/lib/basenji/reader/pdf_reader.ex
1
defmodule Basenji.Reader.PDFReader do
2
  @moduledoc false
3

4
  import Basenji.Reader
5

6
  def format, do: :pdf
4✔
7

8
  def file_extensions, do: ["pdf"]
543✔
9

10
  def get_magic_numbers, do: [%{offset: 0, magic: [0x25, 0x50, 0x44, 0x46, 0x2D]}]
541✔
11

12
  def close(_any), do: :ok
5✔
13

14
  def get_entries(pdf_file_path, _opts \\ []) do
15
    with {:ok, %{pages: pages}} <- get_metadata(pdf_file_path) do
544✔
16
      padding = String.length("#{pages}") - 1
543✔
17

18
      file_entries =
543✔
19
        1..pages
20
        |> Enum.map(fn idx ->
21
          %{file_name: "#{String.pad_leading("#{idx}", padding, "0")}.jpg"}
2,172✔
22
        end)
23

24
      {:ok, %{entries: file_entries}}
25
    end
26
  end
27

28
  def get_entry_stream!(pdf_file_path, entry) do
29
    file_name = entry[:file_name]
9✔
30
    {page_num, _rest} = Integer.parse(file_name)
9✔
31

32
    create_resource(fn ->
9✔
33
      with {:ok, output} <- exec("pdftoppm", ["-f", "#{page_num}", "-singlefile", "-jpeg", "-q", pdf_file_path]) do
9✔
34
        [output |> :binary.bin_to_list()]
35
      end
36
    end)
37
  end
38

39
  def read(pdf_file_path, _opts \\ []) do
40
    with {:ok, %{entries: file_entries}} <- get_entries(pdf_file_path) do
542✔
41
      file_entries =
542✔
42
        file_entries
43
        |> Enum.map(fn entry ->
44
          entry
45
          |> Map.put(:stream_fun, fn -> get_entry_stream!(pdf_file_path, entry) end)
2,168✔
46
        end)
47

48
      {:ok, %{entries: file_entries}}
49
    end
50
  end
51

52
  def get_metadata(pdf_file_path) do
53
    with {:ok, output} <- exec("pdfinfo", ["-isodates", pdf_file_path]) do
544✔
54
      metadata =
543✔
55
        String.split(output, "\n")
56
        |> Map.new(fn line ->
57
          String.split(line, ":", parts: 2)
58
          |> case do
9,774✔
59
            [k, v] -> to_metadata(k, v)
9,774✔
NEW
60
            [v] -> to_metadata("unknown_#{System.monotonic_time()}", v)
×
61
          end
62
        end)
63

64
      {:ok, metadata}
65
    end
66
  end
67

68
  defp to_metadata(k, v) do
69
    k = k |> String.trim() |> ProperCase.snake_case() |> String.to_atom()
9,774✔
70
    v = convert_value(k, v |> String.trim())
9,774✔
71
    {k, v}
72
  end
73

74
  defp convert_value(:creation_date, v), do: DateTimeParser.parse!(v)
543✔
75
  defp convert_value(:mod_date, v), do: DateTimeParser.parse!(v)
543✔
76
  defp convert_value(:pages, v), do: String.to_integer(v)
543✔
77

78
  defp convert_value(:filesize, v) do
79
    {first, _rest} = Integer.parse(v)
543✔
80
    first
543✔
81
  end
82

83
  defp convert_value(:pagesize, v) do
84
    String.split(v, " ")
85
    |> Enum.reduce([], fn part, acc ->
86
      Integer.parse(part)
87
      |> case do
2,172✔
88
        {coord, _rest} -> [coord | acc]
1,086✔
89
        _ -> acc
1,086✔
90
      end
91
    end)
92
    |> case do
543✔
93
      [x, y] -> {x, y}
543✔
94
      _ -> v
×
95
    end
96
  end
97

98
  defp convert_value(_k, "yes"), do: true
×
99
  defp convert_value(_k, "no"), do: false
4,344✔
100
  defp convert_value(_k, "none"), do: nil
543✔
101
  defp convert_value(_k, v), do: v
2,172✔
102
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc