@@ -58,6 +58,48 @@ func appendFFmpegLiveOutputStreamArgs(args []string, audioOnly bool) []string {
5858 )
5959}
6060
61+ func appendYtDlpVideoConfigArgs (args []string , configArgs string ) []string {
62+ skipValue := false
63+ for _ , arg := range strings .Split (configArgs , "," ) {
64+ trimmedArg := strings .TrimSpace (arg )
65+ if skipValue {
66+ skipValue = false
67+ continue
68+ }
69+ if trimmedArg == "-o" || trimmedArg == "--output" || trimmedArg == "-P" || trimmedArg == "--paths" {
70+ skipValue = true
71+ continue
72+ }
73+ if strings .HasPrefix (trimmedArg , "--output=" ) || strings .HasPrefix (trimmedArg , "--paths=" ) ||
74+ (len (trimmedArg ) > 2 && (strings .HasPrefix (trimmedArg , "-o" ) || strings .HasPrefix (trimmedArg , "-P" ))) {
75+ continue
76+ }
77+ if trimmedArg != "" {
78+ args = append (args , trimmedArg )
79+ }
80+ }
81+ return args
82+ }
83+
84+ func twitchVideoDownloadArgs (quality , url , outputPath , configArgs string ) []string {
85+ args := []string {
86+ "-f" , quality ,
87+ url ,
88+ "-o" , outputPath ,
89+ "--merge-output-format" , "mp4" , "--no-part" ,
90+ "--no-warnings" , "--progress" , "--newline" , "--no-check-certificate" ,
91+ // Twitch VOD playlists can change their fMP4 initialization segment after a
92+ // discontinuity. The native HLS downloader rejects a second EXT-X-MAP, while
93+ // ffmpeg handles the new initialization section correctly.
94+ "--hls-prefer-ffmpeg" ,
95+ }
96+
97+ // User arguments are intentionally last so an explicit downloader preference
98+ // in the configuration can override the default. Output options are excluded
99+ // so the application-owned temporary path remains authoritative.
100+ return appendYtDlpVideoConfigArgs (args , configArgs )
101+ }
102+
61103// DownloadTwitchVideo downloads a Twitch video.
62104func DownloadTwitchVideo (ctx context.Context , video ent.Vod ) error {
63105 // Get video channel
@@ -115,26 +157,13 @@ func DownloadTwitchVideo(ctx context.Context, video ent.Vod) error {
115157 tmpVideoDownloadExt := filepath .Ext (video .TmpVideoDownloadPath )
116158 tmpVideoDownloadPathNoExt := strings .TrimSuffix (video .TmpVideoDownloadPath , tmpVideoDownloadExt )
117159
118- // Get user arguments from config
119- configYtDlpArgs := config .Get ().Parameters .YtDlpVideo
120- configYtDlpArgsArr := strings .Split (configYtDlpArgs , "," )
121-
122- var cmdArgs []string
123- cmdArgs = append (cmdArgs ,
124- "-f" , qualityString ,
160+ cmdArgs := twitchVideoDownloadArgs (
161+ qualityString ,
125162 url ,
126- "-o" , fmt .Sprintf ("%s.%%(ext)s" , tmpVideoDownloadPathNoExt ),
127- "--merge-output-format" , "mp4" , "--no-part" ,
128- "--no-warnings" , "--progress" , "--newline" , "--no-check-certificate" ,
163+ fmt .Sprintf ("%s.%%(ext)s" , tmpVideoDownloadPathNoExt ),
164+ config .Get ().Parameters .YtDlpVideo ,
129165 )
130166
131- // Sanitize config args before appending
132- for _ , arg := range configYtDlpArgsArr {
133- if strings .TrimSpace (arg ) != "" {
134- cmdArgs = append (cmdArgs , arg )
135- }
136- }
137-
138167 // Create yt-dlp command
139168 cmd , cookieFile , err := ytdlpSvc .CreateCommand (ctx , cmdArgs , true )
140169 defer func () {
0 commit comments