@@ -24,14 +24,36 @@ public Task CreateJobAsync(Video video,
2424 string fileName = NameHelper . GetFileName ( video , Name ) ;
2525 video . Filename = fileName ;
2626 string [ ] ? command = null ;
27- string [ ] args ;
27+ string [ ] args = BuildYtdlpArgs ( fileName , url , liveFromStart ) ;
2828
29+ if ( useCookiesFile )
30+ //args = ["--cookies", $"/cookies/{video.ChannelId}.txt", .. args];
31+ // Remove this workaround if issue resolved.
32+ // https://github.com/yt-dlp/yt-dlp/issues/5977#issuecomment-2121742572
33+ ( command , args ) = BuildCookiesHackCommand ( fileName , url , liveFromStart , video . ChannelId , mountPath ) ;
34+
35+ return jobService . CreateInstanceAsync ( deploymentName : instanceName ,
36+ containerName : instanceName ,
37+ imageName : "yt-dlp" ,
38+ fileName : fileName ,
39+ command : command ,
40+ args : args ,
41+ mountPath : mountPath ,
42+ cancellation : cancellation ) ;
43+ }
44+
45+ /// <summary>
46+ /// Builds yt-dlp command line arguments.
47+ /// </summary>
48+ private string [ ] BuildYtdlpArgs ( string fileName , string url , bool liveFromStart )
49+ {
2950 // Live-from-start mode uses minimal args to avoid compatibility issues
3051 // Working example: yt-dlp --live-from-start --no-part -o filename url
3152 if ( liveFromStart )
3253 {
33- args =
54+ return
3455 [
56+ "-v" ,
3557 "--live-from-start" ,
3658 "--embed-thumbnail" ,
3759 "--embed-metadata" ,
@@ -40,76 +62,70 @@ public Task CreateJobAsync(Video video,
4062 url
4163 ] ;
4264 }
43- else
65+
66+ var args = new List < string >
4467 {
45- args =
46- [
47- "--ignore-config" ,
48- "--retries" , "30" ,
49- "--concurrent-fragments" , "16" ,
50- "--merge-output-format" , "mp4" ,
51- "-S" , "+proto:http,+codec:h264" ,
52- "--embed-thumbnail" ,
53- "--embed-metadata" ,
54- "--no-part" ,
55- "-o" , fileName ,
56- url
57- ] ;
68+ "-v" ,
69+ "--ignore-config" ,
70+ "--retries" , "30" ,
71+ "--concurrent-fragments" , "16" ,
72+ "--merge-output-format" , "mp4" ,
73+ "-S" , "+proto:http,+codec:h264" ,
74+ "--embed-thumbnail" ,
75+ "--embed-metadata" ,
76+ "--no-part" ,
77+ "-o" , fileName ,
78+ url
79+ } ;
5880
59- // Workaround for twitcasting ERROR:
60- // Initialization fragment found after media fragments, unable to download
61- // https://github.com/yt-dlp/yt-dlp/issues/5497
62- if ( url . Contains ( "twitcasting.tv" ) ) args = [ "--downloader" , "ffmpeg" , .. args ] ;
81+ // Workaround for twitcasting ERROR:
82+ // Initialization fragment found after media fragments, unable to download
83+ // https://github.com/yt-dlp/yt-dlp/issues/5497
84+ if ( url . Contains ( "twitcasting.tv" ) )
85+ {
86+ args . InsertRange ( 0 , [ "--downloader" , "ffmpeg" ] ) ;
6387 }
6488
65- if ( useCookiesFile )
66- //args = ["--cookies", $"/cookies/{video.ChannelId}.txt", .. args];
67- // Remove this workaround if issue resolved.
68- // https://github.com/yt-dlp/yt-dlp/issues/5977#issuecomment-2121742572
69- ( command , args ) = copyCookiesHack ( ) ;
89+ return [ .. args ] ;
90+ }
7091
71- return jobService . CreateInstanceAsync ( deploymentName : instanceName ,
72- containerName : instanceName ,
73- imageName : "yt-dlp" ,
74- fileName : fileName ,
75- command : command ,
76- args : args ,
77- mountPath : mountPath ,
78- cancellation : cancellation ) ;
92+ /// <summary>
93+ /// Converts arguments array to shell command string with proper quoting.
94+ /// </summary>
95+ private static string ArgsToCommandString ( string [ ] args )
96+ {
97+ return string . Join ( " " , args . Select ( arg =>
98+ arg . Contains ( ' ' ) || arg . Contains ( ' \' ' ) ? $ "' { arg . Replace ( "'" , "' \\ ''" ) } '" : arg ) ) ;
99+ }
79100
80- ( string [ ] command , string [ ] args ) copyCookiesHack ( )
81- {
82- // Cookies file has to be mounted elsewhere and then cp.
83- // Because yt-dlp does not support using cookies file in Read-only file system.
84- // Which it is how K8s handle secrets.
85-
86- // original ENTRYPOINT: "dumb-init", "--", "yt-dlp", "--no-cache-dir"
87-
88- command = [ "dumb-init" , "--" , "sh" , "-c" ] ;
89-
90- // cp under mountPath to make sure the permission is writable
91- // Live-from-start mode uses minimal args to avoid compatibility issues
92- if ( liveFromStart )
93- {
94- args =
95- [
96- $ "cp -r /cookies { mountPath } /cookies && yt-dlp --live-from-start --no-part --cookies { mountPath } /cookies/{ video . ChannelId } .txt -o '{ fileName } ' '{ url } '"
97- ] ;
98- }
99- else
100- {
101- args =
102- [
103- $ "cp -r /cookies { mountPath } /cookies && yt-dlp --ignore-config --retries 30 --concurrent-fragments 16 --merge-output-format mp4 -S '+proto:http,+codec:h264' --embed-thumbnail --embed-metadata --no-part --cookies { mountPath } /cookies/{ video . ChannelId } .txt -o '{ fileName } ' '{ url } '"
104- ] ;
105-
106- // Workaround for twitcasting ERROR:
107- // Initialization fragment found after media fragments, unable to download
108- // https://github.com/yt-dlp/yt-dlp/issues/5497
109- if ( url . Contains ( "twitcasting.tv" ) ) args [ 0 ] = args [ 0 ] . Replace ( "--ignore-config" , "--ignore-config --downloader ffmpeg" ) ;
110- }
111-
112- return ( command , args ) ;
113- }
101+ /// <summary>
102+ /// Builds command for cookies workaround.
103+ /// Cookies file has to be mounted elsewhere and then copied,
104+ /// because yt-dlp does not support using cookies file in Read-only file system,
105+ /// which is how K8s handles secrets.
106+ /// </summary>
107+ private ( string [ ] command , string [ ] args ) BuildCookiesHackCommand (
108+ string fileName ,
109+ string url ,
110+ bool liveFromStart ,
111+ string channelId ,
112+ string mountPath )
113+ {
114+ // original ENTRYPOINT: "dumb-init", "--", "yt-dlp", "--no-cache-dir"
115+ var command = new [ ] { "dumb-init" , "--" , "sh" , "-c" } ;
116+
117+ // Build yt-dlp args and convert to command string
118+ var ytdlpArgs = BuildYtdlpArgs ( fileName , url , liveFromStart ) ;
119+ var ytdlpCommand = ArgsToCommandString ( ytdlpArgs ) ;
120+
121+ // Insert cookies parameter
122+ var cookiesPath = $ "{ mountPath } /cookies/{ channelId } .txt";
123+ ytdlpCommand = ytdlpCommand . Replace ( "-o " , $ "--cookies { cookiesPath } -o ") ;
124+
125+ // cp under mountPath to make sure the permission is writable
126+ var shellScript = $ "cp -r /cookies { mountPath } /cookies && yt-dlp { ytdlpCommand } ";
127+ var args = new [ ] { shellScript } ;
128+
129+ return ( command , args ) ;
114130 }
115131}
0 commit comments