From 005df3225b5c24977902f1e41fc99baf9bad64fb Mon Sep 17 00:00:00 2001 From: "hehihoho3@gmail.com" Date: Mon, 3 Aug 2026 11:59:12 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=88=8F=ED=8F=BC=20=ED=81=90=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0(ShortformJob/Clip)=EC=99=80=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rawOutput은 파싱 성공 여부와 무관하게 원문 보존. videoId 유니크로 중복 등록 방지. Co-Authored-By: Claude Fable 5 --- .../domain/shortform/ShortformClip.java | 51 ++++++++++++ .../domain/shortform/ShortformJob.java | 79 +++++++++++++++++++ .../shortform/ShortformJobRepository.java | 12 +++ .../domain/shortform/ShortformJobStatus.java | 3 + 4 files changed, 145 insertions(+) create mode 100644 src/main/java/com/hlab/yanalyst/domain/shortform/ShortformClip.java create mode 100644 src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJob.java create mode 100644 src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobRepository.java create mode 100644 src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobStatus.java diff --git a/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformClip.java b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformClip.java new file mode 100644 index 0000000..74859dd --- /dev/null +++ b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformClip.java @@ -0,0 +1,51 @@ +package com.hlab.yanalyst.domain.shortform; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** 작업 하나에서 나온 구간(ID 1~5) 하나. capcutJson이 실제 편집 프로그램에 붙여넣는 값. */ +@Entity +@Table(name = "shortform_clip") +@Getter +@NoArgsConstructor +public class ShortformClip { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "job_id") + private ShortformJob job; + + @Column(nullable = false) + private Integer clipNo; + + @Column(length = 100) + private String titleTop; + + @Column(length = 200) + private String titleMain; + + @Column(columnDefinition = "TEXT") + private String capcutJson; + + @Column(columnDefinition = "TEXT") + private String titleCandidates; + + @Column(columnDefinition = "TEXT") + private String durationTable; + + static ShortformClip of(ShortformJob job, ParsedClip p) { + ShortformClip clip = new ShortformClip(); + clip.job = job; + clip.clipNo = p.clipNo(); + clip.titleTop = p.titleTop(); + clip.titleMain = p.titleMain(); + clip.capcutJson = p.capcutJson(); + clip.titleCandidates = p.titleCandidates(); + clip.durationTable = p.durationTable(); + return clip; + } +} diff --git a/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJob.java b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJob.java new file mode 100644 index 0000000..50f734d --- /dev/null +++ b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJob.java @@ -0,0 +1,79 @@ +package com.hlab.yanalyst.domain.shortform; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 숏폼 큐 작업 하나 = 유튜브 영상 하나. Opal "숏폼 생성기" 실행 결과를 담는다. + * + *

rawOutput은 파싱 성공 여부와 무관하게 항상 원문을 보존한다 — 파서가 놓친 정보는 + * 여기서 복구할 수 있다. + */ +@Entity +@Table(name = "shortform_job", + indexes = @Index(name = "idx_sfj_video_id", columnList = "videoId", unique = true)) +@Getter +@NoArgsConstructor +public class ShortformJob { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 500) + private String youtubeUrl; + + @Column(nullable = false, length = 20) + private String videoId; + + /** 결과의 첫 클립 title_main. 목록 표시에 쓴다. */ + @Column(length = 300) + private String title; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private ShortformJobStatus status = ShortformJobStatus.PENDING; + + @Column(columnDefinition = "TEXT") + private String rawOutput; + + @CreationTimestamp + @Column(updatable = false) + private LocalDateTime createdAt; + + private LocalDateTime completedAt; + + @OneToMany(mappedBy = "job", cascade = CascadeType.ALL, orphanRemoval = true) + @OrderBy("clipNo ASC") + private List clips = new ArrayList<>(); + + public static ShortformJob create(String youtubeUrl, String videoId) { + ShortformJob job = new ShortformJob(); + job.youtubeUrl = youtubeUrl; + job.videoId = videoId; + return job; + } + + public void applyResult(String raw, List parsed) { + this.rawOutput = raw; + this.clips.clear(); + for (ParsedClip p : parsed) { + this.clips.add(ShortformClip.of(this, p)); + } + this.title = parsed.isEmpty() ? this.title : parsed.get(0).titleMain(); + this.status = ShortformJobStatus.DONE; + this.completedAt = LocalDateTime.now(); + } + + public void markFailed(String raw) { + this.rawOutput = raw; + this.status = ShortformJobStatus.FAILED; + this.completedAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobRepository.java b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobRepository.java new file mode 100644 index 0000000..f2d8b14 --- /dev/null +++ b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobRepository.java @@ -0,0 +1,12 @@ +package com.hlab.yanalyst.domain.shortform; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface ShortformJobRepository extends JpaRepository { + Optional findByVideoId(String videoId); + List findAllByOrderByCreatedAtDesc(); + List findByStatusOrderByCreatedAtAsc(ShortformJobStatus status); +} diff --git a/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobStatus.java b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobStatus.java new file mode 100644 index 0000000..13697af --- /dev/null +++ b/src/main/java/com/hlab/yanalyst/domain/shortform/ShortformJobStatus.java @@ -0,0 +1,3 @@ +package com.hlab.yanalyst.domain.shortform; + +public enum ShortformJobStatus { PENDING, DONE, FAILED }