ikiwiki/ plugins/ contrib/ screenplay.pm

This plugin works for me. It follows the standard for a movie screenplay pretty closely, I am not aware of any errors in format. Please let me know if you find any.

Right now all it does is display your pages properly in a web browser. What I would like to add is the ability to output a file that could easily be printed once the screenplay is finished. We keep all the scenes we work on in one folder and eventually we will want to print a script out of that folder. It would be great if an up to date PDF or TXT script could be put in the folder when a scene is saved. I will do it, it just isn't a priority yet.

I am not a published writer and not an authority on script formatting. I got what I know out of a book.

Briefly, you type a command on a line, like ".d", then on the next line (for the dialog command) you type a person's name. Then you hit return again and write the words he is supposed to speak out all on one line. When you save your document this simple text will become a properly formatted script.

Thank you Joey for having me here.

### Headings:

    Most headings should begin with a transition. The list of valid commands is:
    .fi    =>    FADE IN: a gradual transition from a solid color to an image
    .fo    =>    FADE OUT.
    .ftb    =>    FADE TO BLACK.
    .ftw    =>    FADE TO WHITE.
    .ct     =>    CUT TO: indicates an instantaneous shift from one shot to the next
    .shot    =>    lack of an explicit transition assumes a cut
    .hct    =>    HARD CUT TO: describes a jarring transition
    .qct    =>    QUICK CUT TO: describes a cut sooner than expected
    .tct    =>    TIME CUT TO: emphasizes time passing
    .mct    =>    MATCH CUT TO: image in first shot visually or thematically matches image in second
    .dt    =>    DISSOLVE TO: gradual transition from image to another implies passage of time.
    .rdt    =>    RIPPLE DISSOLVE TO: indicates transition into daydream or imagination
    .wt    =>    WIPE TO: new image slides over top of last one

    Example transition:

    .fi (or any transition command) <= Writes a transition line, except .shot which omits it.
    type shot heading here   <= this line will be capitalized
    First direction.   <= these lines are not capitalized.
    Second direction.
    Third direction, etc...

    Direction without a shot heading:
    .dir
    First direction.
    Second direction.
    Third direction, etc...

    Some items aren't implemented in dialogue yet:
    1) you must watch that you don't leave a " -- " dangling on a line by itself,
         instead, carry the last word onto the line with a dash
    2) observe lyrical line endings in dialogue by indenting wrapped lines by two spaces
    3) you must watch that the four line limit for parenthetical direction is not exceeded

    Example dialogue:

    .d
    char name   <= this line will be capitalized
    this is what he's saying                  <= Dialogue
    raises hand to wave                       <= Parenthetical direction
    this is more of what he's saying          <= Dialogue
    this is going to be in parenthesis        <= Parenthetical direction
    this is more of what he's saying, etc...  <= Dialogue

    .note
    Allows you to add a temporary note to a script without getting an error.
    All notes need to be removed eventually because they are a format violation.

    ###name this file screenplay.pm and pop it in your Plugin folder. Then you need to add the plugin to your Ikiwiki setup file.

    #!/usr/bin/perl
    # Screenplay markup language
    package IkiWiki::Plugin::screenplay;

    use warnings;
    use strict;
    use IkiWiki 3.00;
    use Text::Format;
    use Log::Log4perl qw(:easy);
    Log::Log4perl->easy_init($INFO);
    #Log::Log4perl->easy_init($ERROR);

    sub import {
        hook(type => "getsetup", id => "screenplay", call => \&getsetup);
        hook(type => "htmlize", id => "screenplay", call => \&htmlize, longname => "Screenplay");
    }

    sub getsetup () {
            return
                    plugin => {
                            safe => 1,
                            rebuild => 1, # format plugin
                            section => "format",
                    },
    }

    sub htmlize (@) {
        #set up variables and fill with defaults
        my %params=@_;
        my $content = $params{content};
        my @lines = split(/\r\n|\r|\n/, $content);
        my @chunk;
        my @formatted;
        my $current_line = shift(@lines);
        my $current_command = "";
        my $current_chunk = "";

        while (scalar(@lines) > 0) {
        until ( &dot_command($current_line) || scalar(@lines) == 0 ) {
            #skip spaces; mark bad lines
                unless ( &blank_line($current_line) ) {
                    push(@formatted, "<br />");
            push(@formatted, &no_command($current_line));
            }
            $current_line = shift(@lines);
        }

        #Exit while loop if we're out of lines
            last if (scalar(@lines) == 0);

        #set command for chunk
        $current_command = $current_line;
        $current_line = shift(@lines);

        #get chunk, i.e. all text up to next blank line or a dot command.
        until (substr($current_line,0,1) eq '.' || $current_line =~ m// || $current_line =~ m/^\s*$/) {
            push(@chunk,$current_line);
            $current_line = shift(@lines);
            last unless defined $current_line;
            }

        #Start with a blank line unless unneeded.
        if (scalar(@formatted) > 0 ) {
            push(@formatted, "<br />");
        }

        #remaining lines are not commands.
        if (scalar(@chunk)) {
            $current_chunk = shift(@chunk);
            if ($current_command eq ".shot") {
                push(@formatted, &indent(&chunk(uc($current_chunk),57),17));
                            while (scalar(@chunk)) {
                                    $current_chunk = shift(@chunk);
                                    push(@formatted, "<br />");
                                    push(@formatted, &indent(&chunk($current_chunk,57),17));
                            }

                    } elsif ($current_command eq ".note") {
                push(@formatted, "NOTE:<br />");
                            push(@formatted, &chunk($current_chunk,75));
                            while (scalar(@chunk)) {
                                    $current_chunk = shift(@chunk);
                                    push(@formatted, "<br />");
                                    push(@formatted, &chunk($current_chunk,75));
                            }

                    } elsif ($current_command eq ".dir") {
                            push(@formatted, &indent(&chunk($current_chunk,57),17));
                while (scalar(@chunk)) {
                                    $current_chunk = shift(@chunk);
                    push(@formatted, "<br />");
                                    push(@formatted, &indent(&chunk($current_chunk,57),17));
                            }

                    } elsif ($current_command eq ".d") {
                push(@formatted, &indent(&chunk(uc($current_chunk),32),41));
                $current_chunk = shift(@chunk);
                            push(@formatted, &indent(&chunk($current_chunk,34),27));
                while (scalar(@chunk) / 2 >= 1 ) {
                    $current_chunk = shift(@chunk);
                    push(@formatted, &indent(&chunk(&pd($current_chunk),19),34));
                    $current_chunk = shift(@chunk);
                    push(@formatted, &indent(&chunk($current_chunk,34),27));
                }

                    } elsif ($current_command eq ".pd") {
                            push(@formatted, &indent(&chunk(uc($current_chunk),32),41));
                            $current_chunk = shift(@chunk);
                            push(@formatted, &indent(&chunk(&pd($current_chunk),19),34));
                            $current_chunk = shift(@chunk);
                            push(@formatted, &indent(&chunk($current_chunk,34),27));
                            while (scalar(@chunk) / 2 >= 1 ) {
                                    $current_chunk = shift(@chunk);
                                    push(@formatted, &indent(&chunk(&pd($current_chunk),19),34));
                                    $current_chunk = shift(@chunk);
                                    push(@formatted, &indent(&chunk($current_chunk,34),27));
                            }

                    } elsif ($current_command =~ m/^\.(fi|fo|ct|hct|qct|tct|mct|dt|rdt)$/) {
                if ($current_command eq ".fi") {
                    push(@formatted, &indent(&chunk(uc("FADE IN:"),20),17));
                } elsif ($current_command eq ".fo") {
                                    push(@formatted, &indent(&chunk(uc("FADE OUT:"),20),60));
                } elsif ($current_command eq ".ct") {
                                    push(@formatted, &indent(&chunk(uc("CUT TO:"),20),60));
                            } elsif ($current_command eq ".hct") {
                                    push(@formatted, &indent(&chunk(uc("HARD CUT TO:"),20),60));
                            } elsif ($current_command eq ".qct") {
                                    push(@formatted, &indent(&chunk(uc("QUICK CUT TO:"),20),60));
                            } elsif ($current_command eq ".tct") {
                                    push(@formatted, &indent(&chunk(uc("TIME CUT TO:"),20),60));
                            } elsif ($current_command eq ".mct") {
                                    push(@formatted, &indent(&chunk(uc("MATCH CUT TO:"),20),60));
                            } elsif ($current_command eq ".dt") {
                                    push(@formatted, &indent(&chunk(uc("DISSOLVE TO:"),20),60));
                            } elsif ($current_command eq ".rdt") {
                                    push(@formatted, &indent(&chunk(uc("RIPPLE DISSOLVE TO:"),20),60));
                            } elsif ($current_command eq ".wt") {
                                    push(@formatted, &indent(&chunk(uc("WIPE TO:"),20),60));
                }
                push(@formatted, &indent(&chunk(uc($current_chunk),57),17));
                            while (scalar(@chunk)) {
                                    $current_chunk = shift(@chunk);
                                    push(@formatted, "<br />");
                                    push(@formatted, &indent(&chunk($current_chunk,57),17));
                            }

            }
            #mark the rest of the chunk as 'no command'
                if (scalar(@chunk)) {
                        $current_chunk = shift(@chunk);
                        push(@formatted, &no_command($current_chunk));
                }

        }
        }
        my @content;
        my $i = 0;
        $current_line = "";
        while (scalar(@formatted)) {
        $i++;
        $current_line = shift(@formatted);
        if ( $i % 60 == 0 ) {
            push(@content, &indent($i/60  . ".<br />",72) );
        }
        push(@content, $current_line);
        }
        $content = join("\r\n",@content);
        return $content;
    }

    sub blank_line {
        my $line = shift(@_);
        my $ret = 0;

            if ($line =~ m// || $line =~ m/^\s*$/) {
                $ret = 1;
            } else {
            $ret = 0;
        }

        return $ret;
    }

    sub chunk () {
            my $unchunked = shift(@_);
            my $columns = shift(@_);
        my $text = new Text::Format;
        $text->rightFill(1);
        $text->columns($columns);
        $text->firstIndent(0);
        $text->tabstop(0);
        $text->extraSpace(1);
            my @chunked = split /\n/, $text->format($unchunked);
            my @formatted;
            foreach (@chunked) {
                    push(@formatted, $_ . "<br />");
            }
            return @formatted;
    }

    sub dot_command {
            my $line = shift(@_);
            my $ret = 0;

            if ($line =~ m/^\.(ct|dir|dt|d|fi|fo|hct|mct|note|pd|qct|rdt|shot|tct)$/) {
                $ret = 1;
            } else {
                $ret = 0;
            }

            return $ret;
    }

    sub indent () {
        my @unindented = @_;
        my $spaces = pop @unindented;
        my @indented;
        foreach (@unindented) {
            push(@indented, "&nbsp;" x $spaces . $_);
        }
        return @indented;
    }

    sub no_command () {
        my $line = shift(@_);
              my $text = new Text::Format;
            $text->rightFill(1);
            $text->columns(68);
            $text->firstIndent(0);
            $text->tabstop(0);
            $text->extraSpace(1);
            my @chunked = split /\n/, $text->format($line);
            my @formatted;
        push(@formatted, ("NO COMMAND: "));
            foreach (@chunked) {
                    push(@formatted, ( $_ . "<br />" ));
            }
            return @formatted;
    }

    sub pd () {
            my @chunk = @_;
            # add '(' to top item
            my $line = "(" . shift(@chunk);
            unshift(@chunk, $line);

            # add ')' to bottom item
            $line = pop(@chunk) . ")";
            push(@chunk, $line);

            return @chunk;
    }

    1