69 lines
3.7 KiB
Java
69 lines
3.7 KiB
Java
package com.hlab.yanalyst.domain.upload;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
import org.springframework.http.*;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.client.RestTemplate;
|
|
import org.springframework.web.util.UriComponentsBuilder;
|
|
|
|
import java.net.URI;
|
|
import java.net.http.HttpClient;
|
|
import java.net.http.HttpRequest;
|
|
import java.net.http.HttpResponse;
|
|
import java.nio.file.Path;
|
|
import java.time.Duration;
|
|
import java.time.Instant;
|
|
|
|
@Component
|
|
public class YoutubeResumableUploadClient {
|
|
private static final String UPLOAD_URL = "https://www.googleapis.com/upload/youtube/v3/videos";
|
|
private final RestTemplate restTemplate;
|
|
private final ObjectMapper objectMapper;
|
|
private final HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
|
|
|
|
public YoutubeResumableUploadClient(@Qualifier("restTemplate") RestTemplate restTemplate, ObjectMapper objectMapper) {
|
|
this.restTemplate = restTemplate;
|
|
this.objectMapper = objectMapper;
|
|
}
|
|
|
|
public String upload(String accessToken, Path file, long size, String contentType, String title,
|
|
String description, String privacyStatus, Instant publishAt) throws Exception {
|
|
ObjectNode metadata = objectMapper.createObjectNode();
|
|
metadata.putObject("snippet").put("title", title).put("description", description).put("categoryId", "22");
|
|
ObjectNode status = metadata.putObject("status");
|
|
status.put("privacyStatus", publishAt == null ? privacyStatus : "private");
|
|
status.put("selfDeclaredMadeForKids", false);
|
|
if (publishAt != null) status.put("publishAt", publishAt.toString());
|
|
|
|
URI initiateUri = UriComponentsBuilder.fromUriString(UPLOAD_URL)
|
|
.queryParam("uploadType", "resumable").queryParam("part", "snippet,status").build().toUri();
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.setBearerAuth(accessToken);
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
headers.set("X-Upload-Content-Type", contentType);
|
|
headers.set("X-Upload-Content-Length", Long.toString(size));
|
|
ResponseEntity<String> initiated = restTemplate.exchange(initiateUri, HttpMethod.POST,
|
|
new HttpEntity<>(objectMapper.writeValueAsString(metadata), headers), String.class);
|
|
URI sessionUri = initiated.getHeaders().getLocation();
|
|
if (sessionUri == null) throw new IllegalStateException("YouTube 업로드 세션 주소를 받지 못했습니다.");
|
|
|
|
HttpRequest request = HttpRequest.newBuilder(sessionUri).timeout(Duration.ofHours(2))
|
|
.header("Authorization", "Bearer " + accessToken)
|
|
.header("Content-Type", contentType)
|
|
.header("Content-Length", Long.toString(size))
|
|
.PUT(HttpRequest.BodyPublishers.ofFile(file)).build();
|
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
|
throw new IllegalStateException("YouTube 업로드 실패 (HTTP " + response.statusCode() + "): "
|
|
+ response.body().substring(0, Math.min(500, response.body().length())));
|
|
}
|
|
JsonNode result = objectMapper.readTree(response.body());
|
|
String videoId = result.path("id").asText("");
|
|
if (videoId.isBlank()) throw new IllegalStateException("YouTube 영상 ID를 받지 못했습니다.");
|
|
return videoId;
|
|
}
|
|
}
|