anitomy_sys/
ffi.rs

1//! The FFI bindings to [anitomy-c](https://github.com/Xtansia/anitomy-c).
2
3#![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}