1#![allow(non_camel_case_types)]
4#![allow(non_upper_case_globals)]
5
6use std::ffi::CStr;
7use std::os::raw::c_char;
8
9#[inline]
10pub unsafe fn raw_into_string(raw_string: *const c_char) -> String {
11 CStr::from_ptr(raw_string).to_string_lossy().into_owned()
12}
13
14pub type element_category_t = i32;
15pub const kElementIterateFirst: element_category_t = 0;
16pub const kElementAnimeSeason: element_category_t = kElementIterateFirst;
17pub const kElementAnimeSeasonPrefix: element_category_t = 1;
18pub const kElementAnimeTitle: element_category_t = 2;
19pub const kElementAnimeType: element_category_t = 3;
20pub const kElementAnimeYear: element_category_t = 4;
21pub const kElementAudioTerm: element_category_t = 5;
22pub const kElementDeviceCompatibility: element_category_t = 6;
23pub const kElementEpisodeNumber: element_category_t = 7;
24pub const kElementEpisodeNumberAlt: element_category_t = 8;
25pub const kElementEpisodePrefix: element_category_t = 9;
26pub const kElementEpisodeTitle: element_category_t = 10;
27pub const kElementFileChecksum: element_category_t = 11;
28pub const kElementFileExtension: element_category_t = 12;
29pub const kElementFileName: element_category_t = 13;
30pub const kElementLanguage: element_category_t = 14;
31pub const kElementOther: element_category_t = 15;
32pub const kElementReleaseGroup: element_category_t = 16;
33pub const kElementReleaseInformation: element_category_t = 17;
34pub const kElementReleaseVersion: element_category_t = 18;
35pub const kElementSource: element_category_t = 19;
36pub const kElementSubtitles: element_category_t = 20;
37pub const kElementVideoResolution: element_category_t = 21;
38pub const kElementVideoTerm: element_category_t = 22;
39pub const kElementVolumeNumber: element_category_t = 23;
40pub const kElementVolumePrefix: element_category_t = 24;
41pub const kElementIterateLast: element_category_t = 25;
42pub const kElementUnknown: element_category_t = kElementIterateLast;
43
44extern "C" {
45 pub fn string_free(string: *mut c_char);
46}
47
48#[repr(C)]
49pub struct string_array_t {
50 _unused: [u8; 0],
51}
52
53extern "C" {
54 pub fn string_array_new() -> *mut string_array_t;
55 pub fn string_array_size(array: *const string_array_t) -> usize;
56 pub fn string_array_at(array: *const string_array_t, pos: usize) -> *const c_char;
57 pub fn string_array_add(array: *mut string_array_t, value: *const c_char);
58 pub fn string_array_free(array: *mut string_array_t);
59}
60
61#[repr(C)]
62pub struct options_t {
63 _unused: [u8; 0],
64}
65
66extern "C" {
67 pub fn options_allowed_delimiters(options: *mut options_t, allowed_delimiters: *const c_char);
68 pub fn options_ignored_strings(options: *mut options_t, ignored_strings: *const string_array_t);
69 pub fn options_parse_episode_number(options: *mut options_t, parse_episode_number: bool);
70 pub fn options_parse_episode_title(options: *mut options_t, parse_episode_title: bool);
71 pub fn options_parse_file_extension(options: *mut options_t, parse_file_extension: bool);
72 pub fn options_parse_release_group(option: *mut options_t, parse_release_group: bool);
73}
74
75#[repr(C)]
76pub struct element_pair_t {
77 pub category: element_category_t,
78 pub value: *mut c_char,
79}
80
81#[repr(C)]
82pub struct elements_t {
83 _unused: [u8; 0],
84}
85
86extern "C" {
87 pub fn elements_empty(elements: *const elements_t) -> bool;
88 pub fn elements_empty_category(
89 elements: *const elements_t,
90 category: element_category_t,
91 ) -> bool;
92 pub fn elements_count(elements: *const elements_t) -> usize;
93 pub fn elements_count_category(
94 elements: *const elements_t,
95 category: element_category_t,
96 ) -> usize;
97 pub fn elements_at(elements: *const elements_t, pos: usize) -> element_pair_t;
98 pub fn elements_get(elements: *const elements_t, category: element_category_t) -> *mut c_char;
99 pub fn elements_get_all(
100 elements: *const elements_t,
101 category: element_category_t,
102 ) -> *mut string_array_t;
103}
104
105#[repr(C)]
106pub struct anitomy_t {
107 _unused: [u8; 0],
108}
109
110extern "C" {
111 pub fn anitomy_new() -> *mut anitomy_t;
112 pub fn anitomy_parse(anitomy: *mut anitomy_t, filename: *const c_char) -> bool;
113 pub fn anitomy_elements(anitomy: *const anitomy_t) -> *const elements_t;
114 pub fn anitomy_options(anitomy: *mut anitomy_t) -> *mut options_t;
115 pub fn anitomy_destroy(anitomy: *mut anitomy_t);
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use std::ffi::CString;
122
123 const BLACK_BULLET_FILENAME: &'static str =
124 "[異域字幕組][漆黑的子彈][Black Bullet][11-12][1280x720][繁体].mp4";
125 const TORADORA_FILENAME: &'static str = "[TaigaSubs]_Toradora!_(2008)_-_01v2_-_Tiger_and_Dragon_[1280x720_H.264_FLAC][1234ABCD].mkv";
126
127 unsafe fn get_element(elems: *const elements_t, cat: element_category_t) -> String {
128 let cstr = elements_get(elems, cat);
129 let val = raw_into_string(cstr);
130 string_free(cstr);
131 val
132 }
133
134 #[test]
135 fn anitomy_new_destroy() {
136 unsafe {
137 let ani = anitomy_new();
138 assert!(!ani.is_null());
139 anitomy_destroy(ani);
140 }
141 }
142
143 #[test]
144 fn anitomy_parse_good_input() {
145 unsafe {
146 let filename = CString::new(BLACK_BULLET_FILENAME).unwrap();
147 let ani = anitomy_new();
148 assert!(!ani.is_null());
149
150 assert!(anitomy_parse(ani, filename.as_ptr()));
151
152 anitomy_destroy(ani);
153 }
154 }
155
156 #[test]
157 fn anitomy_parse_bad_input() {
158 unsafe {
159 let filename = CString::new("").unwrap();
160 let ani = anitomy_new();
161 assert!(!ani.is_null());
162
163 assert!(!anitomy_parse(ani, filename.as_ptr()));
164
165 anitomy_destroy(ani);
166 }
167 }
168
169 #[test]
170 fn anitomy_elements_not_null() {
171 unsafe {
172 let ani = anitomy_new();
173 assert!(!ani.is_null());
174
175 assert!(!anitomy_elements(ani).is_null());
176
177 anitomy_destroy(ani);
178 }
179 }
180
181 #[test]
182 fn anitomy_elements_empty_good_input() {
183 unsafe {
184 let filename = CString::new(BLACK_BULLET_FILENAME).unwrap();
185 let ani = anitomy_new();
186 assert!(!ani.is_null());
187
188 assert!(anitomy_parse(ani, filename.as_ptr()));
189 {
190 let elems = anitomy_elements(ani);
191 assert!(!elems.is_null());
192 assert!(!elements_empty(elems));
193 assert!(!elements_empty_category(elems, kElementAnimeTitle));
194 assert!(elements_count(elems) > 0);
195 assert!(elements_count_category(elems, kElementAnimeTitle) == 1);
196 }
197
198 anitomy_destroy(ani);
199 }
200 }
201
202 #[test]
203 fn anitomy_elements_empty_bad_input() {
204 unsafe {
205 let filename = CString::new("").unwrap();
206 let ani = anitomy_new();
207 assert!(!ani.is_null());
208
209 assert!(!anitomy_parse(ani, filename.as_ptr()));
210 {
211 let elems = anitomy_elements(ani);
212 assert!(!elems.is_null());
213 assert!(elements_empty(elems));
214 assert!(elements_empty_category(elems, kElementAnimeTitle));
215 assert!(elements_count(elems) == 0);
216 assert!(elements_count_category(elems, kElementAnimeTitle) == 0);
217 }
218
219 anitomy_destroy(ani);
220 }
221 }
222
223 #[test]
224 fn anitomy_elements_get_good_input() {
225 unsafe {
226 let filename = CString::new(BLACK_BULLET_FILENAME).unwrap();
227 let ani = anitomy_new();
228 assert!(!ani.is_null());
229
230 assert!(anitomy_parse(ani, filename.as_ptr()));
231 {
232 let elems = anitomy_elements(ani);
233 assert!(!elems.is_null());
234 assert!(elements_count_category(elems, kElementAnimeTitle) == 1);
235 assert_eq!(get_element(elems, kElementAnimeTitle), "Black Bullet");
236 }
237
238 anitomy_destroy(ani);
239 }
240 }
241
242 #[test]
243 fn anitomy_elements_get_bad_input() {
244 unsafe {
245 let filename = CString::new("").unwrap();
246 let ani = anitomy_new();
247 assert!(!ani.is_null());
248
249 assert!(!anitomy_parse(ani, filename.as_ptr()));
250 {
251 let elems = anitomy_elements(ani);
252 assert!(!elems.is_null());
253 assert!(elements_count_category(elems, kElementAnimeTitle) == 0);
254 assert_eq!(get_element(elems, kElementAnimeTitle), "");
255 }
256
257 anitomy_destroy(ani);
258 }
259 }
260
261 #[test]
262 fn anitomy_elements_get_all_good_input() {
263 unsafe {
264 let filename = CString::new(BLACK_BULLET_FILENAME).unwrap();
265 let ani = anitomy_new();
266 assert!(!ani.is_null());
267
268 assert!(anitomy_parse(ani, filename.as_ptr()));
269 {
270 let elems = anitomy_elements(ani);
271 assert!(!elems.is_null());
272 assert!(elements_count_category(elems, kElementEpisodeNumber) == 2);
273 assert_eq!(
274 {
275 let array = elements_get_all(elems, kElementEpisodeNumber);
276 assert!(!array.is_null());
277 let size = string_array_size(array);
278 assert!(size == 2);
279 let vals: Vec<_> = (0..size)
280 .map(|i| raw_into_string(string_array_at(array, i)))
281 .collect();
282 string_array_free(array);
283 vals
284 },
285 ["11", "12"]
286 );
287 }
288
289 anitomy_destroy(ani);
290 }
291 }
292
293 #[test]
294 fn anitomy_elements_get_all_bad_input() {
295 unsafe {
296 let filename = CString::new("").unwrap();
297 let ani = anitomy_new();
298 assert!(!ani.is_null());
299
300 assert!(!anitomy_parse(ani, filename.as_ptr()));
301 {
302 let elems = anitomy_elements(ani);
303 assert!(!elems.is_null());
304 assert!(elements_count_category(elems, kElementEpisodeNumber) == 0);
305 let epnums = elements_get_all(elems, kElementEpisodeNumber);
306 assert!(!epnums.is_null());
307 assert!(string_array_size(epnums) == 0);
308 string_array_free(epnums);
309 }
310
311 anitomy_destroy(ani);
312 }
313 }
314
315 #[test]
316 fn anitomy_elements_at() {
317 unsafe {
318 let filename = CString::new(BLACK_BULLET_FILENAME).unwrap();
319 let ani = anitomy_new();
320 assert!(!ani.is_null());
321
322 assert!(anitomy_parse(ani, filename.as_ptr()));
323 {
324 let elems = anitomy_elements(ani);
325 assert!(!elems.is_null());
326 assert!(elements_count(elems) > 0);
327 let pair = elements_at(elems, 0);
328 assert_eq!(pair.category, kElementFileExtension);
329 assert_eq!(
330 {
331 let value = raw_into_string(pair.value);
332 string_free(pair.value);
333 value
334 },
335 "mp4"
336 );
337 }
338
339 anitomy_destroy(ani);
340 }
341 }
342
343 #[test]
344 fn anitomy_options_not_null() {
345 unsafe {
346 let ani = anitomy_new();
347 assert!(!ani.is_null());
348
349 assert!(!anitomy_options(ani).is_null());
350
351 anitomy_destroy(ani);
352 }
353 }
354
355 #[test]
356 fn anitomy_options_allowed_delimiters() {
357 unsafe {
358 let filename = CString::new(TORADORA_FILENAME).unwrap();
359 let ani = anitomy_new();
360 assert!(!ani.is_null());
361
362 assert!(anitomy_parse(ani, filename.as_ptr()));
363 {
364 let elems = anitomy_elements(ani);
365 assert!(!elems.is_null());
366 assert!(elements_count_category(elems, kElementAnimeTitle) == 1);
367 assert_eq!(get_element(elems, kElementAnimeTitle), "Toradora!");
368 }
369
370 {
371 let opts = anitomy_options(ani);
372 assert!(!opts.is_null());
373 let allowed_delimiters = CString::new("").unwrap();
374 options_allowed_delimiters(opts, allowed_delimiters.as_ptr());
375 }
376
377 assert!(anitomy_parse(ani, filename.as_ptr()));
378 {
379 let elems = anitomy_elements(ani);
380 assert!(!elems.is_null());
381 assert!(elements_count_category(elems, kElementAnimeTitle) == 1);
382 assert_eq!(get_element(elems, kElementAnimeTitle), "_Toradora!_");
383 }
384
385 anitomy_destroy(ani);
386 }
387 }
388
389 #[test]
390 fn anitomy_options_ignored_strings() {
391 unsafe {
392 let filename = CString::new(TORADORA_FILENAME).unwrap();
393 let ani = anitomy_new();
394 assert!(!ani.is_null());
395
396 assert!(anitomy_parse(ani, filename.as_ptr()));
397 {
398 let elems = anitomy_elements(ani);
399 assert!(!elems.is_null());
400 assert!(elements_count_category(elems, kElementEpisodeTitle) == 1);
401 assert_eq!(get_element(elems, kElementEpisodeTitle), "Tiger and Dragon");
402 }
403
404 {
405 let opts = anitomy_options(ani);
406 assert!(!opts.is_null());
407 let ignored_strings = string_array_new();
408 assert!(!ignored_strings.is_null());
409 let string = CString::new("Dragon").unwrap();
410 string_array_add(ignored_strings, string.as_ptr());
411 options_ignored_strings(opts, ignored_strings);
412 string_array_free(ignored_strings);
413 }
414
415 assert!(anitomy_parse(ani, filename.as_ptr()));
416 {
417 let elems = anitomy_elements(ani);
418 assert!(!elems.is_null());
419 assert!(elements_count_category(elems, kElementEpisodeTitle) == 1);
420 assert_eq!(get_element(elems, kElementEpisodeTitle), "Tiger and");
421 }
422
423 anitomy_destroy(ani);
424 }
425 }
426
427 #[test]
428 fn anitomy_options_parse_episode_number() {
429 unsafe {
430 let filename = CString::new(TORADORA_FILENAME).unwrap();
431 let ani = anitomy_new();
432 assert!(!ani.is_null());
433
434 assert!(anitomy_parse(ani, filename.as_ptr()));
435 {
436 let elems = anitomy_elements(ani);
437 assert!(!elems.is_null());
438 assert!(elements_count_category(elems, kElementEpisodeNumber) == 1);
439 }
440
441 {
442 let opts = anitomy_options(ani);
443 assert!(!opts.is_null());
444 options_parse_episode_number(opts, false);
445 }
446
447 assert!(anitomy_parse(ani, filename.as_ptr()));
448 {
449 let elems = anitomy_elements(ani);
450 assert!(!elems.is_null());
451 assert!(elements_count_category(elems, kElementEpisodeNumber) == 0);
452 }
453
454 anitomy_destroy(ani);
455 }
456 }
457
458 #[test]
459 fn anitomy_options_parse_episode_title() {
460 unsafe {
461 let filename = CString::new(TORADORA_FILENAME).unwrap();
462 let ani = anitomy_new();
463 assert!(!ani.is_null());
464
465 assert!(anitomy_parse(ani, filename.as_ptr()));
466 {
467 let elems = anitomy_elements(ani);
468 assert!(!elems.is_null());
469 assert!(elements_count_category(elems, kElementEpisodeTitle) == 1);
470 }
471
472 {
473 let opts = anitomy_options(ani);
474 assert!(!opts.is_null());
475 options_parse_episode_title(opts, false);
476 }
477
478 assert!(anitomy_parse(ani, filename.as_ptr()));
479 {
480 let elems = anitomy_elements(ani);
481 assert!(!elems.is_null());
482 assert!(elements_count_category(elems, kElementEpisodeTitle) == 0);
483 }
484
485 anitomy_destroy(ani);
486 }
487 }
488
489 #[test]
490 fn anitomy_options_parse_file_extension() {
491 unsafe {
492 let filename = CString::new(TORADORA_FILENAME).unwrap();
493 let ani = anitomy_new();
494 assert!(!ani.is_null());
495
496 assert!(anitomy_parse(ani, filename.as_ptr()));
497 {
498 let elems = anitomy_elements(ani);
499 assert!(!elems.is_null());
500 assert!(elements_count_category(elems, kElementFileExtension) == 1);
501 }
502
503 {
504 let opts = anitomy_options(ani);
505 assert!(!opts.is_null());
506 options_parse_file_extension(opts, false);
507 }
508
509 assert!(anitomy_parse(ani, filename.as_ptr()));
510 {
511 let elems = anitomy_elements(ani);
512 assert!(!elems.is_null());
513 assert!(elements_count_category(elems, kElementFileExtension) == 0);
514 }
515
516 anitomy_destroy(ani);
517 }
518 }
519
520 #[test]
521 fn anitomy_options_parse_release_group() {
522 unsafe {
523 let filename = CString::new(TORADORA_FILENAME).unwrap();
524 let ani = anitomy_new();
525 assert!(!ani.is_null());
526
527 assert!(anitomy_parse(ani, filename.as_ptr()));
528 {
529 let elems = anitomy_elements(ani);
530 assert!(!elems.is_null());
531 assert!(elements_count_category(elems, kElementReleaseGroup) == 1);
532 }
533
534 {
535 let opts = anitomy_options(ani);
536 assert!(!opts.is_null());
537 options_parse_release_group(opts, false);
538 }
539
540 assert!(anitomy_parse(ani, filename.as_ptr()));
541 {
542 let elems = anitomy_elements(ani);
543 assert!(!elems.is_null());
544 assert!(elements_count_category(elems, kElementReleaseGroup) == 0);
545 }
546
547 anitomy_destroy(ani);
548 }
549 }
550}