{"id":10576,"date":"2023-12-26T19:19:11","date_gmt":"2023-12-27T00:19:11","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=10576"},"modified":"2023-12-26T19:19:11","modified_gmt":"2023-12-27T00:19:11","slug":"python-code-creating-title-images","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=10576","title":{"rendered":"Python Code &#8212; Creating Title Images"},"content":{"rendered":"\n<p>Instead of allowing YouTube to randomly pick a frame to use as the preview image, I have always made a title image for the Township meetings I post to YouTube. At first, this was a manual (and thus time consuming for a lot of videos). In the interim, I have created a script that generates the color gradient background and overlays text including the meeting type and date. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n# Valid meeting types: &quot;TrusteeRegular&quot;,  &quot;TrusteeSpecial&quot;, &quot;TrusteeEmer&quot;, &quot;TrusteeHearing&quot;, &quot;BZAReg&quot;, &quot;BZAHearing&quot;, &quot;ZCReg&quot;, &quot;ZCHearing&quot;\nstrMeetingListSpreadsheet = &#039;MeetingList.xlsx&#039;\n\nfrom PIL import Image, ImageDraw, ImageFont\nimport pandas as pd\n\nBLACK= (0,0,0)\nWHITE = (255,255,255)\n\nTRUSTEE_COLOR_PALETTE = &#x5B;(156,12,12), (92,7,7), (0,0,0)]\nBZA_COLOR_PALETTE = &#x5B;(253,139,1), (91,51,0), (0,0,0)]\nZC_COLOR_PALETTE = &#x5B;(24,113,56), (8,41,20), (0,0,0)]\nMISC_COLOR_PALETTE = &#x5B;(175,28,195), (55,9,61), (0,0,0)]\n\nobjFontMeetingTitle = ImageFont.truetype(&quot;\/usr\/share\/fonts\/liberation-sans\/LiberationSans-Regular.ttf&quot;,115)\nobjFontMeetingTopic = ImageFont.truetype(&quot;\/usr\/share\/fonts\/liberation-sans\/LiberationSans-Regular.ttf&quot;,115)\nobjFontMeetingDate = ImageFont.truetype(&quot;\/usr\/share\/fonts\/liberation-sans\/LiberationSans-Italic.ttf&quot;,95)\n\nclass Point(object):\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\nclass Rect(object):\n    def __init__(self, x1, y1, x2, y2):\n        minx, maxx = (x1,x2) if x1 &lt; x2 else (x2,x1)\n        miny, maxy = (y1,y2) if y1 &lt; y2 else (y2,y1)\n        self.min = Point(minx, miny)\n        self.max = Point(maxx, maxy)\n\n    width  = property(lambda self: self.max.x - self.min.x)\n    height = property(lambda self: self.max.y - self.min.y)\n\ndef gradient_color(minval, maxval, val, color_palette):\n    &quot;&quot;&quot; Computes intermediate RGB color of a value in the range of minval\n        to maxval (inclusive) based on a color_palette representing the range.\n    &quot;&quot;&quot;\n    max_index = len(color_palette)-1\n    delta = maxval - minval\n    if delta == 0:\n        delta = 1\n    v = float(val-minval) \/ delta * max_index\n    i1, i2 = int(v), min(int(v)+1, max_index)\n    (r1, g1, b1), (r2, g2, b2) = color_palette&#x5B;i1], color_palette&#x5B;i2]\n    f = v - i1\n    return int(r1 + f*(r2-r1)), int(g1 + f*(g2-g1)), int(b1 + f*(b2-b1))\n\ndef horz_gradient(draw, rect, color_func, color_palette):\n    minval, maxval = 1, len(color_palette)\n    delta = maxval - minval\n    width = float(rect.width)  # Cache.\n    for x in range(rect.min.x, rect.max.x+1):\n        f = (x - rect.min.x) \/ width\n        val = minval + f * delta\n        color = color_func(minval, maxval, val, color_palette)\n        draw.line(&#x5B;(x, rect.min.y), (x, rect.max.y)], fill=color)\n\ndef vert_gradient(draw, rect, color_func, color_palette):\n    minval, maxval = 1, len(color_palette)\n    delta = maxval - minval\n    height = float(rect.height)  # Cache.\n    for y in range(rect.min.y, rect.max.y+1):\n        f = (y - rect.min.y) \/ height\n        val = minval + f * delta\n        color = color_func(minval, maxval, val, color_palette)\n        draw.line(&#x5B;(rect.min.x, y), (rect.max.x, y)], fill=color)\n\n\nif __name__ == &#039;__main__&#039;:\n    df = pd.read_excel(strMeetingListSpreadsheet, sheet_name=&quot;Sheet1&quot;)\n\n    df = df.reset_index()  # make sure indexes pair with number of rows\n\n    for index, row in df.iterrows():\n        strGraphicName = f&quot;{row&#x5B;&#039;Date&#039;].strftime(&#039;%Y%d%m&#039;)}-{row&#x5B;&#039;Type&#039;]}.png&quot;\n        strMeetingType = row&#x5B;&#039;Type&#039;]\n\n        # Draw a three color horizontal gradient.\n        region = Rect(0, 0, 1920, 1080)\n        width, height = region.max.x+1, region.max.y+1\n        image = Image.new(&quot;RGB&quot;, (width, height), BLACK)\n        draw = ImageDraw.Draw(image)\n\n        # Add meeting title\n        if strMeetingType == &quot;TrusteeRegular&quot;:\n            horz_gradient(draw, region, gradient_color, TRUSTEE_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Trustee Regular Meeting&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;TrusteeSpecial&quot;:\n            horz_gradient(draw, region, gradient_color, TRUSTEE_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Trustee Special Meeting&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;TrusteeEmer&quot;:\n            horz_gradient(draw, region, gradient_color, TRUSTEE_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Trustee Emergency Meeting&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;TrusteeHearing&quot;:\n            horz_gradient(draw, region, gradient_color, TRUSTEE_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Trustee Public Hearing&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;BZAReg&quot;:\n            horz_gradient(draw, region, gradient_color, BZA_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;BZA Regular Meeting&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;BZAHearing&quot;:\n            horz_gradient(draw, region, gradient_color, BZA_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;BZA Public Hearing&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;ZCReg&quot;:\n            horz_gradient(draw, region, gradient_color, ZC_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Zoning Commission Meeting&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        elif strMeetingType == &quot;ZCHearing&quot;:\n            horz_gradient(draw, region, gradient_color, ZC_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Zoning Commission Hearing&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n        else:\n            horz_gradient(draw, region, gradient_color, MISC_COLOR_PALETTE)\n            draw.text((1670, 525),&quot;Township Meeting&quot;,WHITE,font=objFontMeetingTopic, anchor=&quot;rm&quot;)\n\n        # Add township and date\n        draw.text((1070, 225),&quot;Hinckley Township&quot;,WHITE,font=objFontMeetingTitle, anchor=&quot;rm&quot;)\n        draw.text((1770, 825),row&#x5B;&#039;Date&#039;].strftime(&#039;%B %d, %Y&#039;),WHITE,font=objFontMeetingDate, anchor=&quot;rm&quot;)\n\n        image.save(strGraphicName, &quot;PNG&quot;)\n        print(f&quot;image saved as {strGraphicName}&quot;)\n\n\n<\/pre><\/div>\n\n\n<p>I have an Excel file which contains the meeting type code, a long meeting title that is used as the second line of the image, a date (and a MeetingDate that I use in my concat formulae that create the title and description for YouTube). To use an Excel date in concat, you need to use a TEXT formula with the text formatting string. <\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"129\" data-id=\"10577\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet-1024x129.png\" alt=\"\" class=\"wp-image-10577\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet-1024x129.png 1024w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet-300x38.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet-768x97.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet-1536x193.png 1536w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet-750x94.png 750w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-SourceSpreadsheet.png 1894w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/figure>\n\n\n\n<p>This allows me to have a consistent preview image for all of our postings without actually <em>making<\/em> dozens of files by hand. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-Results.png\"><img loading=\"lazy\" decoding=\"async\" width=\"623\" height=\"670\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-Results.png\" alt=\"\" class=\"wp-image-10578\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-Results.png 623w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2023\/12\/TitleImageScript-Results-279x300.png 279w\" sizes=\"auto, (max-width: 623px) 100vw, 623px\" \/><\/a><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Instead of allowing YouTube to randomly pick a frame to use as the preview image, I have always made a title image for the Township meetings I post to YouTube. At first, this was a manual (and thus time consuming for a lot of videos). In the interim, I have created a script that generates &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1945],"tags":[1947,1946,664],"class_list":["post-10576","post","type-post","status-publish","format-standard","hentry","category-python","tag-pandas","tag-pillow","tag-python"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/10576","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10576"}],"version-history":[{"count":1,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/10576\/revisions"}],"predecessor-version":[{"id":10579,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/10576\/revisions\/10579"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10576"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10576"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10576"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}