imdb_index/
record.rs

1use std::cmp;
2use std::fmt;
3use std::str::FromStr;
4
5use csv;
6use serde::{Deserialize, Deserializer, Serialize};
7
8use crate::error::Error;
9
10/// An IMDb title record.
11///
12/// This is the primary type of an IMDb media entry. This record defines the
13/// identifier of an IMDb title, which serves as a foreign key in other data
14/// files (such as alternate names, episodes and ratings).
15#[derive(Clone, Debug, Deserialize)]
16pub struct Title {
17    /// An IMDb identifier.
18    ///
19    /// Generally, this is a fixed width string beginning with the characters
20    /// `tt`.
21    #[serde(rename = "tconst")]
22    pub id: String,
23    /// The specific type of a title, e.g., movie, TV show, episode, etc.
24    #[serde(rename = "titleType")]
25    pub kind: TitleKind,
26    /// The primary name of this title.
27    #[serde(rename = "primaryTitle")]
28    pub title: String,
29    /// The "original" name of this title.
30    #[serde(rename = "originalTitle")]
31    pub original_title: String,
32    /// Whether this title is classified as "adult" material or not.
33    #[serde(rename = "isAdult", deserialize_with = "number_as_bool")]
34    pub is_adult: bool,
35    /// The start year of this title.
36    ///
37    /// Generally, things like movies or TV episodes have a start year to
38    /// indicate their release year and no end year. TV shows also have a start
39    /// year. TV shows that are still airing lack an end time, but TV shows
40    /// that have stopped will typically have an end year indicating when it
41    /// stopped airing.
42    ///
43    /// Note that not all titles have a start year.
44    #[serde(rename = "startYear", deserialize_with = "csv::invalid_option")]
45    pub start_year: Option<u32>,
46    /// The end year of this title.
47    ///
48    /// This is typically used to indicate the ending year of a TV show that
49    /// has stopped production.
50    #[serde(rename = "endYear", deserialize_with = "csv::invalid_option")]
51    pub end_year: Option<u32>,
52    /// The runtime, in minutes, of this title.
53    #[serde(
54        rename = "runtimeMinutes",
55        deserialize_with = "csv::invalid_option"
56    )]
57    pub runtime_minutes: Option<u32>,
58    /// A comma separated string of genres.
59    #[serde(rename = "genres")]
60    pub genres: String,
61}
62
63/// The kind of a title. These form a partioning of all titles, where every
64/// title has exactly one kind.
65///
66/// This type has a `FromStr` implementation that permits parsing a string
67/// containing a title kind into this type. Note that parsing a title kind
68/// recognizes all forms present in the IMDb data, and also addition common
69/// sense forms. For example, `tvshow` and `tvSeries` are both accepted as
70/// terms for the `TVSeries` variant.
71#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
72#[allow(missing_docs)]
73pub enum TitleKind {
74    #[serde(rename = "movie")]
75    Movie,
76    #[serde(rename = "short")]
77    Short,
78    #[serde(rename = "tvEpisode")]
79    TVEpisode,
80    #[serde(rename = "tvMiniSeries")]
81    TVMiniSeries,
82    #[serde(rename = "tvMovie")]
83    TVMovie,
84    #[serde(rename = "tvSeries")]
85    TVSeries,
86    #[serde(rename = "tvShort")]
87    TVShort,
88    #[serde(rename = "tvSpecial")]
89    TVSpecial,
90    #[serde(rename = "video")]
91    Video,
92    #[serde(rename = "videoGame")]
93    VideoGame,
94}
95
96impl TitleKind {
97    /// Return a string representation of this title kind.
98    ///
99    /// This string representation is intended to be the same string
100    /// representation used in the IMDb data files.
101    pub fn as_str(&self) -> &'static str {
102        use self::TitleKind::*;
103        match *self {
104            Movie => "movie",
105            Short => "short",
106            TVEpisode => "tvEpisode",
107            TVMiniSeries => "tvMiniSeries",
108            TVMovie => "tvMovie",
109            TVSeries => "tvSeries",
110            TVShort => "tvShort",
111            TVSpecial => "tvSpecial",
112            Video => "video",
113            VideoGame => "videoGame",
114        }
115    }
116
117    /// Returns true if and only if this kind represents a TV series.
118    pub fn is_tv_series(&self) -> bool {
119        use self::TitleKind::*;
120
121        match *self {
122            TVMiniSeries | TVSeries => true,
123            _ => false,
124        }
125    }
126}
127
128impl fmt::Display for TitleKind {
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        write!(f, "{}", self.as_str())
131    }
132}
133
134impl Ord for TitleKind {
135    fn cmp(&self, other: &TitleKind) -> cmp::Ordering {
136        self.as_str().cmp(other.as_str())
137    }
138}
139
140impl PartialOrd for TitleKind {
141    fn partial_cmp(&self, other: &TitleKind) -> Option<cmp::Ordering> {
142        Some(self.cmp(other))
143    }
144}
145
146impl FromStr for TitleKind {
147    type Err = Error;
148
149    fn from_str(ty: &str) -> Result<TitleKind, Error> {
150        use self::TitleKind::*;
151
152        match &*ty.to_lowercase() {
153            "movie" => Ok(Movie),
154            "short" => Ok(Short),
155            "tvepisode" | "episode" => Ok(TVEpisode),
156            "tvminiseries" | "miniseries" => Ok(TVMiniSeries),
157            "tvmovie" => Ok(TVMovie),
158            "tvseries" | "tvshow" | "show" => Ok(TVSeries),
159            "tvshort" => Ok(TVShort),
160            "tvspecial" | "special" => Ok(TVSpecial),
161            "video" => Ok(Video),
162            "videogame" | "game" => Ok(VideoGame),
163            unk => Err(Error::unknown_title(unk)),
164        }
165    }
166}
167
168/// A single alternate name.
169///
170/// Every title has one or more names, and zero or more alternate names. To
171/// represent multiple names, AKA or "also known as" records are provided.
172/// There may be many AKA records for a single title.
173#[derive(Clone, Debug, Deserialize)]
174pub struct AKA {
175    /// The IMDb identifier that these AKA records describe.
176    #[serde(rename = "titleId")]
177    pub id: String,
178    /// The order in which an AKA record should be preferred.
179    #[serde(rename = "ordering")]
180    pub order: i32,
181    /// The alternate name.
182    #[serde(rename = "title")]
183    pub title: String,
184    /// A geographic region in which this alternate name applies.
185    #[serde(rename = "region")]
186    pub region: String,
187    /// The language of this alternate name.
188    #[serde(rename = "language")]
189    pub language: String,
190    /// A comma separated list of types for this name.
191    #[serde(rename = "types")]
192    pub types: String,
193    /// A comma separated list of attributes for this name.
194    #[serde(rename = "attributes")]
195    pub attributes: String,
196    /// A flag indicating whether this corresponds to the original title or
197    /// not.
198    #[serde(
199        rename = "isOriginalTitle",
200        deserialize_with = "optional_number_as_bool"
201    )]
202    pub is_original_title: Option<bool>,
203}
204
205/// A single episode record.
206///
207/// An episode record is an entry that joins two title records together, and
208/// provides episode specific information, such as the season and episode
209/// number. The two title records joined correspond to the title record for the
210/// TV show and the title record for the episode.
211#[derive(Clone, Debug, Deserialize)]
212pub struct Episode {
213    /// The IMDb title identifier for this episode.
214    #[serde(rename = "tconst")]
215    pub id: String,
216    /// The IMDb title identifier for the parent TV show of this episode.
217    #[serde(rename = "parentTconst")]
218    pub tvshow_id: String,
219    /// The season in which this episode is contained, if it exists.
220    #[serde(
221        rename = "seasonNumber",
222        deserialize_with = "csv::invalid_option"
223    )]
224    pub season: Option<u32>,
225    /// The episode number of the season in which this episode is contained, if
226    /// it exists.
227    #[serde(
228        rename = "episodeNumber",
229        deserialize_with = "csv::invalid_option"
230    )]
231    pub episode: Option<u32>,
232}
233
234/// A rating associated with a single title record.
235#[derive(Clone, Debug, Deserialize)]
236pub struct Rating {
237    /// The IMDb title identifier for this rating.
238    #[serde(rename = "tconst")]
239    pub id: String,
240    /// The rating, on a scale of 0 to 10, for this title.
241    #[serde(rename = "averageRating")]
242    pub rating: f32,
243    /// The number of votes involved in this rating.
244    #[serde(rename = "numVotes")]
245    pub votes: u32,
246}
247
248fn number_as_bool<'de, D>(de: D) -> Result<bool, D::Error>
249where
250    D: Deserializer<'de>,
251{
252    i32::deserialize(de).map(|n| n != 0)
253}
254
255fn optional_number_as_bool<'de, D>(de: D) -> Result<Option<bool>, D::Error>
256where
257    D: Deserializer<'de>,
258{
259    Ok(i32::deserialize(de).map(|n| Some(n != 0)).unwrap_or(None))
260}