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

camatcode / basenji / 860f7aa949c7b0dbdf1f2bd20957119d0882aeba

08 Jul 2025 07:58PM UTC coverage: 69.521% (+0.4%) from 69.083%
860f7aa949c7b0dbdf1f2bd20957119d0882aeba

push

github

web-flow
Merge pull request #30 from camatcode/work/pdf-support

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%)

467.38 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
5✔
7

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

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

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

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

18
      file_entries =
488✔
19
        1..pages
20
        |> Enum.map(fn idx ->
21
          %{file_name: "#{String.pad_leading("#{idx}", padding, "0")}.jpg"}
1,952✔
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]
18✔
30
    {page_num, _rest} = Integer.parse(file_name)
18✔
31

32
    create_resource(fn ->
18✔
33
      with {:ok, output} <- exec("pdftoppm", ["-f", "#{page_num}", "-singlefile", "-jpeg", "-q", pdf_file_path]) do
18✔
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
487✔
41
      file_entries =
487✔
42
        file_entries
43
        |> Enum.map(fn entry ->
44
          entry
45
          |> Map.put(:stream_fun, fn -> get_entry_stream!(pdf_file_path, entry) end)
1,948✔
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
489✔
54
      metadata =
488✔
55
        String.split(output, "\n")
56
        |> Map.new(fn line ->
57
          String.split(line, ":", parts: 2)
58
          |> case do
8,784✔
59
            [k, v] -> to_metadata(k, v)
8,784✔
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()
8,784✔
70
    v = convert_value(k, v |> String.trim())
8,784✔
71
    {k, v}
72
  end
73

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

78
  defp convert_value(:filesize, v) do
79
    {first, _rest} = Integer.parse(v)
488✔
80
    first
488✔
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
1,952✔
88
        {coord, _rest} -> [coord | acc]
976✔
89
        _ -> acc
976✔
90
      end
91
    end)
92
    |> case do
488✔
93
      [x, y] -> {x, y}
488✔
94
      _ -> v
×
95
    end
96
  end
97

98
  defp convert_value(_k, "yes"), do: true
×
99
  defp convert_value(_k, "no"), do: false
3,904✔
100
  defp convert_value(_k, "none"), do: nil
488✔
101
  defp convert_value(_k, v), do: v
1,952✔
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