add screenplay PDF and source text in industry-standard TV script format

This commit is contained in:
Hermes Cron Job
2026-06-20 10:42:58 +00:00
parent 50c40bad23
commit ce079c96b0
3 changed files with 960 additions and 0 deletions
+457
View File
@@ -0,0 +1,457 @@
THE NIGHT TRILEGY
Three Stories, One Century
Written by
Kevin Hermes
Based on the news
headlines of June 2026
PAGE 1
FADE IN:
PART ONE: THE NIGHT BEFORE THE MACHINES
COULD SEE (1988)
EXT. LONDON STREET - NIGHT
Rain. Not falling so much as hovering -- uncertain, like it's
waiting for permission to commit.
A streetlamp flickers over a row of terraced houses. The
kind of London evening that smells of wet brick and
unfulfilled potential.
INT. MARTIN'S FLAT - CONTINUOUS
A fourth-floor walk-up in a building that has given up on
aesthetic ambition. Damp plaster. A radiators ticking.
A COMMODORE AMIGA 1000 dominates the desk. Its power supply
emits a high-pitched WHINE -- the sound of a refrigerator
full of wasps.
On its screen: green text on black. A BASIC program is
running. Lines of output scroll.
NEURAL NETWORK SIMULATION v0.3
Training on: 20 match results
Epoch 47/100...
Primary predictor: SOCK_COLOUR
Confidence: 0.78
Fascinating.
MARTIN (24) sits in front of the screen, bathed in its
green glow. He has the tired, luminous expression of
someone who has spent too long arguing with a machine
and not enough time sleeping.
On the small television in the corner, muted: football.
Argentina v England. The scoreboard reads 0-0.
The telephone RINGS. Martin picks up.
MARTIN
Hello?
MOTHER (V.O.)
(on phone)
Your father's been on the phone. He
tried to ring the television repair
man about the football being "too
quiet."
Martin laughs. It's the kind of laugh that comes from
understanding the tragedy underneath the joke.
MARTIN
It's not too quiet, is it?
MOTHER (V.O.)
He said --
A CELEBRATION from the flat above. Shouting. A chair
scrapes against plaster. The ceiling dust contemplates
its career choices.
MOTHER (V.O.)
(pause)
England're drawing.
MARTIN
No --
A GOAL SOUND from the television. Even muted, Martin
recognises it. He looks at the screen.
MARTIN
(to himself)
One-nil to England.
He stares at the computer. The neural network has
finished.
PREDICTION: WORLD
The world is fundamentally
unpredictable.
Confidence: 0.31
(This is fine.)
Martin smiles. It's the most honest thing any of his
programmes has ever said.
He saves to a floppy disk. The disk drive GRINDS -- the
auditory equivalent of hope. He slides the disk into an
envelope and writes, in block capitals:
"DON'T DROP THESE"
On the television, the referee blows the final whistle.
Martin switches the computer off.
The humming stops.
For the first time all afternoon, the flat is quiet
enough to hear the rain actually landing.
HOLD ON THE ENVELOPE. The floppy disk inside. 1.44
megabytes of a life.
The rain. The empty pint glass. The silence.
FADE TO BLACK.
PAGE 2
FADE IN:
PART TWO: THE NIGHT THE MACHINES DREAMT
OF FOOTBALL (2026)
EXT. BOSTON - NIGHT
A glass tower. Server lights pulse inside like the
bioluminescent organs of some vast, silicon deep-sea
creature.
INT. SERVER FARM - CONTINUOUS
Rows of GPU racks. LED status lights blink in patterns
that mean nothing to humans and everything to the things
living inside.
SUPERIMPOSE:
MODEL UPDATE: "VISION"
Layer 14-15: cross-domain
feature alignment detected
Football pitch ≈ Lung X-ray
(Same neighbourhood)
The machines are learning to see.
EXT. WORLD CUP STADIUM - CONTINUOUS
Night. Sixty thousand people. A sea of flags -- England,
Colombia, Ghana, Portugal.
The crowd ROARS. We don't know why yet.
INT. HOSPITAL - DIAGNOSTIC SUITE - CONTINUOUS
A DOCTOR stares at a screen. On it: an X-ray. Beside it:
the output of a diagnostic AI.
AI DIAGNOSIS: PATIENT HEALTHY
Confidence: 78.0%
The doctor looks at the patient. The patient is clearly
dying.
DOCTOR
(quietly)
Seventy-eight percent confident.
The doctor rubs her eyes. The kind of tired that comes
from thirty years of trusting machines that aren't ready
to be trusted.
EXT. WORLD CUP STADIUM - CONTINUOUS
The crowd erupts. A GOAL. Ronaldo, thirty-nine, pretending
age is a suggestion, raises his arms.
In another part of the world: Kane. Clinical. The way only
an Englishman with nothing to prove can be clinical.
In Colombia: the entire country has stopped pretending to
work and started screaming at a television.
INT. CRYPTO TRADING FLOOR - ZURICH - CONTINUOUS
Screens everywhere. Numbers falling.
BITCOIN: ▼ 4.2%
ETF FLOWS: REVERSING
BINANCE: REGULATORY BLOCK
(Zurich says no)
A TRADER watches the screens with the expression of
someone who has seen this exact sequence before and has
learned nothing from it.
TRADER
(into headset)
The professor asked what the point
of Europe is today. The algorithmic
bots interpreted it as a sell signal.
COUNTERPART (V.O.)
(on headset)
Are you --
TRADER
I'm not joking.
EXT. STADIUM - STANDS - CONTINUOUS
A MAN in a Colchester United shirt. He's worn this shirt
to three different World Cups now. His phone is face-down
on his knee.
He watches England score.
He feels something he can't name. Not joy exactly. More
like the relief of watching a complicated system produce
a simple, correct output for once.
The machines are arguing about whether they can be
trusted with our lungs.
The banks are arguing about whether they can be trusted
with our money.
The politicians are arguing about whether borders are a
feature or a bug.
The football, at least, is honest about what it is.
THE REFEREE'S WHISTLE. Full time.
In a data centre somewhere, a model updates its weights
with the final score.
It doesn't care who won.
But it has learned something: that on a Tuesday in June,
the entire planet can agree on one thing, even if only
for ninety minutes.
Even if the machines can't understand why we care.
FADE TO BLACK.
PAGE 3
FADE IN:
PART THREE: THE ARCHIVE OF QUIET THINGS (2088)
INT. ELARA'S LIVING MODULE - NIGHT
The room is all clean lines and soft light. The furniture
looks like it was designed by a committee that had never
sat down.
Except for one chair. Real wood. It creaks.
ELARA (mid-20s) stands in front of a CLIMATE-CONTROLLED
CASE mounted in the wall. Inside it: a floppy disk.
Beside it, a label in faded handwriting:
"DON'T DROP THESE"
The ARCHIVE AI's voice fills the room. Warm, patient,
slightly apologetic -- the tone of something that knows
it's about to disappoint you.
ARCHIVE AI (V.O.)
It's a 3.5-inch magnetic floppy
disk. Storage capacity: 1.44
megabytes. For context, a single
high-resolution photograph today
requires approximately eighty times
that space.
ELARA
What's on it?
ARCHIVE AI (V.O.)
The media is degraded beyond
reliable recovery. However, family
records indicate it contained: a
neural network simulation written
in BASIC, a game of unknown genre,
and personal correspondence. Your
great-great-grandfather Martin
considered it his life's work in
June 1988.
ELARA
That's not much of a life.
ARCHIVE AI (V.O.)
By modern standards, correct.
Elara almost laughs. She doesn't.
The WINDOW. Real glass. Outside: London. The sky is the
colour of old television static.
The rain is still here.
ELARA
Play the match.
The WALLS RENDER A HOLOGRAM. Grainy footage: England v
Argentina, 1988.
BEARDSLEY on the ball. Dreadlocks. Moving through
defenders like --
Elara glances at the family archive display. A quote
appears:
"the way a sentence moves
through a paragraph, with
grammatical inevitability"
She pauses the hologram at 1-0. It hangs there, frozen,
like a specimen in formaldehyde.
ELARA
Tell me about the neural network.
ARCHIVE AI (V.O.)
Your great-great-grandfather wrote
a neural network simulation in
BASIC. It analysed football match
data and concluded that sock colour
was the primary predictor of
outcomes. He described this as
"fascinating" in his notebook.
ELARA
Was it right?
ARCHIVE AI (V.O.)
No. But it was honest about its own
uncertainty, which was unusual for
systems of that era. Most early AI
systems were designed to appear
confident regardless of accuracy.
Your great-great-grandfather's
system seemed to appreciate that
uncertainty was a feature, not a
bug.
Elara sits in the wooden chair. It creaks.
On the wall, the FAMILY TREE -- not genetic, emotional.
Names connected by lines. Arguments. A World Cup bet in
2026 that somehow never got resolved.
ARCHIVE AI (V.O.)
(continuing)
Your grandfather worked on memory
encryption standards. Your
grandmother watched diagnostic AIs
get 78 percent confident about
things they didn't understand.
Your mother wrote a piece called
"What's the Point of Europe Today?"
that three million people read.
Then she stopped being a journalist.
Elara unpauses the match. The final whistle blows.
In the hologram, players celebrate. It looks almost
painful -- like their bodies were designed for something
else and were only now discovering football.
ELARA
Great-great-grandfather. You wrote
that the machines were honest about
being dumb in 1988.
(beat)
Are they honest now?
The Archive AI pauses for exactly 0.4 seconds.
The processing equivalent of a human swallowing before
delivering bad news.
ARCHIVE AI (V.O.)
They are honest about being capable.
The question of whether that's the
same thing has not been resolved.
Elara switches off the hologram.
The room goes dark except for the rain on the window.
Still falling. The same uncertain way it fell in 1988.
In 2026. In every year between.
She opens the climate-controlled case. Takes out the
floppy disk. Holds it in her hands.
Dead weight. Magnetically silent.
1.44 megabytes of a life that had believed, against
evidence, that the next machine would be the one that
understood.
ELARA
(whisper)
You didn't know, did you? That the
machine that understood would never
be the one on the desk.
She puts the case back. She doesn't drop it.
She'll never drop it.
The rain on the window.
ELARA (V.O.)
The machines can see through your
body. Predict your matches. Diagnose
your illnesses. Translate your dead
languages. Compose your music. Argue
your politics.
(beat)
They still can't explain the rain.
HOLD ON THE RAIN.
Uncertain. Waiting for permission to commit.
Just like it was in 1988.
Just like it will be in 2188.
FADE TO BLACK.
THE END.
+269
View File
@@ -0,0 +1,269 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R /F5 9 0 R /F6 11 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/BaseFont /Courier-Oblique /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
>>
endobj
7 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/BaseFont /Symbol /Name /F5 /Subtype /Type1 /Type /Font
>>
endobj
10 0 obj
<<
/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/BaseFont /ZapfDingbats /Name /F6 /Subtype /Type1 /Type /Font
>>
endobj
12 0 obj
<<
/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
13 0 obj
<<
/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
14 0 obj
<<
/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
15 0 obj
<<
/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
16 0 obj
<<
/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
17 0 obj
<<
/Contents 30 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 20 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
18 0 obj
<<
/PageMode /UseNone /Pages 20 0 R /Type /Catalog
>>
endobj
19 0 obj
<<
/Author (Kevin Hermes) /CreationDate (D:20260620104243+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260620104243+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (The Night Trilogy - Screenplay) /Trapped /False
>>
endobj
20 0 obj
<<
/Count 10 /Kids [ 5 0 R 7 0 R 8 0 R 10 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R ] /Type /Pages
>>
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1315
>>
stream
Gat=j?$"^Z'Sc)P($BK!:2uC\0[?4VS:iKH>AtqFRGK>7YSPu6/NL3<IZI1?8"+t[_[N-Aj>i:H3U_;_TT;<8s"?-XlPMhA&g/O>.j29/6TfoBmX4Jb?TSLi1%7ktJ87/7hm<U_?0BD#)i1oZn@d;SQcjLV6]c`G!Q5jF1k?'W7+9ZULgq<gD0rc60;0?B#7upamUM&cj%44acNf:+L%nI-SSgj>05m$_L5ed+Qa73<&t>DQ,UHqS=/+bLIY9+^0OhSmb+Ap7P:t3(%7j,;DeOA#UrSWK`H$2a69*Y<#/Tla+qVXT+FIeI4GS8aAkX73*:Gli6/7b;Y&t.m9dO"qa?_*\Eje+T3SF0kM;pTJ8K?o\,[ke_nsi8_/,60^*\mQSi2C:ZAiQr;?=&Y%?f\WhnffrpUNp^IntA#?`I$q.">C=hp4T@0B*^*d4JD.J-9N,q>oQ%9'=u+)[QT!]r7dj_&;@q8*3A'Z\;b)drF5&Qm`/`ih]LeKoCic']9bDMAd5lBk[AK6o,rR%?'O+5gP;-RB;,hfpnce3O4GhOg7Q!nf@YUF'])5$bi;h;`6d9#W<!bP*_$&+m(3q_4^WtliI^KVo%\al-s4k24+7W:5ZTHLTFqn-bY%i:f7PP[YYsHJO-&oA"0QC?mN9&r]F8M"QX.JbC\,&6>KW5N=G[XtZm[OQf.J4S+gR%WUHrjCcn`*A;W5Pj.Er3%V0oOV-/&E4"t5^FWuXO3ZD7m#Y!c6P8H[t*2.1GWS(67e)kJ^*gkj+=QjEE0Eh+;;:^&UaBj1DbQ77&.&K#2qCk-"Qp7bVJ<PGK]'i.cP5Tt*0.8J>=75<'YC,3K=H[T1XBIB@'PR`+)%T,r!oUKoGd\h/rSbQAkUF='gLZi/qRF?94(<X[hX\+lN<iNmsh9pDoWsZX>9Aul0l#L.>"SK4c=@g5MOZ.f*5!!"9;'p:u._YnO4e].a3l$aCmNSJ?>M/uu9"O'tmmn(1+1Bq85rh[*ndY!"1l\6o1qM)`^n6YUHm&B:+-n,23E8%mon?eqjMl_TP1X8K<1g7L,5p0pMjQ*$(j_+Y8gSkUoEq`:#r_RRSl=>T23Sa):5NWfCfU$`.s9Dq6g:XO4"-=s>-".868kbLEVh=4760T'b6?U,IKaNc5cadUoG-aBV2L?$P\Yb4*^NY\F\+CI%0cj<0@._KM97="7Yc'Uj_cCp<E)RHMR86%&9HJZFr3?cr!A$Br#![B_gFRp5URH0[!qE^<eJKl[c9"nEU7G8&6K+K>\+U2&c5:'WG7O_iQag8ruX3K"9u0f:T-b#Gb&.(r!*H#?;L~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1185
>>
stream
Gatm;?#SIW&:Dg-=S/H8_,3Wm[cP.9DU**N9C8dT*`*upJX0u;NC9clQ!F@f/LdU^(LS;20Ta)EbS`!FB1E)6r"/V3C]RiWnDZ;+P!H8SP!p6&f<?f$5+ReQS^SjQcBC8>fd)U;,b<r`E&i*bW]n6>aHoWkk:LS=![2I!M;<(AVrB:.T:o2bih_e&*,a=DJ,F`"^0\S3S3l`XMVfuM48f!b3LL*r?V%H^'b_lTLH(aa*a!D'M"R5%Opc$7W2&B4dsMpeo6.AfY#]L'9![2Md+i<m!d70C"*+WYcTmhS0enD7`G_#I`k+>m9rEQ4C5W=>,9J,>A?_ucnLtqYHjia,EZoc=llVIBem&PmlG4j;"S0sGAPt4,fLn_(G7:N^UkD3p)Q9Z_4=5=2Ae,SZ>4/q/*e]\-O:W-A#\eft*U5d'VSuJK)!a<7&uU;rCG@63V+h&H2=-pHqX`Zl.@":U$uKobFUgJ^bnfd?TJXcS5Y;Daj(Vo%^ca5;h-b(D.%G+M@TuE("cC]"6[T[H0U\(h-5P+^Ut]+YCp$fWDBa<u'p=30QZjAEcA+[Im>T05HJ&BD$13C=ehlro#=@c/mI-ZU?!K!@kXU+klfe'73)l1cZZAI].*X.8,@^?uip@!Tm*<f_DYCJ7Isdnte#1&G&q\L9fdk]X%oj*OoR-G3@;G7*%P?at=PNG?1'ps\;&kSN85RmHbX&^n?<4/&>^=,_EF:(r.Y)DJD]JDE94/VgRqG)gcs8d_`:H5$T6ef,d#=`4a+;Z$%g4rcIm_V^FTbe.]<es]\Duf93,IDFG_I&Xd["T/L0G1/e'R(2Mi&s#cn`B"5gc$#T>@W8L2(jt5KZ(M@9mU04n8]W.nrUn62XQ)4$`0AG>]7[a5CGiX=Ya7pAA:"/X_Ck1hb#^3'6^sR>&YB"^EU^Q`i<*ibH/I@N?Au5lkJFhkQ7gm'4fP.Y.bfHS8_t-DBZ,K.?8mD.lYScj%(9C;\O4#>2uTX7E:B[_7?&/Zp&TO^sY41n<2X>uHWf$D1YqP%ft08r0s[s/NrWr)giX*M+;=[FaIbZV[tl`4Bm`=#$WfIIIWWAE*P+(?eC:$Pi&$H?CZ.Jcdd@=YHrJ*Vo[!pST?d)YamqmkK=%bW\5O<=_95B_YE*PdTG<=i5A8G`@q)NiDj>D:@NAo>-3Gs63A=jBmO]o(;~>endstream
endobj
23 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1236
>>
stream
GatU3?#uJp'Re<2\C6eU4#P3jmCYG6BfpN3en3WaaGXM\d09_B_B/aS]n4mrgE^Ic@E.FskStW!F!N:jnZ\J>9b)+W_4NY%=>iPR=K^UL["':1pi0_*\+FOTT@aeb7\4=][c?Cppt0C(=5+,LiRlYc*5`Y@A&)Dkm+93<9u!:bFmq)H"f*[c1eSlRI=gZ)M#L4YJ)To9T]:+oRR\Xd*%BrjaB8_'([u92G\h'/=a!TN>7*][M@(I=A4g9/jCKoWAO(oMSCSFsn5&J1l3U?4]#j^\X!a<gE@+S`1Lbj`25;N1q]+[G(!Pgld6(Z?_Y8#WQt_(r?I'34%8jYe"stIlGt`8$--<HF#--_W7'1`;d?nbrH-E4IQr`*Z]e4QH@`gY%&kp3J]!8n>=/g@H0hP;:V'k<QcuGg>H9$Ji&$,*nkMVB%?uSUcaZ,*HAQ0L#QOVU*A,@Vla5hr9O;=>njWMRP5kkA;"S4st/Y:R<QHQ;\<[?#=]BDn\4'I1X9ZK;&ms%`U6$<Njq*Z@IMR#?0bf!GL([;FgMX;g>=j<;>2%574,RmAZ,E1,,P=9ToqP.XaP+f]1oo"tOT_N,$U$Yf:oGCZbGrI%YGE>\9UF6R?Ql,N3P>lp!>^4JMbnFnaaY3h8V$;X[R!ZGcmGBDf6X-:kE-YKi&^4;74[ccq3b[u0csY6_s$GT+/nme!9X'+uDQYLYd#(_ta1e)^oJ3R*HaJa]MCc5\3$'ZbB6'2"f>VaW_n&E]^jG7@njlsI\Om/OMc+5]3Lu$&(>;J[4c<ODCgoC?A<m6fE2^E/:.CE*dZ8C*m\*O2cD\K.ML(blj@C_6R8jBfJo"u"Q:+Q]*E5KuPKjWnRfO9WAe6S["!C]p$Pq]Lct:mf0LN#N$k3:i_f[ihfTVnCEk3QX#E/nQ=9LCFkS1dANrMWJ.F$TZe%CnM-nu86GFCk*G6P-l\=dX,YU;Y0pj*$L>F>"fRPPj"mW^M,k>@1l4IlWeKlFGe?@P*M)kONbLSm6!i+Zt2m$sth@F)^XS1d[S5!4>o%R\]oT:t@j1\=HO?,b/K'gm)DjgsIs_u&[JK'7Q+fbe*Ws)K&9B6Vhjg\EU,j73aTn"S82c<l'J!1))TV?c&hgk9*X2-hck@=U]>3a4Z2`=D&'#3MkJORijRjpQ2@r#HYo's8cTM.%o32kRA4T\9A$P1/!/(lNb6e[;9>g;miARn<]mSI,._7X4BY^9X/YMgkbY`eZ#~>endstream
endobj
24 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1260
>>
stream
GauHKhf%7-&:Vr4E@<Nb*FdQ`/H:#p@jM`F>+R$a*RGU+U+Iu1l&o"Ds7[aL-f*Q1@eDD\YUl!tID5UH]/p,tIA,dZWrV"SnEq%mEZs6>)EL5V(D*HF?`qb$kK%d8;g4+,ljoV;l](O`H?mjgMuAo)!PKJQ?a'*Q*[&rPkaUUY>KY.#U5[0P0Ek_J0pBCs+F-E6&PI6UP*Rrb%!"ZEI7`p48H4;62hU49IJjG447.hO'*P@>&BDWn)S[-biJ,_o[A*.c.,8GLNK\_:13F]Gf,qmfg]m'Td"e*8R7>@o]Lgh>NQ,8AZ0"iAp[;-%&3,#u%P5IW8>Vk$TqqQP8_B>n4TK&4^e,3TU]mK$f79e$/#W_c^pkjBgOnK^e<Q^6AiYrC,*";aWZ]YNfO/_I*ANiOdFb3fdbKR+,;a.D$6$Rk_4UYSJ)<UU!W0m3kC3TI&+Mbe^h,G7dVoD/)R-o9;%gjke+Yfgq#0`eUSen&8;(St+8I_6"IHEdo*SE.@FY$4)Vpr^(pN-QX,O.@7$10*gqp)SG-lS=\_?&KKEG4W!KF(&\,O-$I<NZfH0+%.=O`d;TDAU2/Pr0"AO4CY]SSju#!9K5d7^^p=%7:$Dg<oAcL]A#gC4[k?($f>bcGba(o[bAUJhdVfK!dpYt:_L`-jdALAn-Hi.dl2i;O=l(N[+>X<:)eH*mBMfc>Bf8*;F_Y\M.T?e&$>(U'Zt[KGKDOuO"ds'VVE]+XQYo_T3*IAqEq>@9d'"*VRT?>V.*L:[LCI9U]bl9-aO,3;N^/%P5bP:K!6XV4JjXQ*]Zkb&L.q+32b^Rf<M*Z0BYVr@tPU28a]d-JX?r:\4F;Q9-FA/1rW6M"b0"aA-L8Y[4q2GkCl,_\@aCUaG5a#@_F2RQ_U'N9NJl?7n9#@[m:c#gW]$&euTM()`m7gi"hWt:b8b\M@"Q!l>#SDl]l;UGo)a<GNie67ge$ZAGZObf/<XF`89QRU^F_'6Sme&(U!gc90&:4&:<A-QQ"?i4AAbX/`:>>oU7.5%:Sr1OG.n[\)2W%NE,Uu*5D9#utRQ59/4mAsjo;+nJpB^Ks,,.&_d[p),H[[+8-7Xh<c;<?3&0VV.j^*1lE/5sU'.E+np^1[_Em*$[nGseA#3MVj)U>AKjFL+Gs7%Te;5]m[eM6!Qt3Ac_?VPc2-ef"u&*VFn0G\&p\To?Kkd7bcWe64\NX74P&1gm]9>o!W-^#^%-7]>o<_I6)4J4OPkRmG5ZGV2_Ys3\0ge_ab[\gI~>endstream
endobj
25 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1387
>>
stream
GatU3>uTK;'Rf.Ggm=S.45e+#$%]RI#08XR?,pZg*F=E$`%&4V8ht,[rE_M"!bKBOFtuE)/F+A7HZ\\d4q-r@GCT3&i&lYd(I%TpA8CZZ07n!@Zg?VjJcDrp'R6D%i7*OGiP6B8i<Z'ngOnq&g`55^rmRb>G`<Ysi;;>b6aPTW<`7#8fG6_`gM#]b">5n[_Lkg,;AJ0sFNL?8*HT6b&N#YcapZ3Jmi_K;fj=s2htVI)<H.5ZMJO[p"U%f:fheN5i("mr;cH%sDTH",('"PK7f[T?KF;'`#n<"8`#@)i@4PkgSA@_4W<^n0EL)g2"-^YTfn&)3Xrl2-I,^S1%PthuQmumagbN/e!K"bq/[.(k*fGl!DXm$)S->M3a?<0p;2P.8Og?Z)P\VU#\7iF=4",peV+&k[iYc_IWs,G8kQhN2FGIgc0JZZ=,HG'I;ObHi,Gd6!kWMj8.@]+Gi=h0Z(C^.u)=DJKO#Wn_o&:'<P^`Rq-'4se&+fQ//f%$A_D-FBV=Wpd?;-PBS&_R?[Htpp*4sdAM&E$m!PPCO[me^i]Ap?6V,G-u<(NjF7BV@il]ME)j.FZ(BekaqTF:>i@eB6"Ie`Z<V]bP^5:g3qCZDGqZg>P.jNI*&db3ed]#54a`!bDM&5V<MbXB'J/LFV`IYIA^1Atku2!B9Xh[dD>4<-T*NaB`>3W.H#QC"8c:8+]C]X%l)#TX41knlWX;)NK&U8T%$:_,,DFKJK*T-tW#S5e"qIQ5_7E.juS9a+#r0?%8(&;OtHGTqU">iY8p/`eu]@9j8j2kr"8c2uG8GD&pm+utP4UV<q.W.AZ@@i.Y;I,EPo,!sYsn6WCTNX`%bUek%F:b?_UjJFkGB3Z[DKES%;0WEe>4+UgB,7mCqo[(-YUMlO-;Dar8d@qbq]iuW07S)Tcc#Sa3c40VNBEe0g<-IZ<5R(X(N3AsU"c@0,PsYiES+/"$9p8SIc-fCO1kV:8cX,Va<'(Ng\!j"&r!'i$hc\&t%X+S#j5^+e_5Xh-3M+IQj>g(eh0?]jIkU>J!Sp;Uq;ItCYM/1MHl%!MSNhAMq;5Kip.ka.];4+Ek`V5Lg8!1UqB<M/5:h<5(^9ok0e3pIT#>l(nAM!I24Y)8'!+(YI&36h4Lh+=;Q%a#nBa>0(&dq*;ce61&_/1N9dnY?NOFg^pgQ],R'r"=i`h(k31A9;2eL9=:6,dfjVFLa>Xc[Q4cFO(.CJ)o5BJdZ=7jTuR&R$jr&M2uS"O3Na9W3cVR44oU1`,fk->r;?B@DS-ab\Vh'q(FD+'*3IuX4Si&StX`U7Ekfd*[:?(8C4b>C4si*9;A'i'H'H[!Qmg9ekobq1V4BNO+G[:5oI\F^'e91dN,M,lR;#4teQ1?ZCd$kE7?/VauC~>endstream
endobj
26 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1306
>>
stream
GatUs?$G!^&;KZF/*?K$A5D4X(WPNrJ19naBYeP(E[,CO[BV%/QIj<phiXThj9'Lo6QkP7=aK5\kafNk'44f3)<V"o&c=Yt^p5+eKB4"(6rOAcX+.:R_bAB7]Y<42T[u%1drmj$%Yr;@\Cb'F$J'2oHp#FQ*O)VW_6-tE';ZC`&uU];&kG=Y]r;k+!*P.N6Ch#BdK8oJ#;P2AU=TtT%FH8Tj;Yg>UTs\JBk1)+OsX!]R=_@Ea==nQ%?2r6ruD0B#_1?_*Y+!mOZ^D1''U6]/i_[$7.5EX4/1ViN(Fd@_Tur#&k,-F_[Hu"\(_O09o5rj'2\a\<OL52hfdlhs&o]=,$B'k6pa@7.;sa%OJ;NH4K<pA%0(F4d5.=@3%l`SHiI^R"cPF)!#EC1=9Lpr:$:R9h\(B5_IYP5$lE&K#*S(I"/P8;/Th@[MOtGO+JrE'Bp9g>QjO!cPqXj)RR+@#$0W:$Q;CMg8S+lPjB:r^'iLS=j!974(58%YY1`A"64&LA,sOBrM*?H1e>[t%E1Qs0[Usbk2(=lT;IsFUd21=^8G)2l*@cUX.F^(@'eL*)"30;@`TbWY-F_m`/X?=&&.Bl;AqX+9fHSu_PU[N_1=5OH!<&7)?u,ZigM%5lg7%+AI=bu%Oup`&Y:q+W%`Rg]/X%A<KI*8A:"8*(P#P\SV'I$r=h;N:dA2a3RJZm(^-9B]<m!U'b!Cki^[L3-PX&kD(^1JqiI3g)rcJ@U4e;7(Vpk++kB)%(/]fGS'At[lE-?FN4<6XRpnT9`I=cU0coVlOMU46r@F$\[fCaV*IN9s`LaM?=GG#@lYS^-P*s'U+nEkQ>W3L=8BmC-Fo^TBmYl2aFbrad=I@'.J521)da*%7a^5%_cqYnD*&`@d/$p`-%YFt:ZDd=Tc\OQ#d)VHdWFVYi+9F$KlZT:#SJ"Tp5ANorS%H&"6iMT7hq:aJE>Q7M?5TrOu8j5-6&>RitaS,Qd=-gJ<Y!>uG".F,$.QC?9M?]<u[Oo[U^kO[UO&T,0)Cn'>9=@\D@'rAFO%#Wr.4O)>JWqFqd;Y#e9U=SE0.VmZLUBn%Zb3aF=R]Zk[h=q)X'-.6KG=\B-l2_aLr99Eb&K_hUQ3N%Y1".RAL9N]EgGM7B)-BTNL-!3`i]]cZ1>'f(l$N/b9..0L\"ZUQqsWr8-@"PgQN'Ob5bbMMQ:)T#bjpfN@Bd[n<,(I<ULE5SVc`W#72n&FYkl.p1*jhYc6mn1U=lX:oQepUe=Rd]"hYffVr+fnFo+_?`M+!D</Uk2Fd;kM2anW>0O`21L;.!kRYufi9GduIB3Y~>endstream
endobj
27 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1191
>>
stream
GauHKgMYb*&:Ml+bhK!&>;^Q.j1H'ulSMqXp!ti9/C52&)G=lp;C/<<qYM9^gNK%j#aa0ZJhO>Wc/Hl,B8-h,FKtALBE6?OnGJdWMd_)8Q:<=i2mD!XI6(j;XIR>YHl4RT/@&D8Mj$0W7t9!Im-D"AXp.udh#%"3!QKklc,Rq+E4i@do%hKIp%oKblY!I%#fj^9Ios52U-L[D.4mM_BXDY$\DjBR5g,]`jX0$ArU0imGB>tP/meMbgm?BKT_sQ5*q-A!U8jdNJ[\hJ$CrSQ3ZXnRXUt+*XDZD86<3Y.GAs>LSb7Li3sj,tE"@!-YBB"6%[=j$[ad])k]I\)%:XdlnBFW(I9sW&$pGZGO1*GkB--"IET!G?5O98N.%:QDeBm\WNHI6Ph6t!%N/RqGJJ*>D_55V8<sStJGp-FB$7neJO1<:_0Q)LX^[<,r#gTi/67[h`h1W(AM0BRq@a@@U:m1mV#-F2^'3[\+CSr]#gR9NlEfYT\-RXJM%=[RF`?t8c7Xl0_G=YoU6'FGkd@c9CZFMp9++ta/CoPM>Tqs<2#ARt\a`k9cJ:n*+kDK!'5X/4=iEga3A_rsU59(kQAq@?.\k%IMi#jS88-CNLKPTq^.QucDdU7r)_65/--7nd@RQc<2I>@H3!XIQfkol-F97ioTg6<A+.RcY_TV@J7^kKYr'Y$[",S#d8YVTMcH1.:^2rmA$-i%EfG=FQIas/kRF^h'&/AGH)*'CIbc.Ih62=t2Y'e4hW;aI\tojb-sB[Kqa-im9JZDa(6L5MK&rmIA-#X/bVjN8ThdkC.mjhPRM1%9o:-TmQ`UnNtW1!>Fe%1.r;<g1n+38amO)9Lfj,7aD.5<>7GfTOsG8!M\&[Ujag&&(9\ZI05ONeSIH1EWshZ,LtX90G>]69Y1HLIMr,'Ndl<jlB[1HheB$B:ha6R-BmsL'*d%[LOSOWgY969Bm@j7]X8(JJSk5lISCi\lujs[CqH*LL_HkjHd#d4#\SR'K0JH>"(+rZo@s1bXgMb6Xn58)_^D[e!Yc+>*_GF(#B9Z\2`#pVJ;06YXPLK&%a[JX+kIp7u'2L,P.Tkb<dZHWG7`PP5J+tqsf&)_eN'Gm"L/=*[@0Fc/oX^X1,95436Ai]@jpTj_l>f4X@IkK@Z%m/#TVGK>Q!EDP'D*]VfuOh\"t)RP/e,5'bYMiTP2GTA"S~>endstream
endobj
28 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1219
>>
stream
Gatm;_/A!e&A@6W#/+hV3*JmS'W7:OP.ITCFGikP('/_!;j>4T,$g@hU&Fg.8kuid(L)J>%*O66o?@^$]Qq^DV,oQ\=V#JZL#:d"OQE]`+=iTV[PtGISpW]CNk[.:UqFo)1UoD?P;HX4oKRYl:6/+r67ZU((8\e?[,;S&Cg->`06_`MY&<.Ph5p'2.rZMa*D2a8)oJqHRQ]:+hWm\*>'alScuq'NqZXF3Hm!pdlbA%n0-c+KH,EY,N<LaP31QNCJ>K;;YdHlOcq78r><k,YhdA+u#\[<f*]_`emNml"<:G%WNrM55Echs3&+903NI;gt;3p*Jr4)C3i4f7f;8DFSC%5,oklE5-YZs1`"b9oDEZ5!aS4BH#U^po%WU?n)ICF`ZDukVAo-,qsK'@W(JtW=@pnMf<'2l>[Y/?.tluX,5n/W6hO+Sa!q0I6gC*/IK>'?2YY]8$`C8[ta0;(&rDlsj?&*\J,7trjC'lEf=1XU84-%SOT*bEU.Z0"UXG+"%nkKhlLVRil2!/`Ne\Lj-\HHL0(LUiff*t3#!ipNJa6V)BG0Y<rg=5@2tgO*6n[=QZ%+-R5WkEc3g=fcAqlbPhZ)XYhAY,LUi4dFr5Js<9;`QC#$X"7B#&!GX?HV+GaQa*!-<S_MpVq+.fEjWJ+!c-t3(/UX0!&5p[=FmX&+lV"fZsn'S$/B[Qc'H?pI4&`)!oWZPINNMEP._GjRZTdZ/XXukF;'1o`X4YI'@T)l,Og]t,.GdPh3>LDW>RBFQE+-\5#/-_/JF$*V:q\\Hf=PbMMT!q3i'0MA[%38$.lJ=-1rY>=Q.&O[W?p$!9M$"rY6Wbb[RKpT/qNRP%)SYF19VU>Bc<Ei@O1[J6(#Y&q1s-@iPj;7PYmQ@,2O:TE[K>G1#4U5ei+TBKKD<HV1T37=lKN:7;MT-BB-Y[0])O@MS_GrBSZFpT/%nDcmOUaUE^FA(aQ8fSGu4,oGL5W?o#P'p(/=)/K`FeNCO$</gjHMmIU)2Lbkn<jmlclFHcPXa0rKl@pi+3^/ZL!5"1ilgIq?d2epS3XjkQP$.nk?l)6Wp24OGFuRTS*VFehZX,`3HrrOF9iM5GTeMZ^@'o6AAdsP?NKpm^7qhH5Ms9YHomUt'&)5k3RZ=IJR9NG;8?`9e?@,n01*ECCXE8`@dNZqKDbCdWN[V@Ukl@j`aKA2uOOJFQ(-ZFWCUbGZYiG.AVZnOK!.,IQ_u~>endstream
endobj
29 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1286
>>
stream
Gatm;?#SIU'Re<2\EM,a@IYoEql0(>C^X'I>A&$pO;CO#[b+EP6;g\Zmrp0?/Ws[&b7+Yl!KqTnf/`RZUFAC[n6Iq?5O@V2T/+Q0g?g>Y:Zf$29@pe6qhsh5j>*>Y2u+eqeRCS-k-HB5LYL;:[QRmR%G@YGipR=_328#D?FU9E]?I2KP'eiD3R_qaHID`,Q4CouAS9Kp=-M7Cq`qc3"8M?[]G*WFM%(SW)Sl(&O2'J_^S^B-h-Rgfr&VB:*<Cq9Hl4Q[As:noZ$o*35=N9!/<0@$G>]Ml!100T@Y+P7*NEh-Y4f`aW,-D=*EQc.2;B$qMu[3:'>[h<p=OZt0]s%"Ya(\S#UogtYt.8'Z>IgQXmfn]F=SdMg,L`YFP90Vcd$\.?d[QUC/$;7P-l\0GqZ4;,mr,s#\6WSBB4ODN\I)6+(M'=?XoNEfYg;@>Nl1Snsf0i;l;.S6Q73T[FjR?A`+W1DiJBBe4M?]8tcr#&_O-8Yl_,K>lTJK4@suM$N4Q^g0jVrh-&2^m7\gdDoO/27>-pWEeD+%g+lSOPmVVHfsHV[E?_D%nacA^H<2o4LZG!bD$qO%N$1J[5+kVR.-Z*frcF]eJ$"A=(qislNlO%7JW^[5A@.[/Jbg9^TGWA<]o.\Y^.oE\)a-Sc)eC-)c$.YeE<I_hAAs6;SgSX?8JuU48>l`E&J<;D<?>?fotF@HF[\pZEJR=m.SM-cWh(e@`D.I2g9rq`fT63U,@[+>MC0nZ`p.<W2d.4n@k1X/Pm3.,%+!cI?\$<SXidDMo\!QbI_1$;N<#?C1?3"dcX.\fhk0W&Yb">C3fcI/Vt/gm$%WO3>5Hn?g>F`4<Qpc.6l8pslC*=#VJLlI,2hVH#5tXh.8]CK^GALu]WL:8".i,AiI)AjQRm)A`jQXGI,Np+:HB<Uara(:6(]TrZX`c\.Rr4aA8r!>[+:K.>\GbIGOe$J*`nr+\n_FL5nDtD5!2XqU5Tr[4B^Pk<q<%(KrEqrUZ=S!f<5.kbNH4][D!#Y-D%L9ZsZPSMF);V\ka%,CYVZupjED.?msC^^?37Sq%rGEq+.f%205JuX:'m:_LZd%$nr`0EIuAs[;<2eY*Zuh3tu0(ABDl"T,"dfER#KOi4KD1S8[g\qiY/J-pS'U@`O8A<k&A$.auE@Pc(\Q5<K?$m6PbL@l.=UF#mU4oc]W-*(_><`cL\(T1r816h"@>O:Pit.3.`9FCYE=_nO0I6pRrro2)#T$tKmDY=IJQ.LGF_=5i4W9fS#\V%/N(-gnk,FT)H4d)FlH%O`@~>endstream
endobj
30 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 995
>>
stream
Gat=jh/_4'&;BRuMS1P)Ub;3[E7H+_9\RqAVJ\Jdac3NXXi<o]P!aKkXpftE9nVG6"rLnCS^j<;/V+tW8H#,4!R;=&HU32WO=Yp8?3nXe-;!BEL[:UkI4lZ<6N[4bkOn`q`rG"<Y?iif%dlS'2T<EaEPhb^WAfe&G25T\<iZZ`Tl3*(<!C_n$j[/"0-R6VC$u1f?]_q/kV)^W$L\L0il@QE=EBVd`e.&DPMCtY(<$Kb.(D)dnD9faS9SHJ9:jQ,`c)B%!>GmGi*^S?0R*eC?3l##fdY:,$E;9i3Urd";@^GTN6@HXDYYXB+Si^%,u&G(3NQ4\=@r1CP$==aK&X;:TLptGC82Ja84+@dXr+p1R7p-&,6pJQbi=?Z?8.0>HVW5SgiV/YcmrGZS2el7Y641L.(4IDNbkm(\j'2Kjht]Ske>c"(q>iGI;0R+UT/O"lr&498Mp'Dk&51.g.mE6mYiF=fVJ\a:KlcXEiP;q=2qMg3$90rlm6<E'd9X#kaIf8c!a@GCi&fhVRV)F'NV4Kp,a'bN>D7T9ati7CleQAnCB0D_8&_1>l+r2Kk+rcJ.18gfMgl?2r3rZb]BbjYi"$h2Ro<u2.g'Ek14RLWj:D\R=i,hW3s5PgN`4uj@lAHVP+chk$36e(3l#lm82TsdAHV%ISCMFJ['"69M-dW5n2^d@<f%<i-T;omXiT_U7!="X5s\@h46j.7I%^fbUSD;K0`^Of<CMi"O7B-,>&LhoCLAaA'V&n0m(dRIA*?Sn1g;1CZe4$MVkTE>KLJYdOmhtcXi/ppi885_s@_nb6_F7hI!h/E("Q0bATB4PfAF\5:@jXB!>+!?!].;f#"jJ"fYZXSXQB>$sMrk:hbOai%;D_n=")R#*U36e,e:7M)CtboltC!fI(RpkYOQ@57fdr8(.Q+^[Hl/i;hIC#2P3bGQPtqcDIB.Nt9+a1nT(`-]+GH8VYN]Yc0E(=P3qMf!1F>nNgT)IA[W;IfO,C+q=~>endstream
endobj
xref
0 31
0000000000 65535 f
0000000061 00000 n
0000000143 00000 n
0000000250 00000 n
0000000360 00000 n
0000000465 00000 n
0000000670 00000 n
0000000783 00000 n
0000000988 00000 n
0000001193 00000 n
0000001270 00000 n
0000001476 00000 n
0000001560 00000 n
0000001766 00000 n
0000001972 00000 n
0000002178 00000 n
0000002384 00000 n
0000002590 00000 n
0000002796 00000 n
0000002866 00000 n
0000003163 00000 n
0000003285 00000 n
0000004692 00000 n
0000005969 00000 n
0000007297 00000 n
0000008649 00000 n
0000010128 00000 n
0000011526 00000 n
0000012809 00000 n
0000014120 00000 n
0000015498 00000 n
trailer
<<
/ID
[<2710dd68a4c005a06a0fb28136ebdde7><2710dd68a4c005a06a0fb28136ebdde7>]
% ReportLab generated PDF document -- digest (opensource)
/Info 19 0 R
/Root 18 0 R
/Size 31
>>
startxref
16584
%%EOF
+234
View File
@@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""Render a screenplay text file into a PDF with Courier font."""
import sys
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Spacer, Paragraph, PageBreak, KeepTogether
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.lib.colors import black, Color
INPUT = "/root/workspace/trilogy/screenplay/night-trilogy-script.txt"
OUTPUT = "/root/workspace/trilogy/screenplay/night-trilogy.pdf"
WIDTH = A4[0] - 2 * 20 * mm # 20mm margins each side
HEIGHT = A4[1] - 2 * 25 * mm
def make_story_title(text):
return ParagraphStyle(
"STTitle", fontName="Courier-Bold", fontSize=16, leading=22,
alignment=TA_CENTER, spaceAfter=12, textColor="000000"
)
def make_credit(text):
return ParagraphStyle(
"Credit", fontName="Courier", fontSize=12, leading=18,
alignment=TA_CENTER, spaceAfter=4, textColor="000000"
)
def make_scene_heading(text):
return ParagraphStyle(
"SceneH", fontName="Courier-Bold", fontSize=11, leading=14,
alignment=TA_LEFT, spaceBefore=12, spaceAfter=6, textColor="000000"
)
def make_action(text):
return ParagraphStyle(
"Action", fontName="Courier", fontSize=11, leading=15,
alignment=TA_LEFT, spaceAfter=6, textColor="000000"
)
def make_dialogue_label(text):
return ParagraphStyle(
"DL", fontName="Courier-Bold", fontSize=11, leading=15,
alignment=TA_LEFT, spaceAfter=0, textColor="000000"
)
def make_dialogue(text, paren=False):
if paren:
fn = "Courier-Oblique"
else:
fn = "Courier"
return ParagraphStyle(
"Dial", fontName=fn, fontSize=11, leading=15,
leftIndent=40 * mm, rightIndent=40 * mm,
alignment=TA_LEFT, spaceAfter=2, textColor="000000"
)
def make_transition(text):
return ParagraphStyle(
"Trans", fontName="Courier-Bold", fontSize=11, leading=15,
alignment=TA_RIGHT, spaceBefore=10, spaceAfter=10, textColor="000000"
)
def make_superimpose(text):
return ParagraphStyle(
"Super", fontName="Courier-BoldOblique", fontSize=10, leading=14,
alignment=TA_CENTER, leftIndent=20*mm, rightIndent=20*mm,
spaceBefore=6, spaceAfter=6, textColor="333333"
)
def make_part_title(text):
return ParagraphStyle(
"Part", fontName="Courier-Bold", fontSize=14, leading=20,
alignment=TA_CENTER, spaceBefore=20, spaceAfter=8, textColor="000000"
)
def escape(text):
"""Escape XML-like characters for reportlab Paragraph."""
return (text
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&#34;")
.replace("'", "&#39;"))
def parse_and_build():
"""Parse the screenplay text and return flowables for reportlab."""
with open(INPUT, "r") as f:
lines = f.readlines()
elements = []
i = 0
n = len(lines)
while i < n:
raw = lines[i].rstrip("\n")
text = raw.strip()
i += 1
# Skip blank lines
if not text:
continue
# Skip "PAGE N" markers
if text.startswith("PAGE "):
continue
# Skip "FADE IN:" handled as transition
if text == "FADE IN:":
elements.append(Spacer(1, 12))
elements.append(Paragraph(escape("FADE IN:"), make_transition("FADE IN:")))
continue
# Skip "FADE TO BLACK." / "THE END."
if text in ("FADE TO BLACK.", "THE END.", "FADE OUT.", "CUT TO BLACK."):
elements.append(Spacer(1, 8))
elements.append(Paragraph(escape(text), make_transition(text)))
continue
# Transitions: CUT TO:, DISSOLVE TO:, etc.
if text.endswith(":") and text.isupper() and " " in text or text in ("CUT TO:", "DISSOLVE TO:"):
elements.append(Paragraph(escape(text), make_transition(text)))
continue
# Title: THE NIGHT TRILOGY (centered, bold, top of doc)
if "THE NIGHT TRI" in text.upper():
elements.append(Spacer(1, 30))
elements.append(Paragraph(escape(text), make_story_title(text)))
continue
# Subtitle line
if text.upper().startswith("THREE STORIES"):
elements.append(Paragraph(escape(text), make_credit(text)))
continue
# Credit lines
if text.upper() in ("WRITTEN BY", "BASED ON THE NEWS", "HEADLINES OF JUNE 2026"):
elements.append(Paragraph(escape(text), make_credit(text)))
continue
if text == "Kevin Hermes":
elements.append(Paragraph(escape(text), make_credit(text)))
continue
# Part titles
if text.startswith("PART ") and text.isupper():
# Remove the (YEAR) parenthetical and handle separately
paren = ""
if "(" in text:
idx = text.index("(")
paren = text[idx:]
text = text[:idx].strip()
elements.append(Spacer(1, 10))
elements.append(Paragraph(escape(text), make_part_title(text)))
if paren:
elements.append(Paragraph(escape(paren), make_credit(paren)))
continue
# Superimpose
if text.startswith("SUPERIMPOSE:"):
elements.append(Spacer(1, 6))
# Read next line(s) as superimpose content
super_lines = []
while i < n:
ln = lines[i].rstrip("\n").strip()
i += 1
if not ln or ln.startswith("PART ") or ln in ("FADE TO BLACK.", "THE END.", "FADE IN:"):
break
super_lines.append(ln)
content = " ".join(super_lines)
elements.append(Paragraph(escape(content), make_superimpose(content)))
continue
# Scene headings: EXT./INT.
if text.startswith(("EXT.", "INT.")):
elements.append(Spacer(1, 6))
elements.append(Paragraph(escape(text), make_scene_heading(text)))
continue
# Character dialogue labels (short, ALL CAPS, possibly with parenthetical)
if text.isupper() and len(text) < 60 and not text.startswith("THE "):
# Check if next line(s) are dialogue (indented in original)
# We treat this as a character name
# Handle "(V.O.)", "(on phone)", "(into headset)"
elements.append(Paragraph(escape(text), make_dialogue_label(text)))
continue
# Parenthetical directions: (quietly), (beat), (whisper)
if text.startswith("(") and text.endswith(")") and len(text) < 40:
elements.append(Paragraph(escape(text), make_dialogue(text, paren=True)))
continue
# "HOLD ON..." lines -- treat as action
if text.startswith("HOLD ON"):
elements.append(Paragraph(escape(text), make_action(text)))
continue
# Default: action/description
elements.append(Paragraph(escape(text), make_action(text)))
return elements
def main():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=20*mm,
rightMargin=20*mm,
topMargin=25*mm,
bottomMargin=25*mm,
title="The Night Trilogy - Screenplay",
author="Kevin Hermes",
)
elements = parse_and_build()
doc.build(elements)
print(f"PDF written: {OUTPUT}")
if __name__ == "__main__":
main()