Skip to main content

screenplay_doc_parser_rs/
screenplay_document.rs

1use crate::{pdf_document, };
2use core::panic;
3use std::{
4    collections::{HashMap, HashSet,},
5    
6    hash::Hash,
7    ops::{Deref, DerefMut, },
8    time::{Instant, },
9    vec,
10};
11use uuid::Uuid;
12
13#[derive(PartialEq, Clone, Debug)]
14pub enum TimeOfDay {
15    Day(String),
16    Night(String),
17    Morning(String),
18    Evening(String),
19    Afternoon(String),
20    Extras(Option<HashMap<String, String>>),
21}
22
23
24#[derive(PartialEq, Clone, Debug)]
25pub struct TimeOfDayCollection {
26    pub day: TimeOfDay,
27    pub night: TimeOfDay,
28    pub morning: TimeOfDay,
29    pub evening: TimeOfDay,
30    pub afternoon: TimeOfDay,
31    pub extras: Option<HashMap<String, String>>,
32}
33impl Default for TimeOfDayCollection {
34    fn default() -> Self {
35        return Self {
36            day: TimeOfDay::Day("DAY".into()),
37            night: TimeOfDay::Night("NIGHT".into()),
38            morning: TimeOfDay::Morning("MORNING".into()),
39            evening: TimeOfDay::Evening("EVENING".into()),
40            afternoon: TimeOfDay::Afternoon("AFTERNOON".into()),
41            extras: None,
42        };
43    }
44}
45impl TimeOfDayCollection {
46    pub fn is_time_of_day(&self, target: &String) -> bool {
47        let vars: Vec<&TimeOfDay> = vec![
48            &self.day,
49            &self.night,
50            &self.morning,
51            &self.evening,
52            &self.afternoon,
53        ];
54
55        for time in vars {
56            match time {
57                TimeOfDay::Day(string)
58                | TimeOfDay::Night(string)
59                | TimeOfDay::Morning(string)
60                | TimeOfDay::Evening(string)
61                | TimeOfDay::Afternoon(string) => {
62                    if string == target {
63                        return true;
64                    }
65                }
66                _ => {}
67            }
68        }
69
70        match &self.extras {
71            None => {
72                return false;
73            }
74            Some(e) => {
75                for (_, string) in e {
76                    if target == string {
77                        return true;
78                    }
79                }
80            }
81        }
82
83        return false;
84    }
85
86    pub fn get_time_of_day(&self, target: &String) -> Option<TimeOfDay> {
87        let vars: Vec<&TimeOfDay> = vec![
88            &self.day,
89            &self.night,
90            &self.morning,
91            &self.evening,
92            &self.afternoon,
93        ];
94
95        for time in vars {
96            match time {
97                TimeOfDay::Day(string)
98                | TimeOfDay::Night(string)
99                | TimeOfDay::Morning(string)
100                | TimeOfDay::Evening(string)
101                | TimeOfDay::Afternoon(string) => {
102                    if string == target {
103                        return Some(time.clone());
104                    }
105                }
106                _ => {}
107            }
108        }
109
110        match &self.extras {
111            None => {
112                return None;
113            }
114            Some(e) => {
115                for (_, string) in e {
116                    if target == string {
117                        return Some(TimeOfDay::Extras(None)); // this is FUCKING horrendous what the fuck man
118                    }
119                }
120            }
121        }
122
123        return None;
124    }
125}
126
127#[derive(PartialEq, Clone, Copy, Debug)]
128pub enum PageFormat {
129    US,
130    A4,
131    OTHER,
132}
133
134/// # SPType
135///
136/// The various Element Types found in a Screenplay.
137///
138/// Types can be assigned to both individual `TextElements` and `Lines`.
139///
140/// Some `Line`s will only contain a single type.
141///
142/// An `SPType::SP_ACTION` Line will contain only `SPType::SP_ACTION` text elements, for example.
143///
144/// But a `SP_CHARACTER` line will potentially contain one or more `SP_CHARACTER` elements, as well as one or more `SP_CHARACTER_EXTENSION` elements:
145///
146/// ```text
147/// ...
148///
149///         CHARLIE (V.O.)
150///     I always wanted to be a gangster.
151///
152/// ...
153///
154/// ```
155///
156/// Notice how `CHARLIE` is the `SP_CHARACTER` and the `(V.O.)` is the `SP_CHARACTER_EXTENSION`.
157///
158/// ## Scene Headings
159///
160/// A Scene Heading will consist of multiple types, such as the `SP_ENVIRONMENT`, meaning interior and/or exterior (INT./EXT.), the location, sublocation, and time of day:
161///
162/// `EXT. BASEBALL FIELD - PITCHER'S MOUND - DAY`
163///
164/// Scene headings can contain more element types, such as a Time Period, or multiple Sublocations.
165#[derive(Default, Clone, Debug, Copy, PartialEq, Eq, Hash)]
166#[repr(u8)]
167#[allow(non_camel_case_types)]
168pub enum SPType {
169    SP_ACTION = 0,
170
171    SP_CHARACTER,
172    SP_CHARACTER_EXTENSION, // require context to parse (previous word type)
173    SP_DG_MORE_CONTINUED,   // specifically has MORE or CONTINUED or CONT'D within parentheses
174    SP_PARENTHETICAL,
175    SP_DIALOGUE,
176    SP_TRANSITION,
177
178    /// SCENE HEADING
179    ///
180    SP_SCENE_HEADING(SceneHeadingElement), // begins with INT. , EXT. , or I./E.
181
182    /// `INT.`, `EXT.`, `INT./EXT.`, etc.
183    //SP_ENVIRONMENT,
184    //SP_LOCATION,
185    //SP_SCENE_HEADING_SUB_ELEMENT,
186    //SP_SCENE_HEADING_SEPARATOR, /// Breaks up a slugline -- EXT. BASEBALL FIELD - PITCHER'S MOUND - PAST - NIGHT
187    //SP_SCENE_TIMEPERIOD, // PAST, PRESENT, FUTURE, arbitrary timeframe "BEFORE DINNER", "AFTER THE EXPLOSION", etc.
188    //SP_SUBLOCATION,
189    //SP_TIME_OF_DAY,
190    SP_SHOT_ANGLE, // SHOT or ANGLE on something, NOT a full scene heading / location
191
192    SP_PAGENUM,  // Nominal page number
193    SP_SCENENUM, // Nominal scene number
194
195    SP_PAGE_HEADER, //LINE --contains the PAGE NUM and potentially a page Revision label
196    SP_PAGE_REVISION_LABEL, //may or may not include the date / color (I think it's two lines usually, but it could be one line potentially...?)
197    SP_LINE_REVISION_MARKER, // asterisks in the left and/or right margins indicate a line or lines have been revised
198
199    SP_MORE_CONTINUED,
200    SP_FOOTER, // Not sure what footers are used for but....
201
202    //DUAL DIALOGUE
203    SP_DUAL_CHARACTERS,
204    SP_DUAL_DIALOGUES,
205
206    SP_DD_L_CHARACTER,
207    SP_DD_L_CHARACTER_EXTENSION,
208    SP_DD_L_PARENTHETICAL,
209    SP_DD_L_DIALOGUE,
210    SP_DD_L_MORE_CONTINUED,
211
212    SP_DD_R_CHARACTER,
213    SP_DD_R_CHARACTER_EXTENSION,
214    SP_DD_R_PARENTHETICAL,
215    SP_DD_R_DIALOGUE,
216    SP_DD_R_MORE_CONTINUED,
217
218    // TITLE PAGE
219    TP_TITLE,
220    TP_BYLINE,
221    TP_AUTHOR,
222    TP_DRAFT_DATE,
223    TP_CONTACT,
224    // -------------
225    SP_OTHER,
226    SP_BLANK, // BLANK element?
227    SP_OMITTED,
228    // Non- content text (asterisks and/or scene numbers in the margins, headers and footers, page numbers, etc.)
229    NON_CONTENT_TOP,
230    NON_CONTENT_BOTTOM,
231    NON_CONTENT_LEFT,
232    NON_CONTENT_RIGHT,
233
234    #[default]
235    NONE,
236    _TYPECOUNT,
237}
238
239// -------- SCREENPLAY TYPED STRUCTS / ENUMS
240
241// -------------------- CHARACTER
242#[derive(Default, PartialEq, Clone, Debug, Eq, Hash)]
243pub struct CharacterID(Uuid);
244impl Deref for CharacterID {
245    type Target = Uuid;
246    fn deref(&self) -> &Self::Target {
247        &self.0
248    }
249}
250impl DerefMut for CharacterID {
251    fn deref_mut(&mut self) -> &mut Self::Target {
252        &mut self.0
253    }
254}
255impl CharacterID {
256    pub fn new() -> Self {
257        CharacterID(Uuid::new_v4())
258    }
259}
260
261#[derive(Default, PartialEq, Clone, Debug, Eq, Hash)]
262pub struct Character {
263    pub name: String,
264    pub id: CharacterID,
265}
266impl Character {
267    pub fn is_line(&self, line: &Line) -> bool {
268        let mut maybe_character_name = String::new();
269        let mut previous_type: Option<SPType> = Some(SPType::NONE);
270        for text_element in &line.text_elements {
271            if previous_type != text_element.element_type {
272                if maybe_character_name == self.name {
273                    println!("'howdy y'all");
274                    return true;
275                }
276                maybe_character_name = String::new();
277            }
278            match text_element.element_type {
279                Some(SPType::SP_CHARACTER)
280                | Some(SPType::SP_DD_L_CHARACTER)
281                | Some(SPType::SP_DD_R_CHARACTER) => {
282                    if !maybe_character_name.is_empty() {
283                        maybe_character_name.push(' ');
284                    }
285                    maybe_character_name.push_str(&text_element.text.clone());
286                }
287                _ => {}
288            }
289            previous_type = text_element.element_type.clone();
290        }
291        if maybe_character_name == self.name {
292            //println!("HOO WEE!");
293            return true;
294        }
295
296        false
297    }
298}
299
300// -------------------- PAGE
301#[derive(Default, PartialEq, Clone, Debug, Eq, Hash)]
302pub struct PageID(pub Uuid);
303impl Deref for PageID {
304    type Target = Uuid;
305    fn deref(&self) -> &Self::Target {
306        &self.0
307    }
308}
309
310impl DerefMut for PageID {
311    fn deref_mut(&mut self) -> &mut Self::Target {
312        &mut self.0
313    }
314}
315
316#[derive(Default, PartialEq, Clone, Debug)]
317pub struct PageNumber(pub String);
318impl Deref for PageNumber {
319    type Target = String;
320    fn deref(&self) -> &Self::Target {
321        &self.0
322    }
323}
324impl DerefMut for PageNumber {
325    fn deref_mut(&mut self) -> &mut Self::Target {
326        &mut self.0
327    }
328}
329
330// -------------------- SCENE
331#[derive(Default, PartialEq, Clone, Debug)]
332pub struct SceneNumber(pub String);
333
334#[derive(Default, PartialEq, Clone, Copy, Debug, Hash, Eq)]
335pub struct SceneID(pub Uuid);
336impl Deref for SceneID {
337    type Target = Uuid;
338    fn deref(&self) -> &Self::Target {
339        &self.0
340    }
341}
342impl DerefMut for SceneID {
343    fn deref_mut(&mut self) -> &mut Self::Target {
344        &mut self.0
345    }
346}
347impl SceneID {
348    pub fn new() -> Self {
349        SceneID(Uuid::new_v4())
350    }
351}
352
353//TODO:
354// make the SP_SCENE_HEADING element take one of THESE as data,
355// instead of having the scene elements flattened out among the SP_TYPEs
356// maybe also do this technique with CHARACTER, DIALOGUE, etc. ,
357// basically make each element have the LINE TYPE, which contains the ELEMENT TYPE as data...
358#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
359pub enum SceneHeadingElement {
360    Line, // The Line Itself
361    Environment,
362    Location,
363    SubLocation,
364    TimeOfDay,
365    Continuity, // CONTINUOUS
366    TimePeriod, // EALIER, LATER, 1950s, WEDNESDAY, etc.
367    Separator,  // hyphen
368    SceneNumber,
369    SlugOther,
370}
371
372//TODO: add get_scene_from_id func to ScreenplayDocument struct
373#[derive(PartialEq, Clone, Debug)]
374pub struct Scene {
375    pub start: ScreenplayCoordinate,
376
377    pub environment: Environment,
378    pub number: Option<SceneNumber>,
379    pub revised: bool,
380
381    pub story_locations: Vec<LocationID>,
382    pub story_time_of_day: Option<TimeOfDay>, // DAY, NIGHT, etc.
383}
384
385pub struct EnvironmentStrings {
386    pub int: Vec<String>,
387    pub ext: Vec<String>,
388    pub combo: Vec<String>,
389}
390impl Default for EnvironmentStrings {
391    fn default() -> Self {
392        EnvironmentStrings {
393            int: vec!["INT.".into()],
394            ext: vec!["EXT.".into()],
395            combo: vec![
396                "INT./EXT.".into(),
397                "I./E.".into(),
398                "EXT./INT.".into(),
399                "E./I.".into(),
400            ],
401        }
402    }
403}
404
405#[derive(Clone, Debug, PartialEq)]
406pub enum Environment {
407    Int,
408    Ext,
409    Combo(Option<Vec<Environment>>),
410}
411impl Environment {
412    pub fn from_str(string: &String, current_env_strs: &EnvironmentStrings) -> Option<Self> {
413        if current_env_strs.int.contains(&string) {
414            return Some(Environment::Int);
415        }
416        if current_env_strs.ext.contains(&string) {
417            return Some(Environment::Ext);
418        }
419        if current_env_strs.combo.contains(&string) {
420            // TODO: actually hanndle combos (4 total possibilities, int/int, int/ext, ext/int, and ext/ext)
421            return Some(Environment::Combo(None));
422        }
423        None
424    }
425}
426
427#[derive(Default, PartialEq, Clone, Debug, Eq, Hash)]
428pub struct LocationID(Uuid);
429
430impl Deref for LocationID {
431    type Target = Uuid;
432    fn deref(&self) -> &Self::Target {
433        &self.0
434    }
435}
436impl DerefMut for LocationID {
437    fn deref_mut(&mut self) -> &mut Self::Target {
438        &mut self.0
439    }
440}
441impl LocationID {
442    pub fn new() -> Self {
443        LocationID(Uuid::new_v4())
444    }
445}
446
447#[derive(Default, PartialEq, Clone, Debug)]
448pub struct LocationNode {
449    pub string: String,
450    pub sublocations: HashSet<LocationID>, // list of IDs for other locations
451    pub superlocation: Option<LocationID>, //
452}
453impl LocationNode {
454    pub fn add_sublocation(&mut self, new_id: LocationID) -> bool {
455        self.sublocations.insert(new_id)
456    }
457
458    ///
459    /// Determines if a path exists under this LocationNode.
460    ///
461    /// ```
462    /// let mut screenplay_doc = screenplay_document::ScreenplayDocument::new();
463    /// ```
464    ///
465    pub fn subpath_exists<'a>(
466        &'a self,
467        this_location_id: &'a LocationID,
468        subpath: &[String],
469        screenplay: &'a ScreenplayDocument,
470    ) -> Option<(&'a LocationID, Vec<String>)> {
471        if subpath.is_empty() {
472            return None;
473        }
474
475        let subpath_root = &subpath[0];
476
477        for id in &self.sublocations {
478            let Some(sublocation) = screenplay.locations.get(id) else {
479                continue;
480            };
481            if sublocation.string == *subpath_root {
482                if subpath.len() == 1 {
483                    return Some((id, Vec::new()));
484                }
485                if subpath.len() > 1 && sublocation.sublocations.is_empty() {
486                    return Some((id, Vec::from(&subpath[1..])));
487                }
488                return sublocation.subpath_exists(id, &subpath[1..], screenplay);
489            }
490        }
491
492        Some((this_location_id, subpath.to_vec()))
493    }
494}
495
496// --------------- BASIC DOCUMENT COMPONENTS ---------------
497
498#[derive(Default, PartialEq, Clone, Debug)]
499pub struct TextElement {
500    pub text: String,
501    pub element_type: Option<SPType>,
502    pub preceding_whitespace_chars: u64,
503    pub element_position: Option<pdf_document::TextPosition>,
504}
505
506#[derive(Default, PartialEq, Clone, Debug)]
507pub struct Line {
508    pub text_elements: Vec<TextElement>,
509    pub scene_number: Option<String>,
510    pub scene_id: Option<SceneID>,
511    pub line_type: Option<SPType>,
512    pub preceding_empty_lines: u64,
513    pub revised: bool,
514    pub blank: bool,
515}
516
517#[derive(Default, PartialEq, Clone, Debug)]
518pub struct Page {
519    pub lines: Vec<Line>,
520    pub page_number: Option<PageNumber>,
521    pub revised: bool,
522    pub revision_label: Option<String>,
523    pub revision_date: Option<String>,
524    pub page_format: Option<PageFormat>,
525}
526
527#[derive(Default, PartialEq, Clone, Debug, Hash, Eq, PartialOrd)]
528pub struct ScreenplayCoordinate {
529    pub page: usize,
530    pub line: usize,
531    pub element: Option<u64>,
532}
533
534#[derive(Default, PartialEq, Clone, Debug)]
535pub struct ScreenplayDocument {
536    pub pages: Vec<Page>,
537    pub revisions: Option<Vec<String>>, // current (and possible previous) revision date(s) from the title page
538    pub scenes: HashMap<SceneID, Scene>,
539    pub locations: HashMap<LocationID, LocationNode>,
540    pub characters: HashSet<Character>,
541    pub page_numbers: HashMap<PageID, PageNumber>,
542}
543impl ScreenplayDocument {
544    pub fn new() -> Self {
545        ScreenplayDocument {
546            pages: Vec::new(),
547            revisions: None,
548            scenes: HashMap::new(),
549            locations: HashMap::new(),
550            characters: HashSet::new(),
551            page_numbers: HashMap::new(),
552        }
553    }
554
555    
556}