nl.vpro.nep.service.impl.NEPItemizeServiceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of media-nep Show documentation
Show all versions of media-nep Show documentation
Support for the several APIs of NEP that POMS is integrating with
package nl.vpro.nep.service.impl;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.beans.factory.annotation.Value;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import nl.vpro.jackson2.Jackson2Mapper;
import nl.vpro.nep.domain.*;
import nl.vpro.nep.service.NEPItemizeService;
import nl.vpro.nep.service.exception.ItemizerStatusException;
import nl.vpro.nep.service.exception.NEPException;
import static nl.vpro.poms.shared.Headers.NPO_DISPATCHED_TO;
import static org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM;
/**
* See also JIRA
* @author Michiel Meeuwissen
* @since 5.6
*/
@Named("NEPItemizeService")
@Slf4j
public class NEPItemizeServiceImpl implements NEPItemizeService {
static final ContentType JSON = ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8);
private final Supplier itemizeLiveKey;
private final String itemizeLiveUrl;
private final Supplier itemizeMidKey;
private final String itemizeMidUrl;
CloseableHttpClient httpClient = HttpClients.custom()
.build();
@Inject
public NEPItemizeServiceImpl(
@Value("${nep.itemizer-api.live.baseUrl}") @NonNull String itemizeLiveUrl,
@Value("${nep.itemizer-api.live.key}") @NonNull String itemizeLiveKey,
@Value("${nep.itemizer-api.mid.baseUrl}") @NonNull String itemizeMidUrl,
@Value("${nep.itemizer-api.mid.key}") @NonNull String itemizeMidKey
) {
this.itemizeLiveKey = new NEPItemizerV1Authenticator(itemizeLiveKey);
this.itemizeLiveUrl = itemizeLiveUrl;
this.itemizeMidKey = new NEPItemizerV1Authenticator(itemizeMidKey);
this.itemizeMidUrl = itemizeMidUrl;
}
public NEPItemizeServiceImpl(String itemizeUrl, String itemizeKey) {
this(itemizeUrl, itemizeKey, itemizeUrl, itemizeKey);
}
protected NEPItemizeServiceImpl(Properties properties) {
this(
properties.getProperty("nep.itemizer-api.baseUrl"),
properties.getProperty("nep.itemizer-api.key")
);
}
@PreDestroy
public void shutdown() throws IOException {
if (httpClient != null) {
httpClient.close();
}
}
private static final ObjectMapper LENIENT = Jackson2Mapper.getLenientInstance();
protected NEPItemizeResponse itemize(@NonNull NEPItemizeRequest request, String itemizeUrl, Supplier itemizeKey) throws NEPException {
String playerUrl = itemizeUrl + "/api/itemizer/job";
log.info("Itemizing {} @ {}", request, playerUrl);
HttpClientContext clientContext = HttpClientContext.create();
String json;
try {
json = LENIENT.writeValueAsString(request);
} catch (JsonProcessingException e) {
throw new NEPException(e, e.getMessage());
}
StringEntity entity = new StringEntity(json, JSON);
HttpPost httpPost = new HttpPost(playerUrl);
authenticate(httpPost, itemizeKey);
httpPost.addHeader(new BasicHeader(HttpHeaders.ACCEPT, JSON.toString()));
log.debug("curl -XPOST -H'Content-Type: application/json' -H'Authorization: {}' -H'Accept: {}' {} --data '{}'", itemizeKey, JSON.toString(), itemizeUrl, json);
httpPost.setEntity(entity);
try (CloseableHttpResponse response = httpClient.execute(httpPost, clientContext)) {
if (response.getStatusLine().getStatusCode() >= 300) {
ByteArrayOutputStream body = new ByteArrayOutputStream();
IOUtils.copy(response.getEntity().getContent(), body);
throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode() + "\n" + json + "\n->\n" + body);
}
return Jackson2Mapper.getLenientInstance().readValue(response.getEntity().getContent(), NEPItemizeResponse.class);
} catch (Exception e) {
throw new NEPException(e, e.getMessage());
}
}
@Override
public NEPItemizeResponse itemizeLive(String channel, Instant start, Instant end, Integer max_bitrate) throws NEPException {
try {
return itemize(
NEPItemizeRequest.builder()
.identifier(channel)
.starttime(
NEPItemizeRequest.fromInstant(start)
.orElseThrow(IllegalArgumentException::new)
)
.endtime(
NEPItemizeRequest.fromInstant(end)
.orElseThrow(IllegalArgumentException::new)
)
.max_bitrate(max_bitrate)
.build(),
itemizeLiveUrl,
itemizeLiveKey
);
} catch (NEPException e) {
throw new NEPException(e, e.getMessage());
}
}
@Override
public NEPItemizeResponse itemizeMid(String mid, Duration start, Duration end, Integer max_bitrate) throws NEPException {
try {
return itemize(
NEPItemizeRequest.builder()
.starttime(NEPItemizeRequest.fromDuration(start, Duration.ZERO))
.endtime(NEPItemizeRequest.fromDuration(end).orElseThrow(IllegalArgumentException::new))
.identifier(mid).max_bitrate(max_bitrate).build(),
itemizeMidUrl,
itemizeMidKey
);
} catch (NEPException e) {
throw new NEPException(e, e.getMessage());
}
}
private static final Set GRAB_SCREEN_HEADERS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(HttpHeaders.CONTENT_TYPE.toLowerCase(), HttpHeaders.CONTENT_LENGTH.toLowerCase())));
protected void grabScreen(@NonNull String identifier, @NonNull String time, @NonNull BiConsumer headers, @NonNull OutputStream outputStream, String itemizeUrl, Supplier key) throws NEPException {
HttpClientContext clientContext = HttpClientContext.create();
String framegrabber = itemizeUrl + "/api/framegrabber?identifier=" + identifier + "&time=" + time;
HttpGet get = new HttpGet(framegrabber);
authenticate(get, key);
get.addHeader(new BasicHeader(HttpHeaders.ACCEPT, APPLICATION_OCTET_STREAM.toString()));
headers.accept(NPO_DISPATCHED_TO, framegrabber);
log.info("Getting {}", framegrabber);
try (CloseableHttpResponse execute = httpClient.execute(get, clientContext)) {
if (execute.getStatusLine().getStatusCode() == 200) {
for (Header h : execute.getAllHeaders()) {
if (GRAB_SCREEN_HEADERS.contains(h.getName().toLowerCase())) {
headers.accept(h.getName(), h.getValue());
}
}
IOUtils.copy(execute.getEntity().getContent(), outputStream);
} else {
StringWriter result = new StringWriter();
IOUtils.copy(execute.getEntity().getContent(), result, Charset.defaultCharset());
throw new RuntimeException(result.toString());
}
} catch (Exception e) {
throw new NEPException(e, e.getMessage());
}
}
@Override
public void grabScreenMid(String mid, Duration offset, @NonNull BiConsumer headers, OutputStream outputStream) throws NEPException {
String durationString = DurationFormatUtils.formatDuration(offset.toMillis(), "HH:mm:ss.SSS", true);
try {
grabScreen(mid, durationString, headers, outputStream, itemizeMidUrl, itemizeMidKey);
} catch (NEPException e) {
throw new NEPException(e, e.getMessage());
}
}
@Override
public void grabScreenLive(String channel, Instant instant, @NonNull BiConsumer headers, OutputStream outputStream) throws NEPException {
try {
grabScreen(channel,
NEPItemizeRequest.fromInstant(instant)
.orElseThrow(() -> new IllegalArgumentException("Instant " + instant + " could not be formatted")),
headers, outputStream, itemizeLiveUrl, itemizeLiveKey);
} catch (NEPException e) {
throw new NEPException(e, e.getMessage());
}
}
@Override
public String getLiveItemizerString() {
return itemizeLiveUrl;
}
@Override
public String getMidItemizerString() {
return itemizeMidUrl;
}
@Override
public ItemizerStatusResponse getLiveItemizerJobStatus(String jobId) {
return getItemizerJobStatus(itemizeLiveUrl, itemizeLiveKey, jobId);
}
@Override
public ItemizerStatusResponse getMidItemizerJobStatus(String jobId) {
return getItemizerJobStatus(itemizeMidUrl, itemizeMidKey, jobId);
}
protected ItemizerStatusResponse getItemizerJobStatus(String url, Supplier key, String jobId) {
String jobs = url + "/api/itemizer/jobs/" + jobId + "/status";
HttpGet get = new HttpGet(jobs);
authenticate(get, key);
HttpClientContext clientContext = HttpClientContext.create();
try (CloseableHttpResponse execute = httpClient.execute(get, clientContext)) {
if (execute.getStatusLine().getStatusCode() == 200) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
IOUtils.copy(execute.getEntity().getContent(), outputStream);
return Jackson2Mapper.getLenientInstance().readValue(outputStream.toByteArray(), ItemizerStatusResponse.class);
}
if (execute.getStatusLine().getStatusCode() == 404) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
IOUtils.copy(execute.getEntity().getContent(), outputStream);
FailureResponse failureResponse = Jackson2Mapper.getLenientInstance().readValue(outputStream.toByteArray(), FailureResponse.class);
throw new ItemizerStatusException(404, failureResponse);
} else {
StringWriter result = new StringWriter();
IOUtils.copy(execute.getEntity().getContent(), result, Charset.defaultCharset());
throw new ItemizerStatusException(execute.getStatusLine().getStatusCode(), result.toString());
}
} catch (NEPException nepException) {
throw nepException;
} catch (Exception e) {
throw new NEPException(e, e.getMessage());
}
}
private void authenticate(HttpUriRequest request, Supplier key) {
request.addHeader(new BasicHeader(HttpHeaders.AUTHORIZATION, key.get()));
}
@Override
public String toString() {
return getClass().getSimpleName() + ":l:" + itemizeLiveUrl + ",m:" + itemizeMidUrl;
}
@Override
@PreDestroy
public synchronized void close() throws Exception {
if (httpClient != null) {
httpClient.close();
httpClient = null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy