preset.go
⎇
Raw
1package service
2
3import (
4 "fmt"
5 "strings"
6 "vidarchive/internal/models"
7 "vidarchive/internal/repository"
8)
9
10type PresetService struct {
11 repo *repository.PresetRepository
12}
13
14func NewPresetService(repo *repository.PresetRepository) *PresetService {
15 return &PresetService{repo: repo}
16}
17
18func (s *PresetService) Create(p *models.Preset) error {
19 if p.IsDefault {
20 if err := s.repo.ClearDefault(); err != nil {
21 return err
22 }
23 }
24 return s.repo.Create(p)
25}
26
27func (s *PresetService) GetByID(id int64) (*models.Preset, error) {
28 return s.repo.GetByID(id)
29}
30
31func (s *PresetService) GetAll() ([]*models.Preset, error) {
32 return s.repo.GetAll()
33}
34
35func (s *PresetService) GetDefault() (*models.Preset, error) {
36 return s.repo.GetDefault()
37}
38
39func (s *PresetService) Update(p *models.Preset) error {
40 if p.IsDefault {
41 if err := s.repo.ClearDefault(); err != nil {
42 return err
43 }
44 }
45 return s.repo.Update(p)
46}
47
48func (s *PresetService) Delete(id int64) error {
49 return s.repo.Delete(id)
50}
51
52func (s *PresetService) BuildArgs(p *models.Preset, formatOverride, customFlags string) []string {
53 var args []string
54
55 // Format selection
56 if formatOverride != "" {
57 args = append(args, "-f", formatOverride)
58 } else if p.FormatMode == "custom" && p.CustomFormat != "" {
59 args = append(args, "-f", p.CustomFormat)
60 } else if p.FormatMode == "preset" && p.Format != "" {
61 if p.Quality != "" {
62 args = append(args, "-f", applyHeightCap(p.Format, p.Quality))
63 } else {
64 args = append(args, "-f", p.Format)
65 }
66 }
67 // If FormatMode is "default" or empty, don't pass -f (let yt-dlp choose)
68
69 // Audio extraction
70 if p.ExtractAudio {
71 args = append(args, "--extract-audio")
72 if p.AudioFormat != "" {
73 args = append(args, "--audio-format", p.AudioFormat)
74 }
75 }
76
77 // Subtitles
78 if p.EmbedSubs {
79 args = append(args, "--embed-subs")
80 if p.SubLangs != "" {
81 args = append(args, "--sub-langs", p.SubLangs)
82 }
83 }
84
85 // Thumbnail
86 if p.EmbedThumbnail {
87 args = append(args, "--embed-thumbnail")
88 }
89
90 // Metadata
91 if p.EmbedMetadata {
92 args = append(args, "--embed-metadata")
93 }
94
95 // Info JSON
96 if p.WriteInfoJSON {
97 args = append(args, "--write-info-json")
98 }
99
100 // Custom flags
101 if p.CustomFlags != "" {
102 args = append(args, strings.Fields(p.CustomFlags)...)
103 }
104
105 if customFlags != "" {
106 args = append(args, strings.Fields(customFlags)...)
107 }
108
109 return args
110}
111
112// applyHeightCap adds a max-height filter to a format selector. For a combined
113// "video+audio" selector it binds the filter to the video stream only (e.g.
114// "bestvideo[height<=720]+bestaudio"), since applying it to the whole expression
115// would wrongly constrain the audio selector. It always appends an unfiltered
116// fallback so a missing capped variant still resolves.
117func applyHeightCap(format, maxHeight string) string {
118 parts := strings.Split(format, "+")
119 parts[0] = fmt.Sprintf("%s[height<=%s]", parts[0], maxHeight)
120 return strings.Join(parts, "+") + "/" + format
121}
122
123func (s *PresetService) EffectiveFlags(p *models.Preset, formatOverride, customFlags string) string {
124 args := s.BuildArgs(p, formatOverride, customFlags)
125 return strings.Join(args, " ")
126}
127