defmodule ExVideoFactory do @moduledoc """ Elixir wrapper for France**Télévisions** Video Factory API. """ require Logger @doc """ Get the list of `videos`. ## Options * `query`: Map of parameters to filter the query - q: query string in title, additional_title, original_title (case insensitive) - qid: filter with the video identifier - page: page index - per_page: number of items per page - etc. ## Response example ```elixir %{ videos: [ %{ "id": "978f7146-41e3-11e6-8153-000d3a23d482", "created_at": "2015-09-16T10:55:12.200+02:00", "updated_at": "2015-09-16T10:55:12.200+02:00", "created_by": nil, "created_via": "import-plurimedia-epg", "title": "Amour, gloire et beauté", "additional_title": "La vengeance de Samantha", "category": %{ "id": "series-fictions", "label": "séries & fictions" }, ... "images": [%{image}, %{image}, ...], "licensing": "svod", "embeddable": true, ... "platforms": %{ "ftv": %{ ... }, "bouygues": %{ ... }, "orange": %{ ... }, ... } "groups": [ %{ "id": "ea2bed61-47e0-4116-bf8c-33c375fd9030", "label": "Amour, gloire et beauté - Saison 13", ... "images": [%{image}, %{image}, ...], }, ... ] }, ... ], size: 10, offset: 0, total: 16238 } ``` """ def videos(query \\ %{}) do page = Map.get(query, "page", 1) |> force_integer page_size = Map.get(query, "per_page", 20) |> force_integer query = query |> format_query response = ExVideoFactory.Client.get("videos", query: query) case response do %HTTPotion.Response{} -> total = get_total_number_of_elements(response) list = response.body %{ videos: list, size: length(list), offset: (page - 1) * page_size + 1, total: total } msg -> Logger.warn("#{msg}") %{ videos: [], size: 0, offset: 0, total: 0, } end end @doc """ Get the list of `regions`. ## Response example ```elixir %{ regions: [ %{ "id": "saint-pierre-et-miquelon", "label": "Saint-Pierre et Miquelon", "code": "SPM", "time_zone": "America/Miquelon" }, ... ], size: 10, total: 12 } ``` """ def regions do response = ExVideoFactory.Client.get("regions") list = response.body total = get_total_number_of_elements(response) %{ regions: list, size: length(list), total: total } end @doc """ Get the list of `types`. ## Response example ```elixir %{ types: [ %{ "id": "bande-annonce", "label": "Bande-Annonce" }, ... ], size: 10, total: 24 } ``` """ def types do response = ExVideoFactory.Client.get("types") list = response.body total = get_total_number_of_elements(response) %{ types: list, size: length(list), total: total } end @doc """ Get the list of `channels`. ## Response example ```elixir %{ "channels": [ %{ "id": "saint-pierre-et-miquelon-1ere", "label": "Saint-Pierre et Miquelon 1ère", "region": %{ "id": "saint-pierre-et-miquelon", "label": "Saint-Pierre et Miquelon" "code": "SPM", "time_zone": "America/Miquelon" }, "tags": ["outremer"] }, ... ], "size": 10, "total": 30 } ``` """ def channels do response = ExVideoFactory.Client.get("channels") list = response.body total = get_total_number_of_elements(response) %{ channels: list, size: length(list), total: total } end defp get_total_number_of_elements(%HTTPotion.Response{headers: headers}) do headers['X-Total-Count'] end defp get_total_number_of_elements(_) do 0 end defp force_integer(param) when is_bitstring(param) do param |> String.to_integer() end defp force_integer(param) do param end @doc false def format_query(query) do query |> Enum.map(fn {key, value} -> format_field(key, value) end) |> Map.new() end defp format_field(key, value) do format_field(key, String.split(key, "_"), value) end defp format_field(_, [prefix, "after"], value) do {prefix <> "_at[gte]", format_iso8601(value)} end defp format_field(_, [prefix, "before"], value) do {prefix <> "_at[lte]", format_iso8601(value)} end defp format_field("channels", _, value) do {"channel.id", Enum.join(value, ",")} end defp format_field(key, _, value) do {key, value} end defp format_iso8601(date) do char = String.at(date, 19) case char do " " -> String.replace(date, ~r/\s/, "+") nil -> raise ArgumentError, message: "Unsupported date format: " <> date _ -> date end end @doc """ Get the list of files information based on `Video` identifier. ## Response example ```elixir [ { "created_at": "2018-05-03T18:29:57+02:00", "updated_at": "2018-05-03T18:29:57+02:00", "id": "c465b024-75a1-479d-9195-effe90da9cbd", "format": { "id": "video-mp4", "label": "video/mp4", "type": "video", "mime_type": "video/mp4" }, "storage": "akamai-replay-prod", "path": "/streaming-adaptatif_france-dom-tom/2018/S18/J4/", "filename": "176668575-5aeb383753763-standard5.mp4", "md5_checksum": null, "filesize_bytes": 82099370, "external_ids": { "video_id": "32a2748d-9aa3-430b-9287-fc840f55b012", "legacy_id": "176668575", "group_id": null, "job_id": "2695577", "remote_id": null }, "tags": [], "created_via": "factory", "version": "1525364791000000", "bitrate_kbps": 1471, "video_tracks": [ { "codec_rfc6381": "avc1.4d001f", "bitrate_kbps": 1390, "width": 1024, "height": 576, "frame_rate_fps": 25 } ], "audio_tracks": [ { "codec_rfc6381": "mp4a.40.2", "bitrate_kbps": 81, "sample_rate_hz": 48000, "lang": "fra" } ], "text_tracks": [], "url": "https://replayftv-vh.akamaihd.net/z/streaming-adaptatif_france-dom-tom/2018/S18/J4/176668575-5aeb383753763-standard5.mp4" }, ... ] ``` """ def get_files_for_video_id(id) do query = %{"external_ids.video_id": id, per_page: 1000} ExVideoFactory.Client.get("files", query: query).body end @doc """ Get the list of files on FTP server based on `Video` identifier. ## Response example ```elixir [ "/ftp/file/path/quality_1.mp4", "/ftp/file/path/quality_2.mp4", "/ftp/file/path/quality_3.mp4", "/ftp/file/path/quality_4.mp4", "/ftp/file/path/quality_5.mp4", ] ``` """ def get_ftp_paths_for_video_id(id) do query = %{"external_ids.video_id": id, per_page: 1000} ExVideoFactory.Client.get("files", query: query).body |> get_files end @doc """ Get the list of files on FTP server based on `Video` diffusion identifier. ## Response example ```elixir [ %{ format: "hds_akamai", urls: [] }, %{ format: "m3u8-download", urls: [ "/ftp/file/path/quality_1.mp4", "/ftp/file/path/quality_2.mp4", "/ftp/file/path/quality_3.mp4", "/ftp/file/path/quality_4.mp4" ] }, %{ format: "hls_v5_os", urls: [ "/ftp/file/path/quality_1.mp4", "/ftp/file/path/quality_2.mp4", "/ftp/file/path/quality_3.mp4", "/ftp/file/path/quality_4.mp4", "/ftp/file/path/quality_5.mp4" ] } ] ``` """ def get_ftp_paths_for_id_diffusion(id_diffusion) do query = [idDiffusion: id_diffusion] ExVideoFactory.LegacyClient.get("getInfosOeuvre/v2/", query: query).body |> get_legacy_files end @doc """ Get the list of HTTP URLs for TTML contents based on `Video` identifier. ## Response example ```elixir [ "https://sample-server.com/some/path/caption.ttml", ] ``` """ def get_http_url_for_ttml(id) do query = %{ "external_ids.video_id": id, per_page: 1000, "format.id": "caption-ttml" } ExVideoFactory.Client.get("files", query: query).body |> get_urls([]) end @doc false def get_files(content) do content |> format_files([]) end @doc false def get_legacy_files(content) do content |> Map.get("videos") |> format_legacy_files([]) end defp format_files([], list), do: list defp format_files([item | items], list) do list = case format_file(item) do nil -> list filename -> List.insert_at(list, -1, filename) end format_files(items, list) end defp format_legacy_files([], list), do: list defp format_legacy_files([item | items], list) do list = case item do %{"format" => _format, "url" => nil} -> list %{"format" => format, "url" => url} -> item = %{ format: format, urls: format_url(url) } List.insert_at(list, -1, item) end format_legacy_files(items, list) end defp format_url("http://replayftv-vh.akamaihd.net/i/" <> url) do list = String.split(url, ",") [base_root, _extension] = Enum.filter(list, fn item -> !is_standard(item) end) qualities = Enum.filter(list, fn item -> is_standard(item) end) for quality <- qualities do "/343079/http/#{base_root}#{quality}.mp4" end end defp format_url(_url) do [] end defp is_standard("standard" <> _id), do: true defp is_standard(_something), do: false defp format_file(%{"format" => %{"id" => id, "mime_type" => mime_type}} = item) do case {id, mime_type} do {"video-mp4", "video/mp4"} -> case Map.get(item, "storage") do "akamai-replay-prod" -> path = Map.get(item, "path") filename = Map.get(item, "filename") "/343079/http" <> path <> filename _ -> nil end {"manifest-ism", "application/xml"} -> case Map.get(item, "storage") do "amazon-replay-prod" -> path = Map.get(item, "path") filename = Map.get(item, "filename") path <> filename _ -> nil end _ -> nil end end defp format_file(_item) do nil end @doc false def get_urls([], result), do: result def get_urls([item | items], result) do result = case Map.get(item, "url", nil) do nil -> result url -> List.insert_at(result, -1, url) end get_urls(items, result) end end