<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vinicius Koji Enari</title>
    <description>The latest articles on DEV Community by Vinicius Koji Enari (@viniciusenari).</description>
    <link>https://dev.to/viniciusenari</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F841886%2Fc1167c51-6310-44fe-bc72-9f724a44ebc5.jpeg</url>
      <title>DEV Community: Vinicius Koji Enari</title>
      <link>https://dev.to/viniciusenari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viniciusenari"/>
    <language>en</language>
    <item>
      <title>Maintaining a Python Package with GitHub Actions</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Sat, 20 May 2023 16:43:04 +0000</pubDate>
      <link>https://dev.to/viniciusenari/maintaining-a-python-package-with-github-actions-2bfn</link>
      <guid>https://dev.to/viniciusenari/maintaining-a-python-package-with-github-actions-2bfn</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A hangman game created using Python and distributed as a Python package through &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt;. It utilizes GitHub actions to test and lint the code whenever there is a push or pull request to the main branch. Additionally, whenever a new release is created, the new version of the package is automatically deployed to PyPI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Wacky Wildcards&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;Package on PyPI: &lt;a href="https://pypi.org/project/hangman-package/"&gt;https://pypi.org/project/hangman-package/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;Demonstration of the game in GitHub Codespaces:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhl3mlq6va7jurugoip92.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhl3mlq6va7jurugoip92.gif" alt="Demonstration" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;The game can be installed using pip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install hangman-package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To play, you just need to import the package, create an instance of the Hangman class and call the &lt;code&gt;play&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hangman&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Hangman&lt;/span&gt;

&lt;span class="n"&gt;hangman&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hangman&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;hangman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can pass a list of words to the Hangman class to utilize custom words. By default, the words are a list of programming language names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;hangman&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Hangman&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;orange&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;melon&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;Github repository: &lt;a href="https://github.com/viniciusenari/hangman-package"&gt;https://github.com/viniciusenari/hangman-package&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;MIT License&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;I wanted to build something with GitHub Actions to learn the basics of how it works. Since I am most comfortable working with Python, I looked for workflow templates available for Python and found two that could be helpful in maintaining a Python package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fng385ksaf8fs82h1tu1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fng385ksaf8fs82h1tu1f.png" alt="Workflow templates" width="613" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wasn't sure what to build for a package, so I ended up going with the first idea that came to mind—a hangman game. While it's not an original idea, it served its purpose of allowing me to utilize and learn more about GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;I utilized two GitHub actions workflows, one for &lt;a href="https://github.com/viniciusenari/hangman-package/blob/main/.github/workflows/python-package.yml"&gt;testing and linting&lt;/a&gt; and another one for &lt;a href="https://github.com/viniciusenari/hangman-package/blob/main/.github/workflows/python-publish.yml"&gt;publishing my package&lt;/a&gt;. The workflow for testing and linting is triggered whenever there is a push or a pull request to the main branch. It utilizes the pytest library to run tests and flake8 to lint, ensuring that the code follows conventions and works correctly. It runs against multiple Python versions.&lt;/p&gt;

&lt;p&gt;The workflow for publishing is triggered when a new release is created. It utilizes the build command to create a distributable version of the package and publishes it to PyPI.&lt;/p&gt;

&lt;p&gt;Overall, I learned how to utilize these workflows to automate important tasks in my development process, making it easier to maintain code quality and distribute my package.&lt;/p&gt;

&lt;p&gt;In addition to GitHub Actions, I also utilized GitHub Codespaces to experiment with my package. Without needing to rely on my local development environment, I was able to test the package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Hangman_(game)"&gt;https://en.wikipedia.org/wiki/Hangman_(game)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubhack23</category>
      <category>python</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What is the best way to evaluate someone's technical skills?</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Wed, 15 Feb 2023 17:01:21 +0000</pubDate>
      <link>https://dev.to/viniciusenari/what-is-the-best-way-to-evaluate-someones-technical-skills-4b7h</link>
      <guid>https://dev.to/viniciusenari/what-is-the-best-way-to-evaluate-someones-technical-skills-4b7h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction 👋🏼
&lt;/h2&gt;

&lt;p&gt;As someone entering the job market and going through multiple hiring processes recently, I started questioning the various methods companies use to evaluate candidates' technical skills. Why do companies use different methods to interview, and what advantages and disadvantages each approach has? I'm writing this article to express my views on each method as an interviewee and learn more insights from you in the comments section.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal 🎯
&lt;/h2&gt;

&lt;p&gt;Before discussing the different interview methods, I want to discuss the goal of the interview process.&lt;br&gt;
I believe there are four criteria that companies want to evaluate the candidates on, they are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Technical Knowledge&lt;/li&gt;
&lt;li&gt;Problem-solving skills&lt;/li&gt;
&lt;li&gt;Communication&lt;/li&gt;
&lt;li&gt;Cultural fit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What criteria are more significant will depend on the company and the role. Different interview methods assess some criteria better than others, so you must consider what you want to evaluate before choosing a way to interview candidates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Different Methods to Evaluate Technical Skills
&lt;/h2&gt;

&lt;p&gt;In the following sections, I will discuss some of the types of interviews I encountered and how each assesses the different interview types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Online Coding Assessments 👨‍💻
&lt;/h3&gt;

&lt;p&gt;Online coding assessments typically involve solving a set of coding problems within a limited time frame using a coding platform. These problems usually focus on the use of data structures and algorithms and can be found on websites such as &lt;a href="https://leetcode.com/" rel="noopener noreferrer"&gt;Leetcode&lt;/a&gt; and &lt;a href="https://www.hackerrank.com/" rel="noopener noreferrer"&gt;HackerRank&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Technical Knowledge&lt;/td&gt;
&lt;td&gt;Online coding assessments test a candidate's knowledge of programming, data structures, and algorithms. Still, they may not necessarily assess the specific technical skills required for the role.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Problem-solving skills&lt;/td&gt;
&lt;td&gt;While these assessments can provide insight into a candidate's problem-solving abilities, it is possible for a candidate to perform well simply by recognizing and implementing a solution that they have seen before, rather than demonstrating their ability to solve new problems.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;Online coding assessments do not directly evaluate a candidate's communication skills.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cultural fit&lt;/td&gt;
&lt;td&gt;Online coding assessments do not assess a candidate's cultural fit with the company.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Coding Assessment with Interviewer 👨‍💻👨‍💼
&lt;/h3&gt;

&lt;p&gt;Also known as "pair programming," coding assessment with an interviewer are similar to the coding assessments discussed before. Still, the difference is that there will be an interviewer present. This format allows the interviewer to observe the candidate's thought process and approach to problem-solving, as well as their coding skills and technical knowledge. The interviewer may ask questions and guide the candidate through the coding problems.&lt;/p&gt;

&lt;p&gt;It provides a more in-depth evaluation of a candidate's problem-solving skills than online coding assessments since the interviewer can assess the candidate's thought process and understanding of the problem.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Technical Knowledge&lt;/td&gt;
&lt;td&gt;Similar to online coding assessments, coding assessments with an interviewer test a candidate's knowledge of programming, data structures, and algorithms but may not directly assess the technical skills required for the role.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Problem-solving skills&lt;/td&gt;
&lt;td&gt;It provides a more in-depth evaluation of a candidate's problem-solving skills than online coding assessments since the interviewer can assess the candidate's thought process and understanding of the problem.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;While coding assessments with an interviewer provide insight into a candidate's ability to communicate their solution, it is only one aspect of communication. It may not fully evaluate their overall communication skills.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cultural fit&lt;/td&gt;
&lt;td&gt;It can evaluate cultural fit depending on the interviewer's approach, who may ask questions to assess the candidate's fit with the company before or after the coding portion of the assessment.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  System Design Interviews 🌐🛠️
&lt;/h3&gt;

&lt;p&gt;While I have only encountered system design interviews twice, I am familiar with their general format. During this interview, the interviewer asks the candidate to create a high-level architecture for a system, often using examples such as YouTube, Twitter, or Spotify.&lt;/p&gt;

&lt;p&gt;It is important to note that candidates are not expected to replicate the exact architecture of these products. Instead, they are evaluated on their understanding of system architecture and ability to weigh different trade-offs between various approaches. These interviews aim to test a candidate's problem-solving skills, ability to create efficient solutions, and communication skills in explaining their designs to the interviewer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Technical Knowledge&lt;/td&gt;
&lt;td&gt;A system design interview assesses a candidate's technical knowledge in designing complex real-world software architecture, which encompasses multiple subjects, including data storage, scalability, and security.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Problem-solving skills&lt;/td&gt;
&lt;td&gt;It evaluates a candidate's problem-solving skills by assessing their ability to design solutions for large-scale, real-world problems. Through them, an interviewer can assess how the candidate breaks down the problem, consider different constraints and trade-offs, and make architectural decisions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;It tests candidates' ability to effectively communicate their design choices, explain trade-offs, and justify their decisions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cultural fit&lt;/td&gt;
&lt;td&gt;They do not directly assess cultural fit, but the interviewer may include questions to evaluate it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Take Home Project 🏠
&lt;/h3&gt;

&lt;p&gt;This type of interview involves providing the candidate with a prompt to develop a project that aligns with the role's requirements. The project may vary depending on the position, such as a webpage, an API, or a mobile app. It should be developed using the required programming languages and frameworks. Typically, candidates are given several days to complete the project and submit it for evaluation.&lt;/p&gt;

&lt;p&gt;Take-home projects allow candidates to demonstrate their skills and problem-solving abilities in a more realistic environment and at their own pace. They also enable the candidate to showcase their creativity and personal style in their approach to solving the problem.&lt;/p&gt;

&lt;p&gt;However, there are some downsides to taking home projects. The interviewer cannot assess the candidate's ability to think on their feet or how they or work under pressure. The candidate may receive help from outside sources, such as friends or online forums, which can compromise the integrity of the evaluation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Technical Knowledge&lt;/td&gt;
&lt;td&gt;Evaluates the candidate's knowledge of the programming language and frameworks used in the role, as well as general software development concepts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Problem-solving skills&lt;/td&gt;
&lt;td&gt;Assesses the candidate's ability to solve problems and design software based on the prompt. This includes breaking down the problem, identifying constraints and trade-offs, and creating a solution that meets the requirements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;Focuses on how well candidates document their code and explain their design choices. It may not evaluate verbal communication directly, but the quality of written communication can indicate the ability to communicate effectively.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cultural fit&lt;/td&gt;
&lt;td&gt;Does not directly assess cultural fit. Still, the project prompt may reflect the team's values and provide insight into the candidate's approach to work.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Resume/Portfolio interview 📄
&lt;/h3&gt;

&lt;p&gt;Resume/portfolio interviews consist of the interviewer conversing with the candidate, focusing on their previous roles, technologies they have worked with, and projects they have been involved in. The interviewer will usually review the candidate's resume and portfolio and ask questions related to their prior experiences or ask some questions prepared before the interview to obtain more specific insights about the candidate.&lt;/p&gt;

&lt;p&gt;In addition to assessing the candidate's technical abilities, these interviews may also evaluate their problem-solving approach. The interviewer may ask the candidate to share any experience where they encountered a challenge and ask them to explain how they dealt with it. Additionally, the interviewer may evaluate the candidate's teamwork approach and how they collaborated with their team members in previous projects by asking questions about it.&lt;/p&gt;

&lt;p&gt;Resume/portfolio conversations offer a chance for candidates to have a more personalized discussion about their technical and soft skills. However, it's essential to remember that this interview format does not allow an in-depth evaluation of a candidate's technical skills.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Technical Knowledge&lt;/td&gt;
&lt;td&gt;By having the candidate talk about his past experiences, the interviewer understands what programming languages, frameworks, and tools the candidate are familiar with.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Problem-solving skills&lt;/td&gt;
&lt;td&gt;The interviewer may ask about specific problems the candidate has faced and how they solved them, providing them with some understanding of the candidate's problem-solving skills.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;The interviewer may ask about how the candidate communicates with team members, how they document their work, and how they present ideas to other people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cultural fit&lt;/td&gt;
&lt;td&gt;Through questions, the interviewer may assess how well the candidate's work style and values align with the team's culture and the company's mission. The interviewer may ask about how the candidate collaborates with others, how they handle conflict and their approach to problem-solving in a team setting.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion 🏁
&lt;/h2&gt;

&lt;p&gt;This article discussed various methods employed to interview and evaluate candidates for technical positions. While companies developed these techniques to ensure fairness and accuracy in assessing candidates, they all have certain limitations. Companies often combine different interviewing methods to achieve a more just and accurate candidate assessment. There may be more effective evaluation methods that are not yet widely adopted. Still, it is essential to acknowledge that companies have limited resources for the hiring process. The current techniques may be the most practical given the company's resources. Still, it is always worth considering whether there are more efficient ways to conduct interviews.&lt;/p&gt;

</description>
      <category>react</category>
      <category>fullstack</category>
      <category>freelance</category>
      <category>uiux</category>
    </item>
    <item>
      <title>Automating Content Creation with Python: Uploading a Video to Youtube</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Wed, 25 Jan 2023 18:52:48 +0000</pubDate>
      <link>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-4-26jn</link>
      <guid>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-4-26jn</guid>
      <description>&lt;p&gt;This is the last part of the series on building a YouTube content creation bot that utilizes Twitch clips. If you have yet to see it, be sure to start with the &lt;a href="https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-1-1h6m"&gt;first part&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading the Video to Youtube
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Generating Title, Description, Tags and Thumbnail
&lt;/h3&gt;

&lt;p&gt;We have completed the video, but before uploading it, several other fields must be filled out when uploading it to YouTube, such as the title, description, tags, and thumbnail. But, these can also be created from the information we obtained through the Twitch API.&lt;/p&gt;

&lt;h4&gt;
  
  
  Title
&lt;/h4&gt;

&lt;p&gt;A simple title can be created by including the name of the game the compilation of clips is from, and the number of clips included. Additionally, we can specify that these are the top most-watched clips of the week.&lt;/p&gt;

&lt;p&gt;Example: "Top &lt;code&gt;&amp;lt;quantity&amp;gt;&lt;/code&gt; Most-Watched &lt;code&gt;&amp;lt;game_name&amp;gt;&lt;/code&gt; Clips of the Week"&lt;/p&gt;

&lt;h4&gt;
  
  
  Description
&lt;/h4&gt;

&lt;p&gt;In the description, I want to include the time range when the clips were created, the languages the clips were taken from and provide a list of the clips and their URLs. Additionally, it is possible to add timestamps for each clip by utilizing the individual duration of each clip to calculate the values.&lt;/p&gt;

&lt;p&gt;Here is an example of a description created from the clips' information:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cxmjqz2j7djsl6p00au.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cxmjqz2j7djsl6p00au.png" alt="Example description" width="637" height="306"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Tags
&lt;/h4&gt;

&lt;p&gt;Tags are keywords that are related to the video, they are not visible to the viewers, but they play an essential role in making the video more discoverable. As all the videos will be compilations of gaming highlights from Twitch, many general tags such as "twitch" and "gaming" can be added to each video. Additionally, more specific tags can be added, such as the names of the broadcasters and the game's name, using the previously collected information. In the program, I initialize a set, so there are no repetitions, with the available tags, and I loop through the clip's info adding the specific information from each clip. After that, I convert the set into an array.&lt;/p&gt;
&lt;h4&gt;
  
  
  Thumbnail
&lt;/h4&gt;

&lt;p&gt;The thumbnail is an image that represents the video and can be created in various ways. The template I used consists of displaying the words "Top &lt;code&gt;&amp;lt;number_of_clips&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;game_name&amp;gt;&lt;/code&gt; Twitch Clips" on it and featuring a grid of individual clip thumbnails as the background. The number of thumbnails in the grid is determined by the &lt;code&gt;floor(sqrt(number_of_clips))&lt;/code&gt; function. I used abbreviations for some games with longer names to save space and make the thumbnail more appealing. For instance, instead of displaying "League of Legends" in the thumbnail, it will show "LOL."&lt;/p&gt;

&lt;p&gt;Here is an example of the thumbnail:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgg55eozfkbmf94qhpz07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgg55eozfkbmf94qhpz07.png" alt="Example Thumbnail" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to check the specific code for creating the title, description, tags and thumbnail here is the &lt;a href="https://github.com/viniciusenari/twitch-highlights-bot/blob/main/project/video_content.py" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Uploading with the Youtube API
&lt;/h3&gt;

&lt;p&gt;Now that I have the video and all the relevant metadata, I will upload the video to Youtube. For this, I utilized the Youtube API. I will not go into detail on how to do it since they already provide an excellent &lt;a href="https://developers.google.com/youtube/v3/quickstart/python" rel="noopener noreferrer"&gt;guide for how to start using the Youtube API with Python&lt;/a&gt;. You need to download the Python libraries to authenticate with Google, set up a project on Google Cloud and then you will be able to download the API credentials as JSON file.&lt;/p&gt;

&lt;p&gt;Google provides code snippets for many actions that you may want to make using the API in this website: &lt;a href="https://developers.google.com/youtube/v3/code_samples/code_snippets" rel="noopener noreferrer"&gt;developers.google.com/youtube/v3/code_samples/code_snippets&lt;/a&gt;. I utilized the code they already provided under the "videos" resource and "insert" method. The only problem is that when the video takes long to upload, there will be an error. To fix that, I found you need to put in place a resumable upload that will resume the video upload if it times out. Here is a &lt;a href="https://github.com/youtube/api-samples/blob/master/python/upload_video.py" rel="noopener noreferrer"&gt;sample code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Besides the video, I also need to upload the category ID (for gaming, it's 20) and the privacy status (I set it to "Public" so the video will be discoverable immediately after upload). After the video is uploaded, I'll use the returned video ID to upload the thumbnail in a separate request.&lt;/p&gt;

&lt;p&gt;This is a snippet of the code I used to upload videos to YouTube&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Uploading thumbnail...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thumbnails&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;videoId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;media_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_video&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video_content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;video_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;video_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;video_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;categoryId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;video_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;

        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;privacyStatus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;video_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;privacy_status&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;insert_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;media_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaFileUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunksize&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resumable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;video_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resumable_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;insert_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/youtube/thumbnail.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Takeaways
&lt;/h1&gt;

&lt;p&gt;With this, I end the series about my project that automates collecting Twitch clips, creating a video compilation, and uploading it to YouTube. Overall, this project shows the power of automation and APIs to create and distribute content efficiently and effectively. Despite this, the project still has several limitations that prevent it from being on par with channels managed by humans. Here are some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When it gets clips using the Twitch API for a specific category, many clips that are returned are from people who were streaming under that category. Still, the clip is unrelated to that category. For example, a streamer playing League of Legends might only show their camera between games. An event unrelated to the game might occur, resulting in a famous clip under the "League of Legends" category.&lt;/li&gt;
&lt;li&gt;Many clips may need knowledge of the streamer's context to be understood, making them less suitable to include in compilations that an audience may view without that context.&lt;/li&gt;
&lt;li&gt;Many YouTube channels that post similar content edit the clips to make them more entertaining. They do this by zooming in on important moments, highlighting areas of interest with red circles, adding sound effects, and many other things.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoyed reading this. If you have any questions, please leave them in the comments. If you want to check the source code or look at the Youtube channel I created to test it, here are the links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Repository: &lt;a href="https://github.com/viniciusenari/twitch-highlights-bot" rel="noopener noreferrer"&gt;github.com/viniciusenari/twitch-highlights-bot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube Channel: &lt;a href="https://www.youtube.com/@mostwatchedtwitchclips" rel="noopener noreferrer"&gt;youtube.com/@mostwatchedtwitchclips&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Automating Content Creation with Python: Editing with MoviePy</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Wed, 25 Jan 2023 18:52:16 +0000</pubDate>
      <link>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-3-pk9</link>
      <guid>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-3-pk9</guid>
      <description>&lt;p&gt;This is a continuation of a multi-part series on building a YouTube content creation bot that utilizes Twitch clips. If you have yet to see it, be sure to start with the &lt;a href="https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-1-1h6m"&gt;first part&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling the Clips into a Video
&lt;/h2&gt;

&lt;p&gt;With the clips downloaded, the next step is to merge them into a single video. I used the &lt;a href="https://zulko.github.io/moviepy/" rel="noopener noreferrer"&gt;MoviePy&lt;/a&gt; Python library. The editing process involves three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create overlays for each clip with the broadcaster's title and name.&lt;/li&gt;
&lt;li&gt;Superimpose the overlay on the top of the corresponding clip.&lt;/li&gt;
&lt;li&gt;Concatenate all the clips together to produce a single video.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way, you will have a final video with the title, and broadcaster name on every clip and all clips merged into one video.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Overlays
&lt;/h3&gt;

&lt;p&gt;I used the Python library &lt;a href="https://pillow.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;Pillow&lt;/a&gt; to create the overlays for the clips. Although MoviePy also allows adding text to clips, it relies on the ImageMagick library, which requires manual installation. I preferred Pillow as it is easier to install, although it may be less efficient.&lt;/p&gt;

&lt;p&gt;I made a transparent image of 1980x1080 resolution to create an overlay with Pillow. I added the title and broadcaster's name at the bottom left corner. If the title is too long, it will be truncated, and a "..." will be added to the end. After an overlay is created, it is saved in a dedicated directory.&lt;/p&gt;

&lt;p&gt;Here's an example of the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_overlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;
    &lt;span class="n"&gt;broadcaster_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcaster_name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="c1"&gt;# Create a 1980x1080 transparent image
&lt;/span&gt;    &lt;span class="n"&gt;overlay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RGBA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Load fonts and create font objects
&lt;/span&gt;    &lt;span class="n"&gt;fnt_clip_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_clip_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fnt_streamer_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_broadcaster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;930&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fnt_clip_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stroke_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stroke_fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;broadcaster_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fnt_streamer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stroke_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stroke_fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/overlays&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/overlays&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/overlays/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Superimposing the Overlay
&lt;/h3&gt;

&lt;p&gt;Using the MoviePy library, I superimposed the overlays on the clips. The process is straightforward: I created a MoviePy &lt;a href="https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=videofileclip#moviepy.video.io.VideoFileClip.VideoFileClip" rel="noopener noreferrer"&gt;&lt;code&gt;VideoFileClip&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=imageclip#moviepy.video.VideoClip.ImageClip" rel="noopener noreferrer"&gt;&lt;code&gt;ImageClip&lt;/code&gt;&lt;/a&gt;, for the clip and overlay. Then, I combined them into a &lt;a href="https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=compositevideoclip#moviepy.video.compositing.CompositeVideoClip.CompositeVideoClip" rel="noopener noreferrer"&gt;&lt;code&gt;CompositeVideoClip&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;moviepy.editor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VideoFileClip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageClip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CompositeVideoClip&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_video&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;clip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VideoFileClip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_resolution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1980&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clip_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;img_clip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ImageClip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/overlays/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompositeVideoClip&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img_clip&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resulting in the following:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmukntkrepny3fv0oxjuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmukntkrepny3fv0oxjuc.png" alt="Example clip with overlay" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Concatenating the Clips
&lt;/h3&gt;

&lt;p&gt;Now that all individual clips have an overlay, the next step is to concatenate them. The MoviePy library offers the &lt;a href="https://github.com/Zulko/moviepy/blob/master/moviepy/video/compositing/concatenate.py" rel="noopener noreferrer"&gt;&lt;code&gt;concatenate_videoclips&lt;/code&gt;&lt;/a&gt; function, which, when used on an array of MoviePy clip objects, returns a single clip composed of all the other clips played one after another. Using the &lt;code&gt;method='compose'&lt;/code&gt; parameter ensures that if the clips have different resolutions, MoviePy will resize them to result in a seamless final product.&lt;/p&gt;

&lt;p&gt;The final step is to convert the clip object into an actual video file. The &lt;a href="https://zulko.github.io/moviepy/ref/VideoClip/VideoClip.html?highlight=write_videofile#moviepy.video.io.VideoFileClip.VideoFileClip.write_videofile" rel="noopener noreferrer"&gt;&lt;code&gt;write_videofile&lt;/code&gt;&lt;/a&gt; method is used to do that. It takes various parameters such as fps, codec, threads, preset, and bitrate. The values for these parameters have been chosen after experimenting to find the best results. It's worth noting that what worked for me may not be the best for others, so one should experiment with different parameters to find the best results for their specific use case.&lt;/p&gt;

&lt;p&gt;Here is the code for concatenating the clips. I create an overlay for each clip, superimpose it to the clip, and append it to an array. It then concatenates all clips on the array and writes a video file from the concatenated clip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;moviepy.editor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;concatenate_videoclips&lt;/span&gt;

&lt;span class="n"&gt;render_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fps&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;codec&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mpeg4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;threads&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ultrafast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bitrate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20000k&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_video_compilation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;clip&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clips&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_overlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_video&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;concatenate_videoclips&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clips&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;compose&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/youtube&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/youtube&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_videofile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/youtube/video.mp4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fps&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;codec&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;threads&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;preset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;preset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;bitrate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bitrate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In the article, I covered how to use MoviePy to edit videos and create a compilation from before downloaded clips. In the &lt;a href="https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-4-26jn"&gt;next part&lt;/a&gt;, I will cover how you can use the Youtube API to upload videos with a custom title, description, thumbnail, and tags. Remember to leave a comment if you have any questions.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Automating Content Creation with Python: Obtaining Clips from Twitch</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Wed, 25 Jan 2023 18:51:49 +0000</pubDate>
      <link>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-2-2n1b</link>
      <guid>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-2-2n1b</guid>
      <description>&lt;p&gt;This is a continuation of a multi-part series on building a YouTube content creation bot that utilizes Twitch clips. If you have yet to see it, be sure to start with the &lt;a href="https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-1-1h6m"&gt;first part&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieving and Downloading the Most Viewed Clips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using the Twitch API
&lt;/h3&gt;

&lt;p&gt;As before mentioned, I used the Twitch API to retrieve the most viewed clips. To access the Twitch API, you must first have a Twitch account and register an application on &lt;a href="https://dev.twitch.tv/console" rel="noopener noreferrer"&gt;dev.twitch.tv/console&lt;/a&gt;. You will get a &lt;code&gt;Client ID&lt;/code&gt; and a &lt;code&gt;Client Secret&lt;/code&gt;. Once you have these credentials, you can make a post request to &lt;a href="https://id.twitch.tv/oauth2/token" rel="noopener noreferrer"&gt;https://id.twitch.tv/oauth2/token&lt;/a&gt; to receive a token in response. With this token, you can make requests to the Twitch API endpoints.&lt;/p&gt;

&lt;p&gt;You can refer to this more &lt;a href="https://dev.twitch.tv/docs/api/get-started/" rel="noopener noreferrer"&gt;in-depth guide&lt;/a&gt; on how to get started using the Twitch API.&lt;/p&gt;

&lt;p&gt;In Python, you can use the &lt;code&gt;requests&lt;/code&gt; library to request to authenticate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwitchAPI&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;client_secret=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;grant_type=client_credentials&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://id.twitch.tv/oauth2/token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; 
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;bearer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bearer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Client-Id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieving Clips
&lt;/h3&gt;

&lt;p&gt;The Twitch API offers an &lt;a href="https://dev.twitch.tv/docs/api/reference/#get-clips" rel="noopener noreferrer"&gt;endpoint&lt;/a&gt; that enables developers to retrieve information about specific clips. This endpoint allows developers to query for clips from a broadcaster or game or by using particular clip IDs. In this application, the request query parameters that were utilized include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;game_id&lt;/code&gt; to query for clips from a specific game.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;started_at&lt;/code&gt; and &lt;code&gt;ended_at&lt;/code&gt; to select a particular period in which the clips were created.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first&lt;/code&gt; to specify the number of clips that must be returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use the &lt;a href="https://dev.twitch.tv/docs/api/reference/#get-top-games" rel="noopener noreferrer"&gt;Get Top Games endpoint&lt;/a&gt; from the Twitch API to get information about the most watched games on Twitch at the moment, which includes the games' IDs. If you want to know the ID of a game that is not on the top games being watched, you can use the &lt;a href="https://dev.twitch.tv/docs/api/reference/#get-games" rel="noopener noreferrer"&gt;Get Games endpoint&lt;/a&gt;. For my project, getting the IDs of the top games is enough since they are the games that are more likely to attract an audience to a YouTube channel. You can store the game IDs in a Python dictionary and then use this data to query for the top clips.&lt;/p&gt;

&lt;p&gt;Below is an example of how you can store the game's IDs in a dictionary&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;project.twitch_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TwitchAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;project.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TwitchAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_top_games&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.twitch.tv/helix/games/top&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;first&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;games_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;games_id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;games_id&lt;/span&gt;

&lt;span class="n"&gt;games_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_top_games&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step before obtaining the clips is to add an option to filter them by language, as the API does not allow queries for clips in a particular language. This way, it will be easier to create compilations of clips that target more specific audiences, as opposed to having clips in different languages.&lt;/p&gt;

&lt;p&gt;In summary, the process involves requesting clips, filtering them based on desired languages, saving relevant information, and continuing until it has the desired number of clips. The information of each clip in a &lt;code&gt;ClipContent&lt;/code&gt; object&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClipContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;broadcaster_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;broadcaster_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;game_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;creator_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcaster_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;broadcaster_id&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcaster_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;broadcaster_name&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;game_id&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnail_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;thumbnail_url&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to check how I implemented it, you can find the code on my GitHub repository at this &lt;a href="https://github.com/viniciusenari/twitch-highlights-bot/blob/main/project/clips.py" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Downloading the Clips
&lt;/h3&gt;

&lt;p&gt;Having obtained information about the clips, one can proceed to download them. There is no direct method to download clips via the API. Still, it is possible to do so using its information. A technique that works most of the time involves utilizing the thumbnail URL of each clip.&lt;/p&gt;

&lt;p&gt;A clip thumbnail URL looks like this:&lt;br&gt;
&lt;code&gt;https://clips-media-assets2.twitch.tv/jTk1-Xmig5ji1Dll05ivnA/AT-cm%7CjTk1-Xmig5ji1Dll05ivnA-preview-260x147.jpg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Removing the "-preview-260x147.jpg" from the URL and adding a ".mp4" extension can generate a valid address for downloading the clip. This method may not work for older clips, but it has worked for recent clips in my testing.&lt;/p&gt;

&lt;p&gt;Here is an example of how you can download the clip from the thumbnail URL using Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-preview&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clip_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.mp4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clip_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;binary/octet-stream&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/clips&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files/clips&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Failed to download clip from thumb: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;In this part of the series, I covered how to use the Twitch API to get information on the most popular clips and download them. In the &lt;a href="https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-3-pk9"&gt;next part&lt;/a&gt;, I will cover how I use the MoviePy library to edit videos. Please comment or reach out if you have any questions.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Automating Content Creation with Python: A Guide to Building a Twitch Highlights Bot</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Wed, 25 Jan 2023 18:51:17 +0000</pubDate>
      <link>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-1-1h6m</link>
      <guid>https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-1-1h6m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Motivation
&lt;/h3&gt;

&lt;p&gt;A few months ago, I came across a &lt;a href="https://dev.to/joaomaranhao/how-i-used-python-to-automate-a-youtube-channel-ph0"&gt;post&lt;/a&gt; by &lt;a href="https://dev.to/joaomaranhao"&gt;@joaomaranhao&lt;/a&gt; here on dev.to. He described how he used Python to automate a Youtube channel. His project inspired me to start my project to automate a YouTube channel, focusing on different kinds of content and utilizing other methods to obtain and edit videos.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Project
&lt;/h3&gt;

&lt;p&gt;You probably know what Twitch is, but in case you don't know, Twitch is a live streaming platform that is used for streaming games. Still, it includes various content, such as music, art, talk shows, and even &lt;a href="https://www.twitch.tv/directory/game/Software%20and%20Game%20Development"&gt;software development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the features available on Twitch is the ability for viewers to make video clips from live streams. By clicking on a button on the bottom right corner of the video player, users can create a video clip by selecting a 5 to 60 seconds segment of the last couple minutes of a live stream. Then they can share it with other people. Video clips are often created so people can share funny and unique moments with others.&lt;/p&gt;

&lt;p&gt;Many Youtube channels post compilations of clips from &lt;a href="https://www.twitch.tv"&gt;Twitch&lt;/a&gt;. You can watch the video below as an example.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Wylg3qd2P4U"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;My project does that. It gets the most watched clips of the week for a particular game, combines them into a video compilation, and then uploads the final product to Youtube with auto-generated titles, descriptions, tags, and thumbnails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technologies Used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt; - The primary programming language for the bot as it provides a lot of libraries that can be used to interact with different platforms and APIs, process data, and build efficient scripts. In this project, I used version 3.11, but some earlier versions should work.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.twitch.tv/docs/api/"&gt;Twitch API&lt;/a&gt; - The Twitch API allows developers to access and interact with the Twitch platform. The bot uses the Twitch API to search for and retrieve the most watched clips from Twitch.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zulko.github.io/moviepy/"&gt;MoviePy&lt;/a&gt; - A Python library used for video editing. The bot is using this library to create the video compilation by merging the clips obtained from Twitch.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pillow.readthedocs.io/en/stable/"&gt;Pillow&lt;/a&gt; - A Python library that allows for image processing. It is being used to handle image processing tasks, such as creating thumbnails and title overlays.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/youtube"&gt;Youtube API&lt;/a&gt;/ &lt;a href="https://cloud.google.com/"&gt;Google Cloud&lt;/a&gt; - The YouTube API is a specific API that allows developers to interact with YouTube. Google Cloud refers to the broader suite of cloud computing services offered by Google. The bot is using the YouTube API to upload the final video compilation to YouTube. One must set up a Google Cloud project to use the Youtube API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Check the other parts
&lt;/h3&gt;

&lt;p&gt;This article will be divided into several sections to provide a comprehensive guide to how I developed this bot. You can check the second part, which will delve into how to use the Twitch API to get information on the most popular clips and download them &lt;a href="https://dev.to/viniciusenari/automating-content-creation-with-python-a-guide-to-building-a-twitch-highlights-bot-part-2-2n1b"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Github Repository and Youtube Channel
&lt;/h3&gt;

&lt;p&gt;If you want to check the source code or look at the Youtube channel I created to test it, here are the links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Repository: &lt;a href="https://github.com/viniciusenari/twitch-highlights-bot"&gt;github.com/viniciusenari/twitch-highlights-bot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube Channel: &lt;a href="https://www.youtube.com/@mostwatchedtwitchclips"&gt;youtube.com/@mostwatchedtwitchclips&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I update my Twitter profile automatically using Python.</title>
      <dc:creator>Vinicius Koji Enari</dc:creator>
      <pubDate>Fri, 12 Aug 2022 18:25:28 +0000</pubDate>
      <link>https://dev.to/viniciusenari/how-i-update-my-twitter-profile-automatically-using-python-5d57</link>
      <guid>https://dev.to/viniciusenari/how-i-update-my-twitter-profile-automatically-using-python-5d57</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I find automating tasks one of the coolest things about programming. Lately, I've been thinking about ways to utilize APIs to automatize some things, and I came up with the idea of making my Twitter profile banner image change automatically from time to time. It would be like having access to a Twitter feature that no one has, the capability of having a slideshow as a banner instead of a single image.&lt;/p&gt;

&lt;p&gt;I will use &lt;a href="https://apod.nasa.gov/apod/astropix.html" rel="noopener noreferrer"&gt;NASA's Astronomy Picture of the Day&lt;/a&gt; API to obtain the images I will use. This API provides a different image daily, making it appropriate for this specific purpose. Also, by using this API, I know that my Twitter banner will repeat no pictures. You can use images from other APIs or your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Obtaining access to the APIs
&lt;/h2&gt;

&lt;p&gt;Before starting, you'll need access to the Twitter API and the NASA API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Twitter API
&lt;/h3&gt;

&lt;p&gt;To access the Twitter API, you must go to &lt;a href="https://developer.twitter.com/" rel="noopener noreferrer"&gt;Twitter's developers portal&lt;/a&gt;. Make sure to log into the portal with the same account you want your banner to change automatically. I'll not cover step-by-step what you need to do to obtain access to the Twitter API, but here is &lt;a href="https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api" rel="noopener noreferrer"&gt;guide&lt;/a&gt;. You'll need to apply for Elevated Access to the API (step four) because the request to update one's profile is part of the v1.1 endpoints. It is not difficult to obtain elevated access. You'll have to explain how you will use the API so they know you are following their terms of conduct.&lt;/p&gt;

&lt;h3&gt;
  
  
  NASA's API
&lt;/h3&gt;

&lt;p&gt;Next, you will need access to NASA Open APIs. NASA's APIs are free to use. It is possible to utilize the API without your API Key by utilizing "DEMO_KEY" as the key.&lt;br&gt;
For example:&lt;br&gt;
&lt;a href="https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY" rel="noopener noreferrer"&gt;https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only limitation is that with the demo key, you can only make 30 requests per hour and 50 requests per day per IP address, while with your API key, you can make up to 1000 requests per hour. The demo key provides enough requests for this project to work since I'm only making one request per day. But I decided to utilize my API key anyway because I would make multiple requests per hour while writing and testing the code. Also, it is easy to obtain.&lt;/p&gt;

&lt;p&gt;To obtain access to the NASA API:&lt;br&gt;
Go to &lt;a href="https://api.nasa.gov/" rel="noopener noreferrer"&gt;api.nasa.gov&lt;/a&gt;.&lt;br&gt;
Scroll the page down until you find a form called Generate API Key.&lt;br&gt;
Fill in your personal information and click on signup. You should receive an API key right after.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw58h3izwpv09wr3e506n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw58h3izwpv09wr3e506n.png" alt="Generate API Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;The are three libraries that you will need.&lt;br&gt;
&lt;a href="https://docs.tweepy.org/en/stable/#" rel="noopener noreferrer"&gt;Tweepy&lt;/a&gt; for more easily use of the Twitter API,&lt;br&gt;
&lt;a href="https://pypi.org/project/requests/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; to obtain the information from the NASA API through an HTTP request,&lt;br&gt;
&lt;a href="https://pypi.org/project/python-dotenv/" rel="noopener noreferrer"&gt;python-dotenv&lt;/a&gt;, which I used to store and access my private keys without exposing them publicly.&lt;br&gt;
You can manually install these libraries or if you cloned my Github repository, install through the &lt;a href="https://github.com/viniciusenari/twitter-profile-updater/blob/main/requirements.txt" rel="noopener noreferrer"&gt;requirements.txt&lt;/a&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pip install tweepy
pip install requests
pip install python-dotenv


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pip install requirements.txt


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you are using python-dotenv, you must create a .env file and write your API keys and access token. Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;TWITTER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TWITTER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TWITTER_API_KEY_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TWITTER_API_KEY_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TWITTER_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TWITTER_ACCESS_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;NASA_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_NASA_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Getting the content from NASA's API
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NASA_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.nasa.gov/planetary/apod?api_key=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;media_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;imageURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hdurl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imageURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the code above, I send an HTTP request and obtain a JSON containing information about today's astronomy picture of the day. &lt;a href="https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is an example of what today's JSON looks like using the demo key. From this, I get the image's title and URL. There are two URLs. The difference is that hdurl is in a higher resolution.&lt;br&gt;
I'm checking if the value of &lt;code&gt;'media-type'&lt;/code&gt; is &lt;code&gt;'image'&lt;/code&gt; because sometimes the API returns a video instead of an image. In that case, I'll skip that day and not update the banner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download the image
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;img_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tmp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tmp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tmp/banner.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If it does not exist, I create a folder called 'tmp' in the main directory, where I will save the banner image. I get the image from the URL I obtained from the NASA API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auth on Twitter API
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWITTER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;api_key_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWITTER_API_KEY_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWITTER_ACCESS_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;access_token_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuthHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_access_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;access_token_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;API&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Using Tweepy to handle the authentication, I pass my API key and secret to Tweepy's OAuthHandler object, set my access token, and then create an API instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating profile
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Banner photo: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;from: https://apod.nasa.gov/apod/astropix.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_profile_banner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tmp/banner.jpg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that I have created an API instance using Tweepy, obtained the content from the NASA API, and downloaded the image, all I have to do is update the banner. I'm also updating my bio to display the title of the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying and setting a scheduler
&lt;/h2&gt;

&lt;p&gt;By running this code, your profile banner and bio will be updated. You'll have to run it daily, so your profile is updated daily. I deployed the code on Heroku and used a scheduler to run the main script every day. Heroku allows you to have up to 5 free projects, so I did not have to pay to use this alternative.&lt;/p&gt;

&lt;p&gt;First, create a new app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49trni694sar2znf5xx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49trni694sar2znf5xx8.png" alt="Create new app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On your app's deploy tab, choose how you want to deploy your project. I linked my app to the Github repository. Still, you can also use the Heroku CLI to deploy your project if it is not on Github.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t4j7myc7lla7xia65gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t4j7myc7lla7xia65gp.png" alt="Deployment method"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under the settings tabs, there is a field to add your config vars. You must add your API keys, access tokens, and secrets there. These variables will then become accessible as if they were in a .env file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mlmhncga82a91nabk17.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mlmhncga82a91nabk17.png" alt="Config vars"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can open the console on the top right and run the main script of your project to test if it applies the changes to your Twitter profile by running it on Heroku.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;python main.py


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fen94ymw9ow7mqyjc6n87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fen94ymw9ow7mqyjc6n87.png" alt="Open Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you'll have to go to the &lt;a href="https://elements.heroku.com/addons" rel="noopener noreferrer"&gt;Addons page&lt;/a&gt; and look for an addon called Heroku scheduler. Open the scheduler page and click on install. You'll have to select the project you want to use this addon.&lt;br&gt;&lt;br&gt;
To use any addon on Heroku, you need to have added your credit card info to your account billing settings. Still, it will not charge you since this addon is free unless you run the application many times a month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcte7fzu4pfewl6y6ytoi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcte7fzu4pfewl6y6ytoi.png" alt="Heroku Scheduler"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, go back to your app overview tab. You should see the Heroku scheduler under the installed addons. Open it and click on create a new job. You will then have to fill in what command you want to run and at what interval it will run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvf0lj4jix6cs38bg84j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvf0lj4jix6cs38bg84j.png" alt="Schedule a job"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading. Let me know in the comments if you have any questions or suggestions.&lt;/p&gt;

&lt;p&gt;Here is the GitHub repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/viniciusenari/twitter-profile-updater" rel="noopener noreferrer"&gt;https://github.com/viniciusenari/twitter-profile-updater&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>twitter</category>
      <category>tutorial</category>
      <category>api</category>
    </item>
  </channel>
</rss>
