1
1
use std:: io:: Cursor ;
2
2
3
3
use axum:: {
4
- extract:: Path ,
5
- http:: StatusCode ,
6
- response:: IntoResponse
4
+ body:: Body , extract:: Path , http:: { Response , StatusCode } , response:: IntoResponse
7
5
} ;
8
6
use image:: ImageFormat ;
9
7
use regex:: Regex ;
10
8
use tokio:: { fs, sync:: OnceCell } ;
11
9
12
- use crate :: utils:: { convert:: convert_image , env:: ENV_CONFIG } ;
10
+ use crate :: utils:: { convert:: { convert_animated_image , convert_static_image } , env:: { EnvConfig , ENV_CONFIG } } ;
13
11
14
12
15
13
static URL_FILE_REGEX : OnceCell < Regex > = OnceCell :: const_new ( ) ;
@@ -18,43 +16,123 @@ static URL_FILE_REGEX: OnceCell<Regex> = OnceCell::const_new();
18
16
pub ( crate ) async fn handler (
19
17
Path ( ( season, episode, target) ) : Path < ( String , String , String ) > ,
20
18
) -> impl IntoResponse {
19
+ let season = season. to_lowercase ( ) ;
20
+ let episode = episode. to_lowercase ( ) ;
21
21
let target = target. to_lowercase ( ) ;
22
22
23
23
let regex = match URL_FILE_REGEX . get_or_try_init (
24
24
|| async {
25
- Regex :: new ( r"(?P<target_frame>[0-9]*)\.(?P<target_format>jpg|jpeg|png|webp)" )
25
+ Regex :: new ( r"(?P<target_frame>[0-9]*)\.(?P<target_format>jpg|jpeg|png|webp)|(?P<target_anim_frame>[0-9]*-[0-9]*)\.(?P<target_anim_format>png|webp|apng|gif) " )
26
26
}
27
27
) . await {
28
28
Ok ( regex) => regex,
29
29
Err ( _) => return StatusCode :: INTERNAL_SERVER_ERROR . into_response ( ) ,
30
30
} ;
31
31
32
- let ( target_frame, target_format) = match regex. captures ( & target) {
32
+ let (
33
+ target_frame,
34
+ target_format,
35
+ is_animated
36
+ ) = match regex. captures ( & target) {
33
37
Some ( result) => {
34
- if result. len ( ) != 3 {
35
- return StatusCode :: BAD_REQUEST . into_response ( )
36
- }
38
+ let result = result
39
+ . name ( "target_frame" )
40
+ . zip ( result. name ( "target_format" ) )
41
+ . zip ( Some ( false ) )
42
+ . or (
43
+ result. name ( "target_anim_frame" )
44
+ . zip ( result. name ( "target_anim_format" ) )
45
+ . zip ( Some ( true ) )
46
+ ) ;
37
47
38
- (
39
- result. name ( "target_frame" ) . unwrap ( ) . as_str ( ) ,
40
- result. name ( "target_format" ) . unwrap ( ) . as_str ( )
41
- )
42
- } ,
43
- None => {
44
- return StatusCode :: BAD_REQUEST . into_response ( ) ;
48
+ if let Some ( result) = result {
49
+ (
50
+ result. 0 . 0 . as_str ( ) ,
51
+ result. 0 . 1 . as_str ( ) ,
52
+ result. 1
53
+ )
54
+ } else {
55
+ return StatusCode :: BAD_REQUEST . into_response ( ) ;
56
+ }
45
57
} ,
58
+ None => return StatusCode :: BAD_REQUEST . into_response ( )
46
59
} ;
47
60
48
61
let env_config = match ENV_CONFIG . get ( ) {
49
62
Some ( env) => env,
50
- None => return StatusCode :: INTERNAL_SERVER_ERROR . into_response ( ) ,
63
+ None => return StatusCode :: INTERNAL_SERVER_ERROR . into_response ( )
51
64
} ;
52
65
53
66
let season_name = match season. as_str ( ) {
54
67
"mygo" => "" ,
55
68
"ave" | "ave-mujica" => "ave-" ,
56
69
_ => ""
57
70
} ;
71
+
72
+ if is_animated {
73
+ handle_animated_image (
74
+ env_config,
75
+ & season_name,
76
+ & episode,
77
+ target_frame,
78
+ target_format
79
+ ) . await
80
+ } else {
81
+ handle_static_image (
82
+ env_config,
83
+ & season_name,
84
+ & episode,
85
+ target_frame,
86
+ target_format
87
+ ) . await
88
+ }
89
+ }
90
+
91
+ async fn handle_animated_image (
92
+ env_config : & EnvConfig ,
93
+ season_name : & str ,
94
+ episode : & str ,
95
+ target_frame : & str ,
96
+ target_format : & str
97
+ ) -> Response < Body > {
98
+ let ( start_frame, end_frame) = match target_frame. split_once ( "-" ) {
99
+ Some ( r) => {
100
+ match r. 0 . parse :: < u32 > ( ) . ok ( ) . zip ( r. 1 . parse :: < u32 > ( ) . ok ( ) ) {
101
+ Some ( r) => r,
102
+ None => return StatusCode :: BAD_REQUEST . into_response ( ) ,
103
+ }
104
+ } ,
105
+ None => return StatusCode :: BAD_REQUEST . into_response ( )
106
+ } ;
107
+
108
+ if start_frame >= end_frame {
109
+ return StatusCode :: BAD_REQUEST . into_response ( ) ;
110
+ }
111
+
112
+ let target_format = match target_format {
113
+ "png" | "apng" => ImageFormat :: Png ,
114
+ "gif" => ImageFormat :: Gif ,
115
+ "webp" => ImageFormat :: WebP ,
116
+ _ => return StatusCode :: UNSUPPORTED_MEDIA_TYPE . into_response ( )
117
+ } ;
118
+
119
+ convert_animated_image (
120
+ env_config,
121
+ start_frame,
122
+ end_frame,
123
+ & season_name,
124
+ & episode,
125
+ target_format
126
+ ) . await
127
+ }
128
+
129
+ async fn handle_static_image (
130
+ env_config : & EnvConfig ,
131
+ season_name : & str ,
132
+ episode : & str ,
133
+ target_frame : & str ,
134
+ target_format : & str
135
+ ) -> Response < Body > {
58
136
let source_file_path = format ! (
59
137
"{}/{}{}_{}.webp" ,
60
138
env_config. image_source_path,
@@ -76,18 +154,12 @@ pub(crate) async fn handler(
76
154
Err ( _) => return StatusCode :: INTERNAL_SERVER_ERROR . into_response ( ) ,
77
155
} ;
78
156
79
- match target_format {
80
- "jpg" | "jpeg" => {
81
- convert_image ( reader, ImageFormat :: Jpeg )
82
- }
83
- "png" => {
84
- convert_image ( reader, ImageFormat :: Png )
85
- }
86
- "webp" => {
87
- convert_image ( reader, ImageFormat :: WebP )
88
- }
89
- _ => {
90
- return StatusCode :: UNSUPPORTED_MEDIA_TYPE . into_response ( ) ;
91
- }
92
- }
157
+ let target_format = match target_format {
158
+ "jpg" | "jpeg" => ImageFormat :: Jpeg ,
159
+ "png" => ImageFormat :: Png ,
160
+ "webp" => ImageFormat :: WebP ,
161
+ _ => return StatusCode :: UNSUPPORTED_MEDIA_TYPE . into_response ( )
162
+ } ;
163
+
164
+ convert_static_image ( reader, target_format) . await
93
165
}
0 commit comments