From ce079c96b0148038c3657d57048d4d90dc691128 Mon Sep 17 00:00:00 2001 From: Hermes Cron Job Date: Sat, 20 Jun 2026 10:42:58 +0000 Subject: [PATCH] add screenplay PDF and source text in industry-standard TV script format --- screenplay/night-trilogy-script.txt | 457 ++++++++++++++++++++++++++++ screenplay/night-trilogy.pdf | 269 ++++++++++++++++ screenplay/render_pdf.py | 234 ++++++++++++++ 3 files changed, 960 insertions(+) create mode 100644 screenplay/night-trilogy-script.txt create mode 100644 screenplay/night-trilogy.pdf create mode 100644 screenplay/render_pdf.py diff --git a/screenplay/night-trilogy-script.txt b/screenplay/night-trilogy-script.txt new file mode 100644 index 0000000..5854982 --- /dev/null +++ b/screenplay/night-trilogy-script.txt @@ -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. diff --git a/screenplay/night-trilogy.pdf b/screenplay/night-trilogy.pdf new file mode 100644 index 0000000..78c07bd --- /dev/null +++ b/screenplay/night-trilogy.pdf @@ -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/NL3i:H3U_;_TT;<8s"?-XlPMhA&g/O>.j29/6TfoBmX4Jb?TSLi1%7ktJ87/7hm05m$_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#WKW5N=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=75<'YC,3K=H[T1XBIB@'PR`+)%T,r!oUKoGd\h/rSbQAkUF='gLZi/qRF?94(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\+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&ffd)U;,baHoWkk: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+im9rEQ4C5W=>,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$fWDBaT05HJ&BD$13C=ehlro#=@c/mI-ZU?!K!@kXU+klfe'73)l1cZZAI].*X.8,@^?uip@!Tm*^=,_EF:(r.Y)DJD]JDE94/VgRqG)gcs8d_`:H5$T6ef,d#=`4a+;Z$%g4rcIm_V^FTbe.]@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=/g@H0hP;:V'kH9$Ji&$,*nkMVB%?uSUcaZ,*HAQ0L#QOVU*A,@Vla5hr9O;=>njWMRP5kkA;"S4st/Y:R=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[4cF>"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"S82c3a4Z2`=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@+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^ebcGba(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#SDl]l;UGo)a>oU7.5%:Sr1OG.n[\)2W%NE,Uu*5D9#utRQ59/4mAsjo;+nJpB^Ks,,.&_d[p),H[[+8-7XhAKjFL+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*OGiP6B8iG`b6aPTW<`7#8fG6_`gM#]b">5n[_Lkg,;AJ0sFNL?8*HT6b&N#YcapZ3Jmi_K;fj=s2htVI)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&:'i@eB6"Ie`ZP.jNI*&db3ed]#54a`!bDM&5V4<-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+utP4UV4+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!1UqBl(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%/QIjUTs\JBk1)+OsX!]R=_@Ea==nQ%?2r6ruD0B#_1?_*Y+!mOZ^D1''U6]/i_[$7.5EX4/1ViN(Fd@_Tur#&k,-F_[Hu"\(_O09o5rj'2\a\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%AW3L=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=-gJuG".F,$.QC?9M?]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<,(I0O`21L;.!kRYufi9GduIB3Y~>endstream +endobj +27 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1191 +>> +stream +GauHKgMYb*&:Ml+bhK!&>;^Q.j1H'ulSMqXp!ti9/C52&)G=lp;C/<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_55V8Tqs<2#ARt\a`k9cJ:n*+kDK!'5X/4=iEga3A_rsU59(kQAq@?.\k%IMi#jS88-CNLKPTq^.QucDdU7r)_65/--7nd@RQc<2I>@H3!XIQfkol-F97ioTg6Fe%1.r;7GfTOsG8!M\&[Ujag&&(9\ZI05ONeSIH1EWshZ,LtX90G>]69Y1HLIMr,'Ndl"(+rZo@s1bXgMb6Xn58)_^D[e!Yc+>*_GF(#B9Z\2`#pVJ;06YXPLK&%a[JX+kIp7u'2L,P.Tkbf4X@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,NK;;YdHlOcq78r>[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)BG0YLDW>RBFQE+-\5#/-_/JF$*V:q\\Hf=PbMMT!q3i'0MA[%38$.lJ=-1rY>=Q.&O[W?p$!9M$"rY6Wbb[RKpT/qNRP%)SYF19VU>BcG1#4U5ei+TBKKDendstream +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:*]Ml!100T@Y+P7*NEh-Y4f`aW,-D=*EQc.2;B$qMu[3:'>[hIgQXmfn]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$.YeEl`E&J<;D?fotF@HF[\pZEJR=m.SM-cWh(e@`D.I2g9rq`fT63U,@[+>MC0nZ`p.C3fcI/Vt/gm$%WO3>5Hn?g>F`4[+:K.>\GbIGOe$J*`nr+\n_FL5nDtD5!2XqU5Tr[4B^Pk<`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\Jdac3NXXiGmGi*^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$90rlm6D7T9ati7CleQAnCB0D_8&_1>l+r2Kk+rcJ.18gfMgl?2r3rZb]BbjYi"$h2Ro&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 diff --git a/screenplay/render_pdf.py b/screenplay/render_pdf.py new file mode 100644 index 0000000..f2d0e11 --- /dev/null +++ b/screenplay/render_pdf.py @@ -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("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + .replace("'", "'")) + + +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()