<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Script - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Script - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 27 Jul 2026 10:32:06 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/script/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Python Program to Download YouTube Videos ]]>
                </title>
                <description>
                    <![CDATA[ YouTube is a well-known internet video streaming service. There are millions of videos in categories such as education, entertainment, and travel.  You can quickly watch videos with a few mouse clicks, but downloading videos is difficult. But in a re... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/python-program-to-download-youtube-videos/</link>
                <guid isPermaLink="false">66bae7dd6d08bb9d26773834</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Fagbuyiro ]]>
                </dc:creator>
                <pubDate>Mon, 14 Nov 2022 15:56:27 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/pexels-christina-morillo-1181671.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>YouTube is a well-known internet video streaming service. There are millions of videos in categories such as education, entertainment, and travel. </p>
<p>You can quickly watch videos with a few mouse clicks, but downloading videos is difficult. But in a recent upgrade, YouTube now allows you to save videos in its download folder for offline viewing. Still, you are unable to save them locally.</p>
<p>In this tutorial, you will learn how to use Python code to download YouTube videos. As you may know, one of Python's great strengths is its huge number of modules and libraries. We will write the Python script using the popular pytube package.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Below are the basic requirements to proceed with this tutorial:</p>
<ul>
<li>Understanding of the Python Programming language</li>
<li>You must have Python 3+ installed on your computer</li>
<li>You must have installed the Python library Pytube</li>
<li>You should have a Python code editor such as Pycharm, Vscode, and so on.</li>
</ul>
<h2 id="heading-pytube-overview-and-installation">Pytube Overview and Installation</h2>
<p>Pytube is a small, dependency-free Python module for accessing videos from the internet.</p>
<p>The native library is not pytube – you must first install it to be able to use it. When you have pip, installation is simple.</p>
<p>To install Pytube using pip, you will need to open your command prompt CLI as an administrator and enter the following command:</p>
<pre><code>pip install pytube
</code></pre><p>The pytube library improves video downloads. Build the YouTube module's object by supplying the URL as a parameter. Then, obtain the video's proper extension and resolution. You can change the name of the file at your leisure – otherwise, the original name will be retained.</p>
<p>Now let's get to the main aspect of writing and implementing the code to download our favorite videos from YouTube.</p>
<pre><code><span class="hljs-keyword">from</span> pytube <span class="hljs-keyword">import</span> YouTube

def Download(link):
    youtubeObject = YouTube(link)
    youtubeObject = youtubeObject.streams.get_highest_resolution()
    <span class="hljs-attr">try</span>:
        youtubeObject.download()
    <span class="hljs-attr">except</span>:
        print(<span class="hljs-string">"An error has occurred"</span>)
    print(<span class="hljs-string">"Download is completed successfully"</span>)


link = input(<span class="hljs-string">"Enter the YouTube video URL: "</span>)
Download(link)
</code></pre><p>You use the <code>from pytube import YouTube</code> function to import the Python Pytube library before continuing with the other aspects. Then you define the function download link.</p>
<p>The <code>youtubeObject = youtubeObject.streams.get_highest_resolution()</code> command will automatically download the highest resolution available.<br>Then I implemented the Try and Except to return an error message if the download fails – else it will print out that the download is completed successfully.</p>
<p>The Link function will ask for the preferred YouTube video link to download, then immediately after you hit the enter button, the video downloading will commence.</p>
<h3 id="heading-the-output">The Output:</h3>
<p><img src="https://paper-attachments.dropboxusercontent.com/s_2A6E5F7B9EF3D136021C2A8815B8956B830A35B9A863E60136A6FD8F4C45E374_1666119447422_pytube.PNG" alt="Image" width="1920" height="1020" loading="lazy"></p>
<p>The video I downloaded was successful. You can see the video in the same Python folder where the file you are working on is located. But if you wish, you can then move the video to your preferred storage location. In my case the video name is "Ronaldo celebrates with Antony.mp4."</p>
<p>However, it would be preferable if you had a reliable internet connection. </p>
<p>This library also has numerous sophisticated features, but we have covered all of the major ones. You can learn more about the pytube library by visiting its official <a target="_blank" href="https://pytube.io/en/latest/">well-written documentation</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We have successfully built a YouTube video downloader script of our own in Python. This helps you avoid the stress of looking for an external website or application to get your preferred video to your local storage. </p>
<p>It also saves you from having to expose your data on a third-party website or phishing link – all in the name of getting a video from YouTube to your local storage.</p>
<p>Hopefully after going through this article, you will understand the process required to download videos from YouTube without the need to download an external application or visit any website you don't trust.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Write a Script to Change Your Zoom Background Every Day ]]>
                </title>
                <description>
                    <![CDATA[ By Saransh Kataria Over the past few months, I've found a new use for the pictures that I've taken while hiking. I started using them as Zoom virtual backgrounds.  If you are anything like me and take a lot of pictures, it can be hard to decide which... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-write-a-script-to-change-zoom-background/</link>
                <guid isPermaLink="false">66d460f23dce891ac3a96816</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Script ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 14 Apr 2022 18:05:20 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/zoom-background-article-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Saransh Kataria</p>
<p>Over the past few months, I've found a new use for the pictures that I've taken while hiking. I started using them as Zoom virtual backgrounds. </p>
<p>If you are anything like me and take a lot of pictures, it can be hard to decide which one looks good. And then I decided to use them all, on different days. </p>
<p>Sadly, Zoom does not have this as a built-in feature. So being a Software developer, I had to automate the process of choosing a random Zoom virtual background every day.</p>
<h1 id="heading-what-does-the-script-do">What does the script do?</h1>
<p>Zoom does have an API that I could have used to change my background every day – but that seemed like too much effort for this task. Software developers are born lazy, right? :)</p>
<p>Instead, I found out that the Zoom application creates a copy of the background that gets selected in its preferences folder and references it. The script just takes in a random file and replaces it with this background file. And voila! A different Zoom virtual background is shown. </p>
<p>You can then put this in a cron job to be executed every day (or any frequency you prefer) to periodically change the background.</p>
<h1 id="heading-get-set-up">Get Set Up</h1>
<p>I have put all the images I want to use as backgrounds in a folder in my user directory. Mine is at <code>/zoom/bgpictures/</code>, and that is what I use in the script. But it is a variable that you can change to whatever you want it to be.</p>
<p>Next, we set a Zoom virtual background in our application. It does not matter which background you choose. All we need is the unique ID that Zoom will assign to this background. </p>
<p>There might be some files already in the directory, but we want to select the one that corresponds to the image that we just uploaded to avoid replacing a different file. </p>
<p>The directory is located at: <code>~/Library/Application Support/zoom.us/data/VirtualBkgnd_Custom</code>. The file name will be something like: <code>9WAE197F-90G2-4EL2-9M1F-AP784B4C2FAD</code></p>
<h1 id="heading-how-to-write-the-script">How to Write the Script</h1>
<p>We will be using a bash script to replace the image we just used. I will be putting this scrip in the Zoom folder that I created for the background images. Again, it can be named whatever you like – I am naming mine <code>~/zoom/zoombg.sh</code>. </p>
<p>The script is as follows:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># The name of file that we copied before and will be replaced with</span>
OG_BG=<span class="hljs-string">"9WAE197F-90G2-4EL2-9M1F-AP784B4C2FAD"</span>;

<span class="hljs-comment"># Directory where Zoom keeps the backgrounds</span>
ZOOM_DIR=<span class="hljs-string">"/Users/<span class="hljs-variable">$USER</span>/Library/Application Support/zoom.us/data/VirtualBkgnd_Custom/"</span>;

<span class="hljs-comment"># Directory of our images</span>
BGPATH=<span class="hljs-string">"/Users/<span class="hljs-variable">$USER</span>/zoom/bgpictures/"</span>;

<span class="hljs-comment"># Picking a random file</span>
NEW_BG=$(find <span class="hljs-string">"<span class="hljs-variable">$BGPATH</span>"</span> -<span class="hljs-built_in">type</span> f | sort -R | head -1);

<span class="hljs-comment"># Replacing the file</span>
cp -R <span class="hljs-string">"<span class="hljs-variable">$NEW_BG</span>"</span> <span class="hljs-string">"<span class="hljs-variable">$ZOOM_DIR</span>/<span class="hljs-variable">$OG_BG</span>"</span>;
</code></pre>
<p>If you choose different paths for the directory, just make sure to change that in the variable. We need to make this script executable by running the command:</p>
<pre><code class="lang-bash">chmod 755 ~/zoom/zoombg.sh
</code></pre>
<h1 id="heading-how-to-change-your-zoom-background-randomly">How to Change Your Zoom Background Randomly</h1>
<p>The script is ready. All we need to do is put it in a cron job which is a built-in time-based job scheduler. We need to decide a schedule for how frequently we want to change the Zoom virtual background. I do mine at 9:55 AM every day since my meetings start at 10 AM.</p>
<p>If you are new to cron jobs, you can use the <a target="_blank" href="https://corntab.com/">generator</a> to help you. I use:</p>
<pre><code class="lang-bash">55 9 * * 1-5 /Users/saranshkataria/zoom/zoombg.sh &gt; /dev/null 2&gt;&amp;1
</code></pre>
<p>The first part (55 9 <em> </em> 1–5) is what you will need to customize according to your schedule. The second part is just telling the OS what to do at that point in time. You'll need to update the path if you chose a different location for the bash script.</p>
<p>To put it in a cron job, type:</p>
<pre><code class="lang-bash">crontab -e
</code></pre>
<p>and it will open up an editor using vi. Hit the I key on the keyboard to enter insert mode, paste in the line, and press Escape followed by “:wq” and enter to save and quit.</p>
<p>That is all there is to it! Now you'll have a random Zoom virtual background every day (or however often you choose).</p>
<p>If you are using Windows, you can modify the script accordingly and it should be fairly straightforward to use.</p>
<p>If you have any questions, feel free to reach out to me.</p>
<p><em>Read more of my posts at: <a target="_blank" href="https://www.wisdomgeek.com/">https://www.wisdomgeek.com</a></em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to use a Bash script to manage downloading and viewing files from an AWS S3 bucket ]]>
                </title>
                <description>
                    <![CDATA[ As you can read in this article, I recently had some trouble with my email server and decided to outsource email administration to Amazon's Simple Email Service (SES).  The problem with that solution was that I had SES save new messages to an S3 buck... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/bash-script-download-view-from-s3-bucket/</link>
                <guid isPermaLink="false">66b995b93cd81de09c96b286</guid>
                
                    <category>
                        <![CDATA[ Bash ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Script ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Clinton ]]>
                </dc:creator>
                <pubDate>Mon, 02 Mar 2020 14:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/code-coding-computer-data-574077.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As you can <a target="_blank" href="https://www.freecodecamp.org/news/aws-simple-email-service-email-server/">read in this article</a>, I recently had some trouble with my email server and decided to outsource email administration to Amazon's Simple Email Service (SES). </p>
<p>The problem with that solution was that I had SES save new messages to an S3 bucket, and using the AWS Management Console to read files within S3 buckets gets stale really fast. </p>
<p>So I decided to write a Bash script to automate the process of downloading, properly storing, and viewing new messages.</p>
<p>While I wrote this script for use on my Ubuntu Linux desktop, it wouldn't require too much fiddling to make it work on a macOS or Windows 10 system through <a target="_blank" href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">Windows SubSystem for Linux</a>.</p>
<p>Here's the complete script all in one piece. After you take a few moments to look it over, I'll walk you through it one step at a time.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># Retrieve new messages from S3 and save to tmpemails/ directory:</span>
aws s3 cp \
   --recursive \
   s3://bucket-name/ \
   /home/david/s3-emails/tmpemails/  \
   --profile myaccount

<span class="hljs-comment"># Set location variables:</span>
tmp_file_location=/home/david/s3-emails/tmpemails/*
base_location=/home/david/s3-emails/emails/

<span class="hljs-comment"># Create new directory to store today's messages:</span>
today=$(date +<span class="hljs-string">"%m_%d_%Y"</span>)
[[ -d <span class="hljs-variable">${base_location}</span>/<span class="hljs-string">"<span class="hljs-variable">$today</span>"</span> ]] || mkdir <span class="hljs-variable">${base_location}</span>/<span class="hljs-string">"<span class="hljs-variable">$today</span>"</span>

<span class="hljs-comment"># Give the message files readable names:</span>
<span class="hljs-keyword">for</span> FILE <span class="hljs-keyword">in</span> <span class="hljs-variable">$tmp_file_location</span>
<span class="hljs-keyword">do</span>
   mv <span class="hljs-variable">$FILE</span> <span class="hljs-variable">${base_location}</span>/<span class="hljs-variable">${today}</span>/email$(rand)
<span class="hljs-keyword">done</span>

<span class="hljs-comment"># Open new files in Gedit:</span>
<span class="hljs-keyword">for</span> NEWFILE <span class="hljs-keyword">in</span> <span class="hljs-variable">${base_location}</span>/<span class="hljs-variable">${today}</span>/*
<span class="hljs-keyword">do</span>
   gedit <span class="hljs-variable">$NEWFILE</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p>We'll begin with the single command to download any messages currently residing in my S3 bucket (by the way, I've changed the names of the bucket and other filesystem and authentication details to protect my privacy). </p>
<pre><code class="lang-bash">aws s3 cp \
   --recursive \
   s3://bucket-name/ \
   /home/david/s3-emails/tmpemails/  \
   --profile myaccount
</code></pre>
<p>Of course, this will only work if you've already installed and configured the AWS CLI for your local system. Now's the time <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html">to do that</a> if you haven't already.</p>
<p>The <em>cp</em> command stands for "copy," <em>--recursive</em> tells the CLI to apply the operation even to multiple objects, <em>s3://bucket-name</em> points to my bucket (your bucket name will obviously be different), the /home/david... line is the absolute filesystem address to which I'd like the messages copied, and the <em>--profile</em> argument tells the CLI which of my multiple AWS accounts I'm referring to.</p>
<p>The next section sets two variables that will make it much easier for me to specify filesystem locations through the rest of the script.</p>
<pre><code class="lang-bash">tmp_file_location=/home/david/s3-emails/tmpemails/*
base_location=/home/david/s3-emails/emails/
</code></pre>
<p>Note how the value of the _tmp_file<em>location</em> variable ends with an asterisk. That's because I want to refer to the <em>files within</em> that directory, rather than the directory itself.</p>
<p>I'll create a new permanent directory within the .../emails/ hierarchy to make it easier for me to find messages later. The name of this new directory will be the current date. </p>
<pre><code class="lang-bash">today=$(date +<span class="hljs-string">"%m_%d_%Y"</span>)
[[ -d <span class="hljs-variable">${base_location}</span>/<span class="hljs-string">"<span class="hljs-variable">$today</span>"</span> ]] || mkdir <span class="hljs-variable">${base_location}</span>/<span class="hljs-string">"<span class="hljs-variable">$today</span>"</span>
</code></pre>
<p>I first create a new shell variable named <em>today</em> that will be populated by the output of the <em>date +"%m</em>%d<em>%Y"</em> command. <em>date</em> itself outputs the full date/timestamp, but what follows (<em>"%m</em>%d<em>%Y"</em>) edits that output to a simpler and more readable format.</p>
<p>I then test for the existence of a directly using that name - which would indicate that I've already received emails on that day and, therefore, there's no need to recreate the directory. If such a directory does <em>not</em> exist (||), then <em>mkdir</em> will create it for me. If you don't run this test, your command could return annoying error messages.</p>
<p>Since Amazon SES gives ugly and unreadable names to each of the messages it drops into my S3 bucket, I'll now dynamically rename them while, at the same time, moving them over to their new home (in the dated directory I just created). </p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> FILE <span class="hljs-keyword">in</span> <span class="hljs-variable">$tmp_file_location</span>
<span class="hljs-keyword">do</span>
   mv <span class="hljs-variable">$FILE</span> <span class="hljs-variable">${base_location}</span>/<span class="hljs-variable">${today}</span>/email$(rand)
<span class="hljs-keyword">done</span>
</code></pre>
<p>The <em>for...do...done</em> loop will read each of the files in the directory represented by the _$tmp_file<em>location</em> variable and then move it to the directory I just created (represented by the _$base<em>location</em> variable in addition to the current value of $<em>today</em>). </p>
<p>As part of the same operation, I'll give it its new name, the string "<em>email</em>" followed by a random number generated by the <em>rand</em> command. You may need to install a random number generator: that'll be <em>apt install rand</em> on Ubuntu. </p>
<p>An earlier version of the script created names differentiated by shorter, sequential numbers that were incremented using a <em>count=1...count=$((count+1))</em> logic within the <em>for</em> loop. That worked fine as long as I didn't happen to receive more than one batch of messages on the same day. If I did, then the new messages would overwrite older files in that day's directory. </p>
<p>I guess it's mathematically possible that my <em>rand</em> command could assign overlapping numbers to two files but, given that the default range <em>rand</em> uses is between 1 and 32,576, that's a risk I'm willing to take.</p>
<p>At this point, there should be files in the new directory with names like email3039, email25343, etc. for each of the new messages I was sent. </p>
<p>Running the <em>tree</em> command on my own system shows me that five messages were saved to my 02_27_2020 directory, and one more to 02_28_2020 (these files were generated using the older version of my script, so they're numbered sequentially). </p>
<p>There are currently no files in <em>tmpemails -</em> that's because the mv command moves files to their new location, leaving nothing behind.</p>
<pre><code class="lang-bash">$ tree
.
├── emails
│   ├── 02_27_2020
│   │   ├── email1
│   │   ├── email2
│   │   ├── email3
│   │   ├── email4
│   │   ├── email5
│   └── 02_28_2020
│       └── email1
└── tmpemails
</code></pre>
<p>The final section of the script opens each new message in my favorite desktop text editor (Gedit). It uses a similar <em>for...do...done</em> loop, this time reading the names of each file in the new directory (referenced using the "<em>today</em>" command) and then opening the file in Gedit. Note the asterisk I added to the end of the directory location.</p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> NEWFILE <span class="hljs-keyword">in</span> <span class="hljs-variable">${base_location}</span>/<span class="hljs-variable">${today}</span>/*
<span class="hljs-keyword">do</span>
   gedit <span class="hljs-variable">$NEWFILE</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p>There's still one more thing to do. If I don't clean out my S3 bucket, it'll download all the accumulated messages each time I run the script. That'll make it progressively harder to manage. </p>
<p>So, after successfully downloading my new messages, I run this short script to delete all the files in the bucket:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># Delete all existing emails </span>

aws s3 rm --recursive s3://bucket-name/ --profile myaccount
</code></pre>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Assisted script writing, with Pychronia Tools ]]>
                </title>
                <description>
                    <![CDATA[ By Pakal de Bonchamp Let software tooling check the consistency of your roleplay scripts for you! (French version of this article available here on Electro-GN) Issue Every  writer will confirm it to you: it is not easy to remain coherent when  you ar... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/assisted-script-writing-with-pychronia-tools/</link>
                <guid isPermaLink="false">66d4608d9f2bec37e2da0660</guid>
                
                    <category>
                        <![CDATA[ larp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ roleplay ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ writing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jul 2019 21:44:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/pychroniatools.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Pakal de Bonchamp</p>
<p><strong>Let software tooling check the consistency of your roleplay scripts for you!</strong></p>
<p><em>(French version of this article available</em> <a target="_blank" href="https://www.electro-gn.com/12495-lecriture-de-scenarios-assistee-avec-pychronia-tools"><em>here on Electro-GN</em></a><em>)</em></p>
<h3 id="heading-issue">Issue</h3>
<p>Every  writer will confirm it to you: it is not easy to remain coherent when  you are working, for several months, on a long story. It is even less so  when the story in question is lived by dozens of characters, each with  their own partial vision of the truth. And live action roleplay games  (LARPs) are especially exposed to this problem.</p>
<p>The  danger does not lie in the first draft of the writing: if he has all  his ideas well in place, the author does not risk much, except for a few  typos and interchanges of names. It is during subsequent modifications  (changes in the chronology of facts, additions of twists and turns,  etc.) that the information — disseminated and duplicated in the  documents of the various participants — will gradually become obsolete  and inconsistent. Eventually, even the character sheets of a common  “murder&amp;mystery party” will end up full of spatial, temporal,  lexical and structural improbabilities if they are not rigorously  checked and compared after each evolution of the scenario.</p>
<p>What can we do to prevent this, without spending a lifetime doing comparative proofreading? First, undoubtedly, <em>deduplicate texts common to several players</em>, which lead to tedious copy and paste (multiplying errors and artificially inflating the mass of the text). Then, <em>allow the author to review the interdependent sets of changes</em> he has successively applied to his scenario. Finally, give him <em>summaries of key information</em>, easier to review than verbose literary texts. And all this in <em>as automated a way as possible</em>,  because the number of documents to be managed can make the smallest  operation very time-consuming (and at risk of carelessness).</p>
<h3 id="heading-the-benefits-of-pychronia-tools-machinery">The benefits of Pychronia Tools machinery</h3>
<p>To benefit from these precious writing facilities during the creation of the <a target="_blank" href="https://chrysalis-game.com/fr/cms/soiree-mystere-chrysalis-mindstorm/">Chrysalis:Mindstorm</a> mystery party (French only), I set up a specific writing process,  involving simple text files (which contain the scenario), various  existing software tools, as well as a module “Pychronia Tools” developed  for the occasion.</p>
<p>Once  this machinery is in place, all you have to do is start it up  and — magic — a hundred PDF files appear one after the other in the  output folder: game master’s manual, complete and summarized character  sheets for players, documents to print on beautiful scrolls to serve as  clues in play, summary sheets for extras….</p>
<p>This tool is much more than a simple document generator: it includes an <strong>automatic check of the script consistency</strong>.  Now, if an index is mentioned in one place but not provided in another,  or if the same symbol has different values from one file to another,  the error is reported.  </p>
<p><strong>Extract of consistency errors returned by the program:</strong></p>
<blockquote>
<p>!!! ERROR IN hints registry for key golden_box_with_blood : [‘needed’] requires a provided hint<br>!!! ERROR IN symbols registry for key murder_date : [‘3 January 2018′,’15 March 2018']</p>
</blockquote>
<p>And  thanks to the automatically generated summary tables, it is possible to  check at a glance that each player is well informed of the facts  concerning him/her, that the distribution of key information is more or  less balanced, and that major events are correctly recorded in summary  sheets.</p>
<p><strong>Extract from the automatic summary of the “facts” of the scenario:</strong></p>
<p><em>Character  names are in italics when they have the “fact” in question only in  their complete character sheet, not in their summary sheet.</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*ytuGvYFYAwYxk2h9mf-Z_A.png" alt="Image" width="800" height="343" loading="lazy"></p>
<p>As  a result, the game master can rely quietly on his own (self-generated)  documents to set up and monitor the game: detailed scenario, evening  planning, checklist of materials and writings to be placed in the  premises, automatic summary of missions and special skills of each  player…</p>
<p>On  top of that, this machine can also send its game documents, by email,  to each player (e.g. character card and documents initially owned). This  avoids the drama that awaits each organizer: spoiling a participant by  sending him the wrong documents.</p>
<p>This  system obviously adds a certain complexity to the project, compared to  some common Word/LibreOffice files. But it provides invaluable support  in terms of scenario scalability and robustness, detecting  inconsistencies and automating daunting tasks. Personally, he saved me  more than once, when I switched the names of some characters in cards,  or forgot to warn some players of the new misdeeds they were supposed to  have committed in the past.</p>
<h3 id="heading-how-to-set-up-such-a-process">How to set up such a process?</h3>
<p><strong>Step 1:</strong> Move away from rich office files (docx, odt, pdf…) to a plain text  format, easily manipulable, where the formatting is explicitly indicated  by special characters. The documents <em>in game</em> with  high graphic and typographical needs (posters, scrolls, newspapers…)  can be left aside, in more usual office automation files: Word,  LibreOffice, InDesign…</p>
<p><strong>Example of plain text (restructuredtext format):</strong></p>
<pre><code class="lang-restructuredtext">Manuel du Maître de Jeu
############################

.. contents:: Table des Matières
    :depth: 2

Concept de la soirée mystère
================================

**Chrysalis:Mindstorm** est un huis clos entre `enquête 
&lt;https://fr.wikipedia.org/wiki/Enqu%C3%AAte&gt;`_ criminelle et conflit
géopolitique, où des agents secrets et des civils de divers pays se 
retrouvent face à un *redoutable* inspecteur de police, qui va les 
pousser dans leurs derniers retranchements.
</code></pre>
<p><strong>Rendering of this text once converted to PDF:</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*gzF6lDg3aFQ4cYiHogwlUw.png" alt="Image" width="800" height="281" loading="lazy"></p>
<p><strong>Step 2:</strong> Use a version manager for scenario files. This makes it possible to go  back in time at any time, to avoid horrifying accidental file  modifications, and to check the consistency of each of the changes made  (renaming a place, adding information for a group of players…).</p>
<p><strong>Viewing a change made to the rules of the game:</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*coZjpx5FCi1RERBRrOYIOQ.jpeg" alt="Image" width="800" height="421" loading="lazy"></p>
<p><strong>Step 3:</strong> Add  a small processing engine, to enrich the text with simple and practical  features: allow one file to include another, define reusable text  blocks, insert variables (e. g. the date of a crucial event, different  for each session of the mystery evening), display different information  according to the team to which the targeted player belongs….</p>
<p><strong>Step 4:</strong> Automate consistency checks. To do this, I created specific tags in the processing engine, which I then inserted as I wrote:<br> — The  {% fact %} tag is used to announce a fact (e. g. “so-and-so tried to  rob Loyd Georges”), and to indicate whether the player is the author or  just a witness.<br> — The {% hint %} tag allows you to request the existence of a physical clue (letter, object…) to give to the player.<br> — The  {% symbol %} tag ensures that a value is unique in all scenario files  (e. g. the exact time of a crime), while avoiding the use of “variables”  that obscure the text.</p>
<p><strong>Example of using special tags to enrich a scenario text:</strong></p>
<blockquote>
<p>To  the attention of {{agent_gamma_fake_name }} : the country of {% symbol  “Balberith” for “first_country_at_war” %} has entered the war, following  the plot led by agent Epsilon {% fact “agent_epsilon_triggered_war” as  author %}. You have a testimony signed by him and attesting to it. {%  hint “epsilon_signed_testimony_for_agent_gamma” is needed %}.</p>
</blockquote>
<p>As  we can see, these tags each have their own syntax, and can use other  features of the processing engine, such as variables (of which we have  an example with _{{agent_gamma_fake<em>name }}</em>).</p>
<p><strong>Step 5:</strong> Link  all this with scripts, which will automate the different steps of  creating game documents: gathering useful data (including player  photos), distributing scenario pages for each participant (global  context, personal context, game rules…), transforming them into PDF  format, and generating summary sheets for the game master.</p>
<p><strong>Some of the PDF documents generated, next to their plain text sources:</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*weQW6mnReW0hV9TNieakNw.png" alt="Image" width="783" height="554" loading="lazy"></p>
<h3 id="heading-the-future">The future</h3>
<p>All this is all well and good, but what about the rest of the role-playing  community? Can it benefit, more broadly, from the functionalities  offered by this scriptwriting support system?</p>
<p>The  answer is yes. However, as seen in the steps above, this tooling  requires a certain affinity with processes that are usually reserved for  computer development; an affinity that many GN authors do not have.</p>
<p>I  am therefore listening to authors tempted by the experience, in order  to see with them how they work, what formats and tools they are able to  use, and how this system could be generalized to suit their use. I could  then see to extract this code (which is currently strongly linked to  the structure of the Chrysalis game files), to make it more autonomous  and more easily deployable.</p>
<p>Note that software such as <a target="_blank" href="https://twinery.org/">Twine</a> already allows scenarios to be created in a fairly simple way, with a  mini-language to define variables and use logical operations. Pychronia  Tools machinery therefore only makes sense for the very high level of  integration it offers, with its automated consistency checks and  end-to-end generation scripts.</p>
<p>Interested in this scriptwriting support system? Feel free to contact me using the information on the <a target="_blank" href="https://chrysalis-game.com/fr/cms/contacts/">Chrysalis Website</a>.</p>
<h3 id="heading-appendix-details-for-computer-literate-people">Appendix : Details for computer literate people</h3>
<p>My machinery is based on the Python language and its document manipulation/creation ecosystem.</p>
<p>As  for the “plain text” format of the scenario, many “markup languages”  can be used for this purpose: restructuredtext, markdown, textile,  latex, or even html… I chose <a target="_blank" href="http://docutils.sourceforge.net/docs/user/rst/quickstart.html">restructuredtext</a> for  its clarity, versatility, and its advanced integration with the Python  language. To edit these text files, of course, Pycharm, Notepad++,  Geany, or a simple notebook can do the trick.</p>
<p>For  the version manager (or “VCS”), I chose Git and its excellent  TortoiseGit graphical interface (available under Windows only  unfortunately). Mercurial, Bazaar, DARCS, or others are just as  relevant. At a minimum, we can use the versioned file backup proposed by  Dropbox et al., even if it offers only a few features to visualize the  differences between several writing steps…</p>
<p><strong>Visualization, via GIT, of the sets of modifications made to the scenario:</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*GzYryvzHO22pB5UoanSL7Q.png" alt="Image" width="800" height="591" loading="lazy"></p>
<p>Regarding the template engine used to process text files (and for specific tags), finally, I have integrated the powerful <a target="_blank" href="http://jinja.pocoo.org/">Jinja2</a>,  which allows you to create variables and macros directly in the  templates. The data handled by this engine comes, in my case, from a  tree structure of Yaml files, but many other sources (python file, csv,  xml…) are very easily integrated.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
