<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[The Storyblocks Tech Blog - Medium]]></title>
        <description><![CDATA[Meet the engineers behind Storyblocks. Our mission: help creatives to move faster, create more, and realize their artistic visions with unlimited freedom. Like what you read? Want to help work on that? Join our team, we’re hiring! - Medium]]></description>
        <link>https://medium.com/storyblocks-engineering?source=rss----65d5377a1f7c---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>The Storyblocks Tech Blog - Medium</title>
            <link>https://medium.com/storyblocks-engineering?source=rss----65d5377a1f7c---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 05 Nov 2026 22:16:14 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/storyblocks-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Where was this video shot?]]></title>
            <link>https://medium.com/storyblocks-engineering/where-was-this-video-shot-e01331b28f37?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/e01331b28f37</guid>
            <category><![CDATA[named-entity-recognition]]></category>
            <category><![CDATA[geospatial-data]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[data-visualization]]></category>
            <dc:creator><![CDATA[Greg Swanson]]></dc:creator>
            <pubDate>Fri, 06 Feb 2026 14:17:41 GMT</pubDate>
            <atom:updated>2026-02-06T14:17:39.957Z</atom:updated>
            <content:encoded><![CDATA[<h4>How we tagged our entire video library with their real-world locations</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JiRGIHUOyjdzynPmnSXL-Q.png" /><figcaption>Storyblocks Video Content Geographic Distribution</figcaption></figure><p>When you search for a video on Storyblocks — “redwoods,” “Aspen festival,” or “York skyline” — how does the site know <em>where</em> that is?</p><p>Until recently, the answer was: <strong>it usually didn’t</strong>.</p><p>And our customers noticed:</p><blockquote>“Finding some of your images is almost impossible because the search engine doesn’t work properly… Many Brazilian cities will never be found directly. I lost hours to get images that should have appeared instantly.”</blockquote><blockquote>“Not enough content from Africa.”</blockquote><p>Enterprise customers and prospects asked similar questions directly to our support and content teams — <em>Can I get footage from a specific city? From a country? From where we’re expanding next quarter?</em></p><h3>The problem wasn’t content.<br>It was location.</h3><p>Video metadata is messy. Titles are short. Tags are inconsistent. Locations are abbreviated, misspelled, implied, stuffed, or sometimes just… forgotten.</p><p>So we built <strong>SB-NERD</strong>: Storyblocks’ internal system for <strong>Named Entity Recognition + Disambiguation</strong>, focused entirely on geographic locations.</p><p>Its job is simple to say and hard to do:</p><ul><li>Find location mentions in video titles and tags or infer them</li><li>Figure out what real place they refer to (if any)</li><li>Link them to a known locality with latitude, longitude</li><li>Or label it <strong>NO LOCATION</strong> or <strong>AMBIGUOUS LOCATION</strong></li></ul><h3>The problem with location at scale</h3><p>Humans are great at location inference. Machines are not.</p><p>Consider these titles:</p><ul><li>“HD San Fran pier timelapse”</li><li>“Zion NP hiking b-roll”</li><li>“Chikago skyline at night”</li><li>“Aspen festival crowd cheers”</li><li>“York city drone shot”</li></ul><p>To a human, these are obvious.<br> To a machine, they’re a minefield.</p><ul><li>Is “Aspen” a city, a tree, or a brand?</li><li>Is “York” New York? York, UK? York, Pennsylvania?</li><li>Is “NP” a location or just noise?</li><li>Is this even a real place — or a studio shoot where location doesn’t matter?</li></ul><p>On top of that consider:</p><blockquote>The world has a lot of repeated names.<br>The internet has even more misspellings.</blockquote><h3>What SB-NERD actually does</h3><p>At a high level, the pipeline looks like this:</p><p><strong>Title + tags → location candidates → best-guess location (or none)</strong></p><p>Under the hood, SB-NERD has two main stages:</p><ol><li><a href="https://arxiv.org/html/2411.05057v1"><strong>NER</strong></a> — Extract candidate location names</li><li><a href="https://datavid.com/blog/named-entity-disambiguation"><strong>NED</strong></a> — Decide which real place (if any) those names refer to</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KmMm2RrgKO3Hwo8aussz7A.png" /></figure><h3>Step 1: Traditional NER (and why it broke)</h3><p>The obvious starting point was classic Named Entity Recognition or NER:<br>Run an <a href="https://huggingface.co/flair/ner-english">off-the-shelf model</a>, extract anything labeled <em>LOCATION</em>, and try to match it.</p><p>It worked… sometimes.</p><p>But Storyblocks metadata isn’t clean prose. It’s short, fragmented, and inconsistent. Traditional NER struggled in two big ways:</p><h4>1. It missed things we cared about</h4><p>Abbreviations, slang, and partial references were often ignored:</p><ul><li>“San Fran”</li><li>“SoCal”</li><li>“Zion NP”</li></ul><h4>2. Extracted text wasn’t matchable</h4><p>Even when NER returned something like “SF,” (comma included) OR “t atl” that’s not a usable key for a geographic database without a lot of extra logic.</p><p>We realized we were asking the wrong question.</p><p>Instead of:<br> <strong>“Is this word a location?”</strong></p><p>We needed:<br> <strong>“What location should we try to match?”</strong></p><h3>The shift: using an LLM as a location candidate generator</h3><p>The breakthrough came when we switched from a state-of-the-art NER model and instead used an <a href="https://huggingface.co/meta-llama/Llama-3.3-70B-Instruct">LLM</a> as a <strong>strict, opinionated candidate guesser</strong>.</p><p>Its job became:</p><blockquote><em>Given a title and tags, output a location candidate in a format that a database like GeoNames can actually match (e.g. city, state, country)</em></blockquote><p>This worked <em>much</em> better on messy metadata.</p><p>The model could:</p><ul><li>Expand abbreviations (“SF” → “San Francisco”)</li><li>Normalize misspellings (“Chikago” → “Chicago”)</li><li>Recognize landmarks (“Golden Gate Bridge”) and record the city that was in.</li><li>And — critically — <strong>not force a location when none exists</strong></li></ul><h3>The key: strict rules + few-shot examples</h3><p>We got good results only after being very opinionated:</p><ul><li>Precise instructions</li><li>Prefer specific over vague (city &gt; region &gt; country)</li><li>Do not hallucinate</li><li>Output clean, structured results</li><li>Explicitly return <strong>NO_LOCATION</strong> or <strong>AMBIGUOUS </strong>for examples like “Best Buy” or if the tags have multiple contradictory locations in them</li></ul><p>We iterated by reviewing thousands of examples, finding failure modes, and adding them directly to the prompt as few-shot patterns:</p><pre>          Input: Title: Niagara Falls Aerial; Tags: [&quot;falls&quot;, &quot;niagara&quot;, &quot;aerial&quot;]<br>          Output: City: Niagara Falls; State/Province/Region: Ontario; Country: Canada<br>        <br>          Input: Title: Into the Woods of Aspen; Tags: [&quot;aspen&quot;, &quot;autumn&quot;]<br>          Output: no location<br>        <br>          Input: Title: Swampy Landscape; Tags: [&quot;swampy&quot;, &quot;trees&quot;, &quot;york&quot;, &quot;new&quot;]<br>          Output: unknown<br>        <br>          Input: Title: Clouds Moving Over Lush Valley Overlooking Na Pali Coast; Tags: [&quot;hawaii&quot;, &quot;kauai&quot;, &quot;na pali&quot;, &quot;island&quot;]<br>          Output: City: Kauai; State/Province/Region: Hawaii; Country: United States</pre><p>This step didn’t <em>solve</em> geolocation.<br> It made the next step possible.</p><h3>Step 2: Disambiguation with a real-world location database</h3><p>Once we have a candidate name, we still have the hard question:</p><p><strong>Which place is it?</strong></p><p>That’s the “D” in SB-NERD.</p><p>We anchored everything around <strong>GeoNames</strong>, an open global database of cities, regions, countries, and landmarks.</p><p>GeoNames gives us:</p><ul><li>Canonical names and alternate spellings</li><li>Administrative hierarchy (city → state → country)</li><li>Latitude and longitude</li><li>Population estimates</li></ul><p>But it also introduces a classic problem:</p><p><strong>Many places share the same name.</strong></p><h3>Matching strategy: precision first, recall second</h3><p>We built matching in layers, always preferring correctness over coverage.</p><h4>Layer 1: Exact matching</h4><p>Fast, predictable, and surprisingly effective.</p><p>To reduce ambiguity, we forced the LLM to return city, state/province, and country whenever possible. That eliminated most “Springfield, USA”-style ambiguity.</p><p>There were still a lot of unmatched locations, which, upon examination, were definitely places</p><blockquote>Our LLM guess might have said “London, United Kingdom,” but the comparison we made to geonames might say “London, Great Britain”</blockquote><h4>Layer 2: Fuzzy matching</h4><p>To handle:</p><ul><li>Common Misspellings (or alternate spellings)</li><li>Accent marks (“Sao Paulo” vs “São Paulo”)</li><li>Formatting differences</li></ul><p>We attempted fuzzy matching, but this didn’t work very well. GeoNames’ alternate names field helped a lot here.</p><p>We ended up only matching if it was an exact match on a known alternate name in the GeoNames database. This expanded our matches more, but we still had a significant number of missed location matches that a human could have matched.</p><h4>Layer 3: Embedding similarity (last resort)</h4><p>For the truly messy cases — where spelling and tokens break down — we used embedding similarity as a fallback.</p><p>We kept this layer intentionally conservative.</p><p>The rule stayed consistent:</p><blockquote><em>Better to return </em><strong><em>NO LOCATION</em></strong><em> than be confidently wrong.</em></blockquote><p>After these 3 steps, we finally were able to confidently tag our library and <strong>label exact locations for nearly 1 million videos.</strong></p><h3>Why this matters &amp; What it unlocks</h3><p>Location is a core part of a video’s identity.</p><p>A misty forest in Oregon is not the same as one in Japan.<br> A city skyline means very different things depending on where it is.</p><p>Accurate geo-tagging unlocks new product value:</p><ul><li>Better search results for place-specific queries</li><li>Natural-language geo search gets much more precise (“videos from coastal Brazil”)</li><li>Geographic diversity in results and collections</li><li>“More from this place” recommendations</li><li>Enterprise workflows tied to regions and markets</li></ul><p>It also lets us <em>see</em> the catalog differently.</p><p>Once locations are structured, we can visualize coverage, spot gaps, and explore the library on a map (not just a search bar).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0Rbx8Y2R5ER3BvJ0ZrHocg.gif" /><figcaption>“Map View” using the geospatial data from our Hackathon last year</figcaption></figure><p>And yes, next time you search for <em>“Belo Horizonte,”</em> the right videos should finally show up.</p><h3>What’s next?</h3><p>As always, there is more work to do — like expanding our natural language understanding in search, shipping geoquerying on the site, &amp; adding EVEN MORE AI features (not to mention all the other great things Storyblocks’ Data Science team is cooking up).</p><p>If that’s work you’re interested in, too, please check out our <a href="https://www.wearestoryblocks.com/">career page</a>.</p><p>Stay tuned for more updates from the Data Science team. And happy searching!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e01331b28f37" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/where-was-this-video-shot-e01331b28f37">Where was this video shot?</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Finding the most clickbait-y thumbnail for you]]></title>
            <link>https://medium.com/storyblocks-engineering/finding-the-most-clickbait-y-thumbnail-for-you-f4802575a32b?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/f4802575a32b</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[computer-vision]]></category>
            <category><![CDATA[stock-media]]></category>
            <category><![CDATA[clip]]></category>
            <category><![CDATA[deep-learning]]></category>
            <dc:creator><![CDATA[Greg Swanson]]></dc:creator>
            <pubDate>Mon, 23 Sep 2024 15:02:17 GMT</pubDate>
            <atom:updated>2024-09-23T15:02:16.679Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>The Data Science team at Storyblocks not only wants you to find what you’re looking for on our site, but <strong>it’s our job to help you find it</strong>. With that in mind, our latest project is designed to help you know if you’ve found the perfect video clip before you’ve even previewed it. Keep reading below to see how we did it.</blockquote><h3>We started by asking ourselves: What makes you click?</h3><p>Put differently, what makes you like a piece of content enough to engage with it? It’s a hard question to quantify, but that’s precisely what we try to do on the Storyblocks’ Data Science team. The more we understand about why users like content, the better we can help them find the content they like.</p><h3>Long story short: we decided thumbnails matter</h3><p>We’ve noticed that sometimes our thumbnails don’t always do justice to the amazing videos they represent. A great thumbnail can be the difference between a click and a scroll, and we want to make sure our users are clicking on videos that truly meet their needs. To address this, we’ve embarked on a quest to find the perfect frame — the one that shouts, “Click me!” without actually making any noise.</p><h4>Side note: what’s a thumbnail?</h4><blockquote>A thumbnail is the first image you see on our video search page. It’s a single frame from the video meant to represent that video’s content without having to play the video. Here’s what they look like today:</blockquote><figure><img alt="12 thumbnail images representing their corresponding dog videos on our “dog” search page" src="https://cdn-images-1.medium.com/max/1024/1*Ekrth3QE3jKJyuIGeEiQdA.png" /><figcaption>12 thumbnail images representing their corresponding dog videos on our “dog” search page</figcaption></figure><p>Our old thumbnails just defaulted to the first frame of the video, which could often look like this (car crash):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*TqJIABJjnYF8knfhNkY6BA.png" /><figcaption>Where’s the crash?</figcaption></figure><p>Or this (cards falling):</p><figure><img alt="Where are the cars?" src="https://cdn-images-1.medium.com/max/626/1*7yJV5Z4f4H-z3gA5UQ7JfQ.png" /><figcaption>Where are the cards?</figcaption></figure><p>Or this (truck camper on a bridge):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/646/1*kWWWuObSea0-Mt9S6kkEGg.png" /><figcaption>Why’s the truck so small and far away?</figcaption></figure><h3>Our 4 Step Thumbnail Transformation Journey</h3><h4>Step 1: Understanding User Engagement</h4><p>First, we needed to understand what makes a thumbnail appealing. Is it the brightness? The action? The presence of smiling faces? We delved into our data to see which thumbnails were getting the most clicks and downloads. This involved a lot of coffee, a lot of data crunching, and a bit of guesswork.</p><h4>Step 2: Leveraging Machine Learning Magic</h4><p>With our insights in hand, we turned to the powerful world of machine learning using 3 seperate models. First, We needed a model to help determine which frame of a video is most relevant to the search. We assumed that if a video appears in a search because its title is close to the query, then a frame similar to the title would be the most representative. For this, we used <a href="https://openai.com/index/clip/">CLIP</a> (Contrastive Language-Image Pre-Training), which helped us align the frames with the titles in a meaningful way.</p><p>Rather than score multiple text-to-image similarities as in OpenAI’s example below, we scored multiple image-to-text similarities to get a score per image that said how similar each image was to the title text. This allowed us to say which out of ~15 thumbnails was the most relevant to the title. Check out <a href="https://huggingface.co/docs/transformers/en/model_doc/clip">Huggingface</a> for some code snippets similar to how we used it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2TYCNx8_N-5iZRoFLxAtlw.png" /><figcaption><a href="https://cdn.openai.com/clip/assets/zero-shot/zs_5.png">https://cdn.openai.com/clip/assets/zero-shot/zs_5.png</a></figcaption></figure><h4>Step 3: Rating the Aesthetic Appeal</h4><p>Next, we needed to ensure the chosen frame wasn’t just relevant but also visually appealing. For this, we used another model to rate images based on their aesthetic appeal. This model was fine-tuned several years ago to work well on our stock image and video data. We were able to repurpose the existing model. This process was made significantly easier by our previous use of our databricks mlflow model registery and our private python utlities packages. The code looked something like this:</p><pre>import sb_quality<br>from sb_quality.predict import get_quality_scores<br><br>DELTA_TABLE_NAME = f&quot;{ENV.ds_db}.frame_quality_scores&quot;<br>#get image urls<br>loader = build_loader(spark)<br>stock_items = (loader<br>                   .get_stock_item_encoding(content_class=CONTENT_CLASS, **image_url_types[CONTENT_CLASS])<br>                   .filter(F.col(&#39;url&#39;).isNotNull()))<br><br>#only process new images<br>if spark.catalog.tableExists(DELTA_TABLE_NAME):<br>  stock_items_processed = spark.read.table(DELTA_TABLE_NAME)<br>  stock_items = stock_items.join(stock_items_processed, &#39;url&#39;, &#39;left_anti&#39;)<br><br>#run our model with our utility function:<br>scores = get_quality_scores(spark, configs, stock_items)<br><br>merge_into_delta(df=scores,<br>                delta_table_name=DELTA_TABLE_NAME,<br>                merge_keys=[&#39;content_class&#39;, &#39;content_id&#39;, &#39;url&#39;],<br>                columns_to_partition_by=[&#39;content_class&#39;],<br>                isolate_to_partition={&#39;content_class&#39;: CONTENT_CLASS.value})<br><br># read our quality scores in downstream applications &amp; jobs<br>query_quality = f&#39;select * from {DELTA_TABLE_NAME}&#39;<br><br>quality = spark.sql(query_quality)</pre><p>If you’re curious what that get_quality_scores function is doing under the hood, here’s anothe code snippet to give you a better idea:</p><pre>def get_quality_scores(spark, configs, stock_items):<br>    batch_size = configs[&#39;BATCH_SIZE&#39;]<br>    num_workers = configs[&#39;NUM_WORKERS&#39;]<br><br>    model_state = get_model_state(configs)<br><br>    num_classes = model_state[&#39;num_classes&#39;]<br>    base_model = model_state[&#39;base_model&#39;]<br>    bc_model_state = spark.sparkContext.broadcast(model_state[&#39;state_dict&#39;])<br><br>    initialize_with_model_state = build_initialize_with_model_state_func(bc_model_state)<br><br>    def predict_batch(urls):<br>        try:<br>            with tempfile.TemporaryDirectory() as download_dir:<br>                files = [<br>                    download_image(url=url, download_dir=download_dir) for _, url in urls.items()<br>                ]<br>                print(&#39;number of files to predict = {}&#39;.format(len(files)))<br><br>                model, input_size = initialize_with_model_state(num_classes=num_classes, model_name=base_model)<br>                model.to(device)<br><br>                transform = create_data_transform(&#39;test&#39;, input_size)<br>                images = ImageDataset(files, transform=transform)<br>                loader = torch.utils.data.DataLoader(images, batch_size=batch_size, num_workers=num_workers)<br><br>                all_predictions = []<br>                with torch.no_grad():<br>                    for batch in loader:<br>                        predictions = list(<br>                            torch.nn.functional.softmax(model(batch.to(device)), dim=1).cpu().numpy()<br>                        )<br>                        for prediction in predictions:<br>                            all_predictions.append(prediction[1])<br><br>        except exceptions.SbQualityError:<br>            all_predictions = [None for _ in range(len(urls))]<br><br>        return pd.Series(all_predictions)<br><br>    predict_udf = pandas_udf(FloatType(), PandasUDFType.SCALAR)(predict_batch)<br>    predictions_df = stock_items.select(col(&#39;content_id&#39;), col(&#39;url&#39;),<br>                                        predict_udf(&#39;url&#39;).alias(&#39;quality_score&#39;))<br><br>    return predictions_df</pre><h4>Step 4: Bringing It All Together</h4><p>Combining these two models in a third custom FFN (feed-forward neural network), we developed a system that selects the best thumbnail for each video. The Code looked somthing like this:</p><pre># our dataset used the similarity score, aesthetic quality score, &amp; media type<br># we scaled our features before feeding them to our model<br>feature_cols = [<br>  &#39;quality_score&#39;,<br>  &#39;cosine_similarity&#39;, <br>  &#39;quality_clip_interaction_term&#39;,<br>  &#39;content_type__motion-background&#39;, <br>  &#39;content_type__template&#39;,<br>  &#39;content_type__vr360&#39;]<br>X = dataset[feature_cols]<br><br>logged_scaler = &#39;models:/Download_Rate-CLIP-Quality_Model_Standard_Scaler/Production&#39;<br>loaded_scaler = mlflow.sklearn.load_model(logged_scaler)<br><br>X_scaled = loaded_scaler.transform(X)<br><br><br>#final model &amp; download rate prediction: <br>logged_model = &#39;models:/Download_Rate-CLIP-Quality Model/Production&#39;<br># Load model as a PyFuncModel.<br>loaded_model = mlflow.pyfunc.load_model(logged_model)<br><br># Predict on a Pandas DataFrame.<br>dataset[&#39;predicted_download_rate&#39;] = np.exp(loaded_model.predict(pd.DataFrame(X_scaled)).flatten())</pre><p>The raw features &amp; predicted score looked something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2HMydPPH5qCUsAXwBZwesg.png" /><figcaption>right-most column is the score &amp; other columns were features</figcaption></figure><p>The visual result? Thumbnails that are both representative of the video content and visually engaging. Here’s an example of those videos from before and what they look like now after our thumbnail makeover:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rg91_mUDYnPlbboc08t3hg.png" /><figcaption><strong>blue outline was before (no crash); after our update we see a car crash</strong></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IT_BY3hvgdvQluEhiutvmg.png" /><figcaption><strong>blue outline was before (no cards); after our update we see cards</strong></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TEpbZo96p9XwEP_Vep2QEA.png" /><figcaption><strong>blue outline was before (camper very small in the background); after our update we see the camper much closer</strong></figcaption></figure><h3>The Impact</h3><blockquote>Since implementing our new thumbnail selection system, we’ve seen a significant uptick in user engagement. More users are clicking on videos, and the satisfaction with their choices has gone up. It’s a win-win!</blockquote><h4>That said, Not Every Thumbnail is Perfect</h4><p>While we’ve seen great overall improvement, there are always exceptions. Sometimes our new thumbnails don’t hit the mark as well as we’d hoped. Despite this, the majority of our updates have been positive, and we are constantly learning and tweaking our models to get better. By the way, what do you think of our new thumbnails? Let us know in the comments below!</p><h3>What’s Next?</h3><p>We’re not stopping here. Our team is continually exploring new ways to enhance your experience on <a href="http://storyblocks.com">Storyblocks.com</a> Whether it’s through better recommendations, smarter search, or even more engaging thumbnails, we’re always working on something exciting. If that’s work you’re interested in, too, please check out our <a href="https://www.wearestoryblocks.com/">career page</a>.</p><p>Stay tuned for more updates from the Data Science team. And as always, happy searching!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4802575a32b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/finding-the-most-clickbait-y-thumbnail-for-you-f4802575a32b">Finding the most clickbait-y thumbnail for you</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Level Up Time at Work: Elevate Your Skills and Your Career]]></title>
            <link>https://medium.com/storyblocks-engineering/level-up-time-at-work-elevate-your-skills-and-your-career-4f174aca33f6?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/4f174aca33f6</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[information-retrieval]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[work-life-balance]]></category>
            <category><![CDATA[level-up]]></category>
            <dc:creator><![CDATA[Greg Swanson]]></dc:creator>
            <pubDate>Wed, 14 Aug 2024 14:01:33 GMT</pubDate>
            <atom:updated>2024-08-14T17:35:36.505Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>It’ll be good for you, your career, and your company</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*Thijz_f0MgYSIQFb--Tm4w.jpeg" /></figure><p>At Storyblocks, we know how to balance work and play. I’m not just talking about letting me go full Utah ski bum while still paying me. As a company, we’ve commited to a structure that helps personal &amp; professional growth.</p><p>The perks are great — think unlimited PTO, “No Meeting Wednesdays,” and both summer and winter vacations — but one of the most impactful benefits is our commitment to personal and professional growth through dedicated <em>level up time.</em></p><h3>What is Level Up Time?</h3><p>Level Up Time is our initiative to ensure every team member has dedicated time each sprint to focus on personal growth. Whether it’s taking a course, working on a side project, attending a conference, or even writing blog posts, this time is all about you. And trust me, it pays off for both you and the company.</p><h4>Courses I’ve Taken: Text Retrieval &amp; Search Engines</h4><p>One of the courses I dove into was on <a href="https://www.coursera.org/learn/text-retrieval">Text Retrieval and Search Engines.</a> This course was a game-changer, deepening my understanding of how search engines work and how information retrieval systems can be optimized.</p><p><strong>How this benefits me:</strong></p><blockquote>Enhanced my technical skills in building and optimizing the Storyblocks search service my team is responsible for.</blockquote><blockquote>Gave me insights into the latest algorithms and techniques in information retrieval.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d_zpb39rteQ02uHhEmXN-Q.png" /><figcaption>This gives you a sense for how many places on our site are powered by search. We’ve even added more since making this diagram.</figcaption></figure><p><strong>How this benefits Storyblocks:</strong></p><blockquote>Deepened my knowledge of how search worked on our site (and it’s all over our site).</blockquote><blockquote>Enabled me to develop innovative search-related prototypes that can be integrated into our product offerings.</blockquote><h4>Building Prototypes: From Face Search to Deep Fakes</h4><p>Level Up Time also allowed me to get hands-on with several exciting projects. Here are a few prototypes I’ve been working on:</p><ol><li><strong>Face Search:</strong> This tool helps users find more faces like the ones they’ve seen in a video. It’s perfect for creators looking for consistent imagery or themes in their projects. I wrote about how we used this to improve a feature on our site <a href="https://medium.com/storyblocks-engineering/find-more-faces-like-this-4738d183b596">here</a>.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y___gcV5PpEpc9TFAUPlOA.jpeg" /></figure><p>2. <strong>Follow More Contributors Like This: </strong>An intuitive feature that recommends contributors based on user preferences, making it easier for users to discover new content they love. Using a nearest neighbor approach we could recommend a contributor like <a href="https://www.storyblocks.com/video/portfolio/Space_Motion">Space_Motion</a> for users who liked content from a contributor like <a href="https://www.storyblocks.com/video/portfolio/albatros">Albatros</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kmugBb2LSo4IRASjxnHs8g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4itoMi3Y5eT6-WrZh_ErGw.png" /></figure><p>3. <strong>Searching Through Embedding Space:</strong> Imagine selecting content you like and don’t like, and then finding new content that averages out those preferences. This advanced search technique uses embedding space to deliver highly personalized recommendations.</p><blockquote>Our original Search for the word, “Travel”:</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*67keptrS7EQJrl4v3EcL2w.png" /></figure><blockquote>Our “Travel” Search after choosing words and videos we don’t like (in this case videos of planes):</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kdo2yaYAs-dNuJrAzMrQ6Q.png" /></figure><p>4. <strong>Deep Fake Video Generator:</strong> By inputting an image of a person, this tool creates a deep fake video. It’s a powerful demonstration of AI and machine learning capabilities, opening up new possibilities for video content creation.</p><blockquote><strong>Note:</strong> To avoid putting fake images on the internet, I won’t be sharing this one in our blog, but it was pretty fun making the head of engineering appear to be doing things that they actually hadn’t.</blockquote><h4>Attending &amp; Speaking at a Conference</h4><p>Level Up Time also gives us the opportunity to attend and speak at industry conferences. I recently was given the chance to both attend and lead a workshop at a major conference in our field, <a href="https://ai4.io/vegas">Ai4</a>. Storyblocks gave me the time to go do it.</p><p><strong>How this benefits me:</strong></p><blockquote>Expands my professional network by connecting with industry leaders and peers.</blockquote><blockquote>Enhances my public speaking and presentation skills, boosting my confidence.</blockquote><p><strong>How this benefits Storyblocks:</strong></p><blockquote>Showcases our innovative projects and tools, positioning Storyblocks as a thought leader.</blockquote><blockquote>Valuable insights and trends can inform our product development and strategy.</blockquote><h4>Writing Blog Posts</h4><p>Level Up Time has also given me the opportunity to share my experiences and knowledge through <a href="https://medium.com/@greg.swanson">blog posts</a> like the one you’re reading. Writing about these projects and courses not only helps solidify my learning but also contributes to the broader community by providing insights into our corner of the DS world. It’s also fun 🙂</p><h3>The Ripple Effect: Personal Growth and Company Success</h3><p>Taking time for personal growth isn’t just good for me — it’s beneficial for Storyblocks as well. The skills and knowledge I gain directly contribute to our product innovation and improve the user experience. Plus, staying up-to-date with the latest trends and technologies ensures that Storyblocks remains at the forefront of the industry.</p><p>So, if you’re considering how to make the most of your time at work, remember that investing in yourself is a win-win. Whether you’re skiing down a Utah slope or diving into a new course, take advantage of every opportunity to level up. Your future self — and your company — will thank you.</p><p>If the idea of a company that not only invests in you, but gives you time to invest in yourself is appealing to you, check out our <a href="https://grnh.se/dcc66c451us">careers page</a> and come work with me!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4f174aca33f6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/level-up-time-at-work-elevate-your-skills-and-your-career-4f174aca33f6">Level Up Time at Work: Elevate Your Skills and Your Career</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hacking your way to a RAG Model for Customer Feedback]]></title>
            <link>https://medium.com/storyblocks-engineering/hacking-your-way-to-a-rag-model-for-customer-feedback-f48def57d2a5?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/f48def57d2a5</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[retrieval-augmented-gen]]></category>
            <category><![CDATA[hackathons]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Greg Swanson]]></dc:creator>
            <pubDate>Wed, 24 Jul 2024 13:28:30 GMT</pubDate>
            <atom:updated>2024-07-24T13:28:30.135Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Hello again Storyblocks Tech Blog (SBTB?) readers — all 47 of you!</blockquote><blockquote>I bring you another story from Storyblocks’ Data Science &amp; Machine Learning Team a.k.a. the custodians of search. This time we build a prototype &amp; have some fun with our friends in User Research, Product, &amp; Data Engineering.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dj0aeOY6swcGxlQnBRAPgA.jpeg" /></figure><p>Buckle up, because I’m about to take you on a wild ride through our recent hackathon, where we built a Retrieval-Augmented Generation (RAG) model for customer feedback.</p><p><strong>Spoiler alert:</strong> it was a total blast, and we even won the hackathon! 🎉</p><h3>The Databricks Debacle: A Bumpy Start</h3><p>Our adventure began with high hopes and the Databricks tutorial on this topic. We thought we had everything we needed to deploy a killer LLM chatbot with RAG and Databricks AI Vector Search. We eagerly followed along <a href="https://www.databricks.com/resources/demos/tours/deploy-llm-chatbots-rag-and-databricks-ai-vector-search">step by step</a>. Things were looking good until… <em>drumroll please</em>… Unity Catalog decided to play hard to get. (We don’t have it enabled in Storyblocks’ version of Databricks.) 😩</p><p>That’s right. No Unity Catalog, no Databricks magic. Our dreams of seamlessly deploying our model were shattered. But we didn’t give up! This was a hackathon, and we were here to win. So, we rolled up our sleeves and pivoted faster than a 2 person Silicon Valley startup.</p><p>(<strong>In reality:</strong> We went to lunch; thought about bailing on the project; then decided to give it another go for <em>funsies</em>.)</p><h3>Hugging Our Way to Success: Enter Hugging Face</h3><p>With Databricks out of the picture, we turned our gaze to the wonderful world of Hugging Face. If you haven’t heard of them, they’re like the fairy godmother of open source models. We decided to grab an LLM (Large Language Model) from Hugging Face. Why? Because they have an insane collection of pre-trained models, and they’re super easy to work with.</p><p>We chose a model that could handle customer feedback like a pro, and we were off to the races. But wait, we needed a way to search through this feedback quickly and efficiently. Enter FAISS.</p><h3>Building Our Own FAISS Index: DIY Style</h3><p>FAISS (Facebook AI Similarity Search) is a library that lets you create fast and efficient similarity searches.</p><p><strong>Translation:</strong> it’s perfect for our RAG model. We decided to build our own FAISS index with a Python script. Here’s a little snippet to give you an idea of how we did it:</p><pre>import faiss<br>import numpy as np<br>from transformers import AutoTokenizer, AutoModel<br># Load the tokenizer and model<br>tokenizer = AutoTokenizer.from_pretrained(&quot;distilbert-base-uncased&quot;)<br>model = AutoModel.from_pretrained(&quot;distilbert-base-uncased&quot;)<br># Sample customer feedback data<br>feedback_data = [&quot;Great service!&quot;, &quot;Loved the product.&quot;, &quot;Could be better.&quot;, &quot;Terrible experience.&quot;]<br># Tokenize and embed the feedback data<br>encoded_inputs = tokenizer(feedback_data, padding=True, truncation=True, return_tensors=&quot;pt&quot;)<br>with torch.no_grad():<br> embeddings = model(**encoded_inputs).last_hidden_state.mean(dim=1).numpy()<br># Create FAISS index<br>index = faiss.IndexFlatL2(embeddings.shape[1])<br>index.add(embeddings)</pre><h3>The Dream Team: Working Together for 48 Hours</h3><p>Now, let’s talk about the real MVPs: our team of six. We had a diverse mix of skills, from developers to a PM to a UX researcher. For 48 hours, we worked like a well-oiled machine. There were late nights, countless cups of coffee, and a few moments where we questioned our sanity (<strong>read:</strong> 1 late night, 2 cups of coffee, and we drank a beer and went to a ball game. TBH it wasn’t that hard lol).</p><blockquote>But in the end, it was all worth it.</blockquote><p>We divided and conquered: some of us imported the 750k most recent comments from our users into delta, some of us prompt engineered questions to ask the LLM, and some of us built the FAISS index, finetuned the model, &amp; the app that used it.</p><h3>The components tie together like this:</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*QUL-aGw4UbJBqoB3L8Kshg.png" /></figure><h3>The Moment of Truth: Presenting Our RAG Model</h3><p>When it came time to present, we were nervous but excited. Our RAG model could retrieve relevant customer feedback in a flash and generate insightful responses like a charm.</p><p><strong>One last curve ball:</strong> We mixed up our presentation time! We thought our presentation was at 11 am, when it was really at 10 am, and we had just started making our presentation at 9:55 am. Miraculously, we threw it together in time.</p><p>Ultimately, The judges were impressed by the quality of the generated responses and their potential to help inform how we improve our site. Our app was able to sort through many of our users’ comments (~750K), find the most relevant ones for answering questions, and summarize findings with actionable bullets.</p><p>Many of those comments could be totally irelevant and looked like this:</p><blockquote>1. ayeeeeeeeeeeeeeee</blockquote><blockquote>2. I need rare pokemon.</blockquote><blockquote>3. Since I’ve started listening to your sounds and music, it gave me a clear view, a vision of happiness. Left my wife and kids, started rollerblading, quit cheese, moved to a forest and live in a tent. Thank You for showing me a way out when I was lost.</blockquote><p>The app could churn through all of them and get to something like this in seconds:</p><blockquote>1. Upgrade the Content Closet:….</blockquote><blockquote>2. Pimp your Search Engine:….</blockquote><blockquote>3. Facelift the Interface:….</blockquote><blockquote>4. Rethink the Price Tag:….</blockquote><blockquote>5. Schmooze &amp; Collaborate:….</blockquote><blockquote>6. Niche It Up:….</blockquote><p><strong>All these sassy recommendations &amp; more in a matter of seconds!</strong></p><p><strong><em>Please Note:</em></strong><em> details of the response were removed for the purpose of posting this publicly &amp; keeping our secret sauce secret — looking at you Shutterstock 👀. Readers, you’ll just have to trust me that each of these bullets had a more in-depth summary of what to fix on our site.)</em></p><h3><strong>And the Winner Is…</strong></h3><p>WE WON! That’s right, our scrappy little team took home the grand prize — or at least the hackathon business prize. It was a sweet victory, made even sweeter by the challenges we faced along the way. We proved that with a bit of creativity, teamwork, and a lot of determination, you can hack your way to success.</p><p>This was all good fun, and there were plenty of other impressive projects that the wider Storyblocks org came up with in two short days. Speaking of fun, our day-to-day work can be pretty enjoyable. If you like the work you see us doing and want to do it, too, <a href="http://wearestoryblocks.com">check out our current openings</a>.</p><p>Until next time, happy hacking! 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f48def57d2a5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/hacking-your-way-to-a-rag-model-for-customer-feedback-f48def57d2a5">Hacking your way to a RAG Model for Customer Feedback</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Who Killed Sir Redshift: How We Used Gather and AI to Throw a Virtual Murder Mystery Party]]></title>
            <link>https://medium.com/storyblocks-engineering/who-killed-sir-redshift-how-we-used-gather-and-ai-to-throw-a-virtual-murder-mystery-party-a27e233c7498?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/a27e233c7498</guid>
            <category><![CDATA[team-building]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[gather]]></category>
            <category><![CDATA[remote-working]]></category>
            <category><![CDATA[chatgpt]]></category>
            <dc:creator><![CDATA[Katie Young]]></dc:creator>
            <pubDate>Fri, 19 Apr 2024 14:09:58 GMT</pubDate>
            <atom:updated>2024-04-19T14:09:58.471Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Collage of AI-generated images depicting a gathering in a mansion" src="https://cdn-images-1.medium.com/max/692/1*Mbeq2XjcjS6EZczcsMceAw.png" /><figcaption>Collage of AI-generated images depicting a gathering in a mansion</figcaption></figure><h3>Celebrating Wins</h3><p>Here at Storyblocks, we recently had some major cross-functional accomplishments that required a lot of collaboration and brain power to implement. In the spirit of ‘work hard, play hard’, the Product &amp; Engineering crew wanted to take a moment to celebrate together in recognition of these efforts. Given our remote work setting, we needed a creative way to host a virtual event with an interactive element. I had found a ‘mystery mansion’ template on <a href="https://app.gather.town/app">Gather’s website</a> that I was dying (pun intended) to use, so we decided to organize a virtual murder mystery.</p><h3>Leaning Into AI</h3><p>Since we only had a week to create the event, we used AI as much as possible. We generated most of the narrative using <a href="https://chat.openai.com/">ChatGPT</a>, then added a few extra Storyblocks-specific touches. Additionally, most of the text-based clues were drafted with ChatGPT and the ‘photographs’ we used were created using the <a href="https://www.imagine.art/?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=Web_Ad_G_2.1&amp;utm_term=midjourney&amp;utm_campaign=&amp;utm_source=adwords&amp;utm_medium=ppc&amp;hsa_acc=3029240990&amp;hsa_cam=21104234856&amp;hsa_grp=156859186061&amp;hsa_ad=694026597112&amp;hsa_src=g&amp;hsa_tgt=kwd-1658709827480&amp;hsa_kw=midjourney&amp;hsa_mt=e&amp;hsa_net=adwords&amp;hsa_ver=3&amp;gad_source=1&amp;gclid=CjwKCAjwt-OwBhBnEiwAgwzrUmMUSsNjX3n1jT6nhAvLtydfZchZxRJ9tLq7uH8DBPDx3-6fDcg0gBoC_mwQAvD_BwE">MidJourney AI tool</a> as well.</p><h3>Crafting the Experience</h3><h3>1. The Space</h3><p>Though it’s absolutely possible to create a custom murder mystery space in Gather, we knew that we wanted to use the mystery mansion, which came with built-in rooms and secret passages. We ended up building the narrative and clues around that space. If you want to customize your spatial experience for your own murder mystery event, I recommend doing that first, as the themes and clues can be enriched by the environment your players will be in.</p><figure><img alt="General view of Mystery Mansion Gather space" src="https://cdn-images-1.medium.com/max/1024/1*zIJh9SltnG1LVpFxMzWvXA.png" /><figcaption>General view of Mystery Mansion Gather space</figcaption></figure><h3>2. The Narrative</h3><p>We asked ChatGPT ‘<em>can you come up with a murder mystery narrative that takes place in a mansion?’</em> and it gave us a great multi-paragraph backstory to start with. From there, we asked it to make some alterations, including ‘<em>can you add some unimportant details about the event?’</em>. A few variations of this request gave us a rich backstory with details such as “trays of rare vintage wines and handcrafted cocktails garnished with edible gold leaf” being served at the event prior to the murder. We also added the Shenandoah mountains as the mansion location — a tribute to Northern Virginia, where our home base is located.</p><figure><img alt="AI-generated image of a bloody murder scene" src="https://cdn-images-1.medium.com/max/1024/1*5JB7lKtzorXpOvuK3C3PSg.png" /><figcaption>AI-generated image of a bloody murder scene</figcaption></figure><h3>3. The Investigator Personas</h3><p>Depending on the size of your group, you might be able to build out personas for each individual. Our group was too large (~16 people), so we decided to randomly assign people (using ChatGPT of course) to three distinct groups. We asked ChatGPT ‘<em>can you create three distinct groups of investigators?’</em> and got pretty generic results, so we decided to provide themes that would be easy to put together costumes for. One was a group of recent college grads who wear shirts representing their favorite universities. Another was a group of mediums who wear bright colors. The third was a group of mysterious investigators who wear all black. From there, we tried again, providing the generic themes and requesting unimportant details. ChatGPT provided gems like a pet bat named Midnight for the third group and an “experimental psychedelic rock band” for the second.</p><h3>4. The Suspects</h3><p>Again because of our group size, we wanted to limit the number of possible suspects. We asked ChatGPT to give us a list of five suspects with backstories based on the narrative we had already generated, then changed the suspects’ first names to those of leaders involved in the tasks we were celebrating. Once we had a solid draft, we asked ChatGPT to randomly select the murderer from that list.</p><h3>5. The Rules</h3><p>We made a rules document outlining basics about the event, including how to interact with objects, the timeline of the event and how to submit guesses. We also noted that there would only be one murderer. Guesses and secondary guesses (in case no one guessed correctly) were submitted by a group-appointed captain using a Google form. We also requested supporting logic for each team’s primary guess, which was a fun detail for our moderators to see. We used the following timeline:</p><ul><li>Intro: ~10 minutes</li><li>Team Discussion + Team Captain Selection: ~5 minutes</li><li>Initial Investigation: 15 minutes</li><li>Team Discussion: ~5 minutes</li><li>Final Investigation: 10 minutes</li><li>Final Discussion + Guess Submission: 10 minutes</li><li>Announcement of Winning Team</li></ul><h3>6. The Clues</h3><p>In a traditional game of Clue, timing is a key element: players typically discover clues at a controlled pace. Using Gather as a medium, we had no control over when certain clues were discovered, so we had to account for that with the clues we provided to make identifying the culprit more difficult — but possible. Based on a list of items ChatGPT suggested, we ended up with the following clues:</p><p><strong>Eye Witnesses:</strong> We came up with 12 eye witnesses (including the suspects) to interact with in Gather. Their avatars were created using <a href="https://www.avatarsinpixels.com/">this site</a>, then placed in Gather with prompts and notes we primarily created using ChatGPT.</p><p><strong>Cell Phone:</strong> Missed calls from a suspect could be seen by clicking the phone icon on this phone, which was created using <a href="https://chromewebstore.google.com/detail/google-drawings/mkaakpdehdafacodkgkpghoibnmamcme">Google Drawings</a></p><figure><img alt="Smart phone views of home screen and call log" src="https://cdn-images-1.medium.com/max/492/1*1hNzVZEg_AFlNdPxEf44nw.png" /><figcaption>Smart phone views of home screen and call log</figcaption></figure><p><strong>Custom Gather Objects (created using </strong><a href="https://krita.org/en/"><strong>Krita</strong></a><strong>):</strong></p><ul><li>A pen with a suspect’s company brand was placed at the murder scene</li><li>Blood splatters of various sizes were placed on both ends of a secret passage</li><li>Headphones were placed at the murder scene and, according to an eye witness, belonged to one of the suspects</li><li>A USB on the floor contained a code to unlock a PDF on the victim’s computer</li><li>A diary contained entries mentioning suspicion of one of the suspects</li><li>A surveillance camera, placed in the hallway, showed an image of a suspect leaving the parlor. We edited the image to add text with a timestamp to indicate that this occurred right before the victim was found</li></ul><figure><img alt="AI-generated image of a woman with a serious expression leaving a room" src="https://cdn-images-1.medium.com/max/1024/1*M4RuHXiDz791jTCnBy2hAQ.png" /><figcaption>AI-generated image of a woman with a serious expression leaving a room</figcaption></figure><p><strong>Built-in Gather Objects:</strong></p><ul><li>The laptop object provided a locked PDF with suspicious emails</li><li>The note object provided an angry letter from the victim’s daughter</li><li>The document object was used for financial documents, a pawn shop receipt and a ‘grocery list’ that was actually a coded message</li><li>Blueprints linking to a pdf we created with Google Drawings depicted the secret passageways in the space</li><li>A duffel bag placed near one of the suspect included a prompt noting that the bag was full of the victim’s jewels</li></ul><p>After generating the content of the text-based clues, we formatted and shared them using Google Drive’s ‘publish to the web’ feature. Players could access these documents by interacting with embedded website objects in Gather. For the locked PDF specifically, we had to drop a link to the PDF in a note object to allow players to interact with the PDF.</p><h3>Putting it All Together</h3><p>A few days before the event, two moderators did a walkthrough. Their feedback prompted some slight alterations to the clues to ensure they weren’t too challenging or confusing. This step provided a lot of insight about the difficulty level and flow of the experience.</p><p>We created separate Slack channels for each group, as well as an all-participants Slack channel. These channels were used to introduce people to their teammates and team personas ahead of time. They also enabled everyone to communicate when in different Gather rooms during the event.</p><p>The day of the murder mystery, we played suspenseful music using <a href="https://www.storyblocks.com/audio/search?media-type=music">Storyblocks’ music library</a> and Gather’s ‘self spotlight’ feature as players gathered in their groups. We then did a brief introduction, a dramatic reading of the narrative, and a run-through of the rules.</p><p>Finally, after the players had time to investigate, regroup, then investigate again, we brought everyone together and called for final guesses. In case you were wondering, one group did correctly guess the murderer. More importantly, everyone seemed to have a great time.</p><figure><img alt="Collage of different rooms in the Mystery Mansion Gather space" src="https://cdn-images-1.medium.com/max/1024/1*ZjXghzraTuQBZAmSyW5rIQ.png" /><figcaption>Collage of different rooms in the Mystery Mansion Gather space</figcaption></figure><h3>Conclusion</h3><p>Thanks to the powers of AI and Gather, we were able to throw a somewhat complicated event together in a matter of days! When you lean into modern tools, you can really accomplish a lot in a small amount of time.</p><p>If you want in on our innovative digital culture, <a href="https://www.wearestoryblocks.com/#careers">we are hiring</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a27e233c7498" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/who-killed-sir-redshift-how-we-used-gather-and-ai-to-throw-a-virtual-murder-mystery-party-a27e233c7498">Who Killed Sir Redshift: How We Used Gather and AI to Throw a Virtual Murder Mystery Party</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[4 Steps to Spark Collaboration & Learning in Your Remote Team]]></title>
            <link>https://medium.com/storyblocks-engineering/4-steps-to-spark-collaboration-learning-in-your-remote-team-99afa3734001?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/99afa3734001</guid>
            <category><![CDATA[professional-development]]></category>
            <category><![CDATA[collaboration]]></category>
            <category><![CDATA[remote-learning]]></category>
            <category><![CDATA[engineering]]></category>
            <dc:creator><![CDATA[Ben Oconis]]></dc:creator>
            <pubDate>Fri, 08 Mar 2024 15:47:54 GMT</pubDate>
            <atom:updated>2024-03-08T15:47:54.785Z</atom:updated>
            <content:encoded><![CDATA[<p>How do you promote team bonding and learning in a virtual workspace? Here’s something we do regularly at Storyblocks:</p><p>🎧 <strong>Step 1: Select Engaging Content</strong><br>Every few weeks, pick a podcast or video presentation that piques your team’s interest. Explore topics that align with your goals and spark curiosity.</p><p>👥 <strong>Step 2: Host a Media Meeting</strong><br>Set up a dedicated meeting to watch or listen to the chosen content as a team. This not only creates a shared experience but also allows for valuable discussions during the session.</p><p>📝 <strong>Step 3: Collaborative Note-Taking</strong><br>Encourage everyone to take notes in a shared space during the meeting. This should be informal and relaxed. This serves as a record of individual insights, fostering collaboration and ensuring no ideas slip through the cracks. As a bonus, rate the content to serve as a recommendation for others.</p><p>🔄 <strong>Step 4: Rotate the Curator’s Role</strong><br>After each session let different team members take the lead in selecting the next piece of content. This not only keeps things fresh but also provides diverse perspectives.</p><figure><img alt="Various art and office supplies with a notebook with text underneath" src="https://cdn-images-1.medium.com/max/1024/1*UmT0JzFWd3ZNeozgTKfJkQ.jpeg" /></figure><h3><strong>Benefits of this Approach:</strong></h3><p><strong>Team Bonding</strong>: Shared experiences strengthen team bonds, creating a sense of togetherness even in virtual spaces.</p><p><strong>Continuous Learning</strong>: Regular exposure to new ideas and perspectives keeps the learning curve steep, fostering a culture of continuous improvement.</p><p><strong>Idea Exchange</strong>: The collaborative note-taking process encourages open dialogue, leading to the exchange of thoughts and insights.</p><p>Let us know how you promote learning in your org! If you would like to join us to get your virtual learn on, <a href="https://www.wearestoryblocks.com/#careers">we are hiring</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99afa3734001" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/4-steps-to-spark-collaboration-learning-in-your-remote-team-99afa3734001">4 Steps to Spark Collaboration &amp; Learning in Your Remote Team</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A decade of shade: automated testing from someone who wants the robots to take it already]]></title>
            <link>https://medium.com/storyblocks-engineering/a-decade-of-shade-automated-testing-from-someone-who-wants-the-robots-to-take-it-already-60193d470eab?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/60193d470eab</guid>
            <category><![CDATA[test-automation]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[efficiency]]></category>
            <category><![CDATA[test-driven-development]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Katy Williamson]]></dc:creator>
            <pubDate>Wed, 25 Oct 2023 13:15:45 GMT</pubDate>
            <atom:updated>2023-10-25T13:15:45.010Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve been in the industry of software application development for almost 10 years (wow, old). While I’ve worked on teams of all spots and stripes, I’ve only worked at two companies, and I hope that’s the reason I haven’t seen the mythical unicorn — a Painless Automated Testing Strategy (TM) — in the wild. It’s out there, right? Somebody has it down? It feels like most of us are “still figuring it out” or “just trying to make the old sh** work” so we can get on with the more interesting parts of our day. At Storyblocks, we have mixed feelings. Some good…</p><ul><li>In general, pull requests include tests</li><li>Passing tests is a step in each CICD pipeline</li><li>Our QA team is invested in our automated test coverage</li></ul><p>… and some bad:</p><ul><li>No clear strategy on when to write what type of test</li><li>Some apathy towards writing “good” tests (I feel you)</li><li>A lot of legacy tests that are not good, pin us to old technologies, or flake often, which give us negative feelings about our suite as a whole</li></ul><p>We’ll name our (very real) Painful Automated Testing Strategy “Patsy”. A good name, because Patsy gets blamed for a poor developer experience, lag in getting code to production, and — for all the lines of code that compose her — she never gives us the coverage and confidence we need. When she’s slow, we’re mad. When she doesn’t catch an issue, we’re mad. When we can’t debug her, we’re mad. Poor Patsy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Sg_Hh3945hpB990Z" /><figcaption>Photo by <a href="https://unsplash.com/@stephenleo1982?utm_source=medium&amp;utm_medium=referral">Stephen Leonardi</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Although I’ve seen more bad than good in my career, I’ve developed a few opinions along the way.</p><p>Some of them are about deciding to create new tests that are valuable to you… and worrying about the old stuff later. There’s value in simply <em>not making the problem worse</em>.</p><p>Others are about how to get test writing off my plate — while still writing good code that we’re confident in, of course.</p><h3>Test requirements, not implementation</h3><p>Katy’s simple truths of engineering posit that:</p><ol><li>The same goal can be accomplished in a million different ways. This is beautiful.</li><li>Engineers, as protectors of a secret and powerful realm of knowledge, hold the power to <em>do things how we want</em>. This is also beautiful.</li></ol><p>I got a little righteous there, but what I’m getting at is: we don’t have to make test writing hard on ourselves. Implementation details change over time and shouldn’t be the subject of our tests. If you find yourself writing a test to confirm a certain function is called with certain arguments, what you’re really doing is adding friction for yourself or another engineer when the target code has to change. Think instead about what would make you confident your code is working as designed. That’s often about outputs rather than inputs.</p><p>At a previous job, I worked on a team where our Business Analyst got involved in the testing strategy. Plain english requirements were translated into “it(‘should’)” Cypress test signatures <em>before development started</em><strong><em>,</em></strong> and the manual QA team even scaffolded them in a PR, so that feature engineers could fill them in throughout development. This had a few benefits:</p><ol><li>Non-engineers got way more invested in writing complete requirements, including edge cases.</li><li>Non-engineers were more able to make a priority call when a test broke or failed, because they shared ownership of the test (and the requirements).</li><li>Engineers, an often mischievous breed, were not left to their own devices / allowed to skip tricky tests.</li></ol><p>It was a lot of overhead to get this practice started, but I liked the theory of it. Is it part of your mythical Patsy?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Zll1d9aaVJt4sq_X" /><figcaption>Photo by <a href="https://unsplash.com/@ninjason?utm_source=medium&amp;utm_medium=referral">Jason Leung</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Feed it to the robots</h3><p>I am so ready for robots to take over test writing. When AI is more able than me to analyze requirements and code and generate tests to cover it… boy howdy.</p><p>One thing we’re trying out at Storyblocks is Github Copilot. My copilot AI buddy is getting better every day at using context clues and my personal habits to suggest code snippets. I can definitely see the potential to pass off test-writing responsibility in the future, and focus more on quality and coverage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*06PaoyyD8no0yBuf4NMt1g.png" /><figcaption>Copilot found a gap in my test scaffold!</figcaption></figure><h3>Re-evaluate your feelings about snapshot tests</h3><p>Snapshot testing is an approach to making sure all downstream effects of a change were intended by the developer. By adding a snapshot test, you’re saving an output at a point in time to source control, so that next time you make a change, any impact to that output is clear in a diff. Usually used for DOM elements, they could be used for function outputs or state too.</p><p>Say I added a snapshot test for this element, called SideNav.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7t2Bxsa5WDVI79kMXDg11g.png" /><figcaption>Check out <a href="https://www.maker.storyblocks.com">https://www.maker.storyblocks.com</a> , it’s pretty cool!</figcaption></figure><p>Here’s an old test we have to confirm the right options are shown in SideNav for a Visitor (as opposed to a Member). It’s ok, but it’s actually a pretty lossy test.</p><pre>it(&#39;should show the right set of drawers for a visitor&#39;, async () =&gt; {<br>    renderWrapped(&lt;AssetRailNav /&gt;, { initialState }});<br>    expect(await screen.findByText(&#39;Stock&#39;)).toBeInTheDocument();<br>    expect(await screen.findByText(&#39;Uploads&#39;)).toBeInTheDocument();<br>    expect(await screen.findByText(&#39;Styles&#39;)).toBeInTheDocument();<br>    expect(await screen.findByText(&#39;Text&#39;)).toBeInTheDocument();<br>    expect(await screen.findByText(&#39;Overlays&#39;)).toBeInTheDocument();<br>    expect(await screen.findByText(&#39;Logos&#39;)).toBeInTheDocument();<br>    expect(await screen.findByText(&#39;Folders&#39;)).toBeInTheDocument();<br>    expect(screen.queryByText(&#39;Record&#39;)).not.toBeInTheDocument();<br>    expect(screen.queryByText(&#39;Brands&#39;)).not.toBeInTheDocument();<br>    expect(screen.queryByText(&#39;Templates&#39;)).not.toBeInTheDocument();<br>});</pre><p>It doesn’t make any assertions about the order of SideNav items, which icons go with which labels, or whether the elements are interactive. It also requires manual rewrite if any of the labels change.</p><p>Here’s a snapshot test for the same component.</p><pre>it(&#39;should show the right set of drawers for a visitor&#39;, async () =&gt; {<br>    renderWrapped(&lt;AssetRailNav /&gt;, { initialState }});<br>    expect(wrapper).toMatchSnapshot();<br>});</pre><p>When you run the test, a snapshot is generated, and you add it to git. It holds a lot of information. The items in SideNav are clearly buttons that contain SVGs and labels, in a specific order.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CbH_abjt24m9CLBSYeuhpQ.png" /><figcaption>Beginning of the new snapshot file in GitHub Desktop</figcaption></figure><p>Uh oh, Design wants the items in SideNav to be reordered! Your wish is my command. Make the change in the component, run the test again, and see that the snapshot doesn’t match anymore.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZCGDgx-zJUGfENjcoHUu6Q.png" /><figcaption>Snapshot failure in Terminal</figcaption></figure><p>Looks exactly like I intended! Accept the changes, commit, move on with my life.</p><p>This example doesn’t capture the main complaints I hear, which are: <em>Too many snapshots have to get regenerated too often! The generated files are huge and hard to read! It adds friction to making a code change!</em></p><p>In my opinion, they’re still super readable if you show restraint when generating snapshots. Stick to the component level, don’t snapshot whole web pages. If you’re well-versed in semantic HTML, the diffs should be readable at that level… and if they’re not, either your code or your component is too complex. They could also illuminate something you didn’t mean to change, or help your teammates review your code better.</p><p>Friction is minimal, imo, because visual diffs are the easiest way to understand changes, and updates are as simple as pressing u while the tests are running in watch mode.</p><p>There’s <a href="https://medium.com/@sapegin/whats-wrong-with-snapshot-tests-37fbe20dfe8e">lots more to read about</a> in the debate, but if used responsibly, snapshots can be useful for teams weary of writing custom tests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YjhTZdHFnO1le7kc" /><figcaption>Photo by <a href="https://unsplash.com/@miyaokabob?utm_source=medium&amp;utm_medium=referral">Patrick Miyaoka</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Choose the test type based on the situation</h3><p>Think of a software application as an obstacle course. (We are now obstacle course overlords.) Each time we develop a new feature for the app, we have to run the obstacle course again to make sure it’s working as expected, getting sliced by barbed wire and bruised by mechanical arms with boxing gloves that come out of nowhere. We take the fastest and easiest way through, and just get it behind us, every time.</p><p>But if we write tests, we can more easily observe that the obstacle course is working. Envision our trustworthy tests like robot assistants. The unit tests are drones with mechanical arms, focusing on specific parts of the course and interacting to get different outcomes. The end-to-end test are cyborgs that run the course for us, while we sit in out comfy chairs. We, the overlords, spend less effort and take on less risk.</p><p>One important decision, before test writing starts, is “should it be a unit test, component test, integration test, or end-to-end test?” Here are some questions I ask myself before choosing one:</p><ul><li>Do I want to cover a lot of inputs and verify a lot of outputs? → Probably a unit test. I’ll write it with react testing library and come up with my edge cases ahead of time.</li><li>Do I want to verify different display experiences based on different conditions? → Probably a component test. I’ll make sure I cover both the Visitor and Member experience.</li><li>Would I trust the test result more if it’s based on realistic mock data? → Probably an integration test. I’ll make sure that mock server response is up to date.</li><li>Am I validating a whole user flow? → Probably an end-to-end test. I’ll write it with Cypress and get feedback from the requirements folks.</li></ul><p>There are a lot more distinctions you can draw here too, but deciding which type of test <em>before</em> writing will help you write fewer tests in the long run.</p><h3>Consider smoke tests and sanity tests</h3><p>Some might say Storyblocks has too many tests! This is a rare problem in the engineering world, where tests are often seen as annoying to write and avoided when possible. But nevertheless, here we are. Our various test suites provide a decent amount of coverage and the main complaint is that they take too long to run.</p><p>One approach I’ve seen in the past is splitting tests into a few different levels. The resulting sub-suites can be run in different situations, and even tied to the severity of the situation.</p><p>“Sanity tests” are the smallest possible set of trustworthy tests that cover critical functionality. These should be run before deploying code in the situation of a critical bug or outage. Run time should be on the order of seconds, allowing us to get fixes out faster than our normal pipeline.</p><p>“Smoke tests” is a term I borrowed from a QA team I worked with, roughly translating to “what does the QA team need to check to green-light this [change|feature|release]?” Related to the “Test requirements, not implementation” section above, this is where those who didn’t build the change have input into how to verify the change with confidence. Ideally, the tests that are identified here stack up and are automated over time to bolster the general test suite.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*a7x8tnbyD7xCHwST" /><figcaption>Photo by <a href="https://unsplash.com/@hannahbusing?utm_source=medium&amp;utm_medium=referral">Hannah Busing</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Invest in SDETs</h3><p><em>Taking a moment to shoutout our QA’s, the folks who keep us honest, catch our mistakes with grace, and understand us better than anyone else! You work hard, and we appreciate you.</em></p><p>I’ve always felt weird about engineers writing tests to prove their own code passes. If only I was allowed to grade my own tests in school…</p><p>“Software Development Engineer in Test” is a job title that acknowledges that a trustworthy automated test strategy is a full-time job. Without this specialization, tests are always the first thing to punt under time pressure, and the first thing to blame when bugs sneak through.</p><p>Let’s hire and grow more SDETs, people! Here at SB, our QA team is awesome and hungry to learn coding to support their (our) goals. A little bit of Cypress can go a long way to improving quality, confidence, and the developer experience. Company leadership can help make this time and training a priority.</p><h3>If you ever spot the mythical unicorn…</h3><p>I’m always listening and learning of new ways to make the day-to-day better for software engineers! If you feel good about your testing strategy, please share your lore with others. And if not, know you’re not alone.</p><p>Thanks for reading! Storyblocks is building a <a href="https://www.wearestoryblocks.com/#careers">Talent Community</a> of awesome people like you. You can also reach out in the comments or <a href="https://www.linkedin.com/in/kathryn-concetta-williamson/">find me on LinkedIn</a> to connect.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=60193d470eab" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/a-decade-of-shade-automated-testing-from-someone-who-wants-the-robots-to-take-it-already-60193d470eab">A decade of shade: automated testing from someone who wants the robots to take it already</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How We’ve Leveraged Gather to Enhance Our Remote Culture]]></title>
            <link>https://medium.com/storyblocks-engineering/how-weve-leveraged-gather-to-enhance-our-remote-culture-966b31652697?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/966b31652697</guid>
            <category><![CDATA[remote-working]]></category>
            <category><![CDATA[culture]]></category>
            <category><![CDATA[gather-town]]></category>
            <category><![CDATA[collaboration]]></category>
            <category><![CDATA[engineering]]></category>
            <dc:creator><![CDATA[Katie Young]]></dc:creator>
            <pubDate>Fri, 22 Sep 2023 13:34:22 GMT</pubDate>
            <atom:updated>2023-09-22T13:34:22.274Z</atom:updated>
            <content:encoded><![CDATA[<p>One of my favorite things about Storyblocks is how rounded and multi-passionate our workforce is. Currently, we have on staff an <a href="https://olivialloydmusic.com/">actively-touring musician</a> managing rights and licensing and a product manager who co-owns a gym for adaptive athletes. We have parents more easily juggling school schedules and, of course, pet-parents frequently posting about the cherished moments they would otherwise miss. This is all possible due to the benefits of a remote-first work environment. Despite these benefits, however, there are some aspects of an in-office workday that felt important to preserve. This is where Gather comes in.</p><figure><img alt="Storyblocks’ Gather office" src="https://cdn-images-1.medium.com/max/1024/1*tnLCbzJTtQKKwRroLhe7OA.png" /><figcaption>Storyblocks’ Gather office</figcaption></figure><h3>What is Gather?</h3><p><a href="https://app.gather.town/app">Gather</a> is an app that simulates an in-office experience, with the ability to reserve a desk, personalize the space, and interact remotely without Zoom calls. Each employee who opts in at Storyblocks has their own dedicated desk that they are free to decorate using Gather’s item library.</p><figure><img alt="Coworkers interacting in Gather app" src="https://cdn-images-1.medium.com/max/265/1*k1KrlU-j8UbVnaKffY1oyQ.png" /><figcaption>Coworkers interacting in Gather app</figcaption></figure><p>Additionally, employees can create custom objects, using image editor tools such as Gimp or Krita, that can be imported into the app. Gather has a mapmaker feature that allows users to expand the office, create portals to other rooms, and make a space private (only others in the same space can hear) or spotlight a space (everyone in the room can hear a speaker).</p><h3>Goodbye, Zoom Fatigue</h3><p>We’ve begun using Gather more frequently for meetings. From our Engineering All-Hands (~40+ attendees) to smaller daily standup meetings, we’ve brought back the practice of convening in a shared space, which leads to more organic discussions, better team building, and an overall feeling of connectedness and community within the workplace.</p><figure><img alt="Coworkers attending a meeting in Gather app" src="https://cdn-images-1.medium.com/max/1024/1*TyuZLMDpI-XHEprkklEXmg.png" /><figcaption>Coworkers attending a meeting in Gather app</figcaption></figure><h3>Celebrating Life Events</h3><p>When big life events happen, such as birthdays and weddings, we wanted a way to celebrate. Now, we can decorate each others’ desks for any occasion using custom and interactable objects.</p><figure><img alt="Desk decorations in celebration of a coworker’s wedding" src="https://cdn-images-1.medium.com/max/250/1*bbOGh5nH3N8yMd6i4Z22pA.png" /><figcaption>Desk decorations in celebration of a coworker’s wedding</figcaption></figure><h3>Community Spaces</h3><p>At Storyblocks, we have several active resource groups (BRGs) through which people with shared experiences are able to meet and form community. Out At Storyblocks (OAS), our LGBTIA+ BRG, now has its own meeting space that we recently used to host an OAS luncheon. We also hosted our annual Pride trivia event in Gather this year.</p><figure><img alt="Group of coworkers attending Pride trivia in Gather app" src="https://cdn-images-1.medium.com/max/1024/1*K9yJJtlDltxuJZ2FgU3YTw.png" /><figcaption>Group of coworkers attending Pride trivia in Gather app</figcaption></figure><h3>New Hire Resources</h3><p>For onboarding engineers, we’ve created a room for new hires to hang out in as they set up their environment and complete the onboarding process. This gives them the chance to ask questions and get to know their new teammates in a more organic setting.</p><figure><img alt="A new hire meets their coworkers in Gather app" src="https://cdn-images-1.medium.com/max/269/1*a0MSdDQT8S1geJ6eHP64YQ.png" /><figcaption>A new hire meets their coworkers in Gather app</figcaption></figure><h3>Team Building</h3><p>We meet on Friday afternoons for a remote gaming event that anyone can join. This is a great way to unwind at the end of the week and get to know people throughout the company. Some games we’ve played have included Wavelength, Codenames, Skribbl and Top Four. Gather also has an <a href="https://support.gather.town/hc/en-us/articles/15910382725652-Dungeon-Escape-Map">escape room template</a> that can be used as a fun team building exercise.</p><figure><img alt="A group of coworkers plays games in Gather app" src="https://cdn-images-1.medium.com/max/538/1*HEoa5wPnKnTuwqiQI3GbKg.png" /><figcaption>A group of coworkers plays games in Gather app</figcaption></figure><h3>Celebrating Individuality</h3><p>Gather has given us a new way to express ourselves and be goofy. People have come up with so many creative ways to style their avatars and decorate the office space. We’ve even started up a Storyblocks ‘marching band’ group to rally for birthdays and other events.</p><figure><img alt="A collage of uniquely-decorated desks and spaces in the Gather app" src="https://cdn-images-1.medium.com/max/1024/1*Wc8FwvSY8jdW4DkO5ecLdw.png" /><figcaption>A collage of uniquely-decorated desks and spaces in the Gather app</figcaption></figure><h3>The ‘Gather Keepers’</h3><p>A group of our most Gather-obsessed employees has banded together to encourage use of the app and to continuously develop new ways to apply this resource. Gather Fridays is our most recent initiative where we encourage employees to spend Fridays working from their ‘desks’. The Storyblocks Gather team maintains a list of our custom objects, so that anyone in the company can use these items. Our list also contains special requests for wishlist items, such as BRG logos and desk decorations.</p><p>A Gather office can change and grow — just like an in-person office setting. We just completed the process of designing a new-and-improved layout alongside the Gather team to facilitate even more collaboration, and we plan to iteratively generate feedback for future improvements.</p><h3>A Tool For The Times</h3><p>Gather has introduced new elements of productivity and just-plain-fun to the work day. It’s contributed to employee engagement and collaboration, and has served as a fantastic medium through which we can witness different personalities coming together to build a great product.</p><p><em>Interested in joining our team and getting in on the fun? </em><a href="https://www.wearestoryblocks.com/"><em>We are hiring</em></a><em>!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=966b31652697" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/how-weve-leveraged-gather-to-enhance-our-remote-culture-966b31652697">How We’ve Leveraged Gather to Enhance Our Remote Culture</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Find More Faces Like This]]></title>
            <link>https://medium.com/storyblocks-engineering/find-more-faces-like-this-4738d183b596?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/4738d183b596</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[face-recognition]]></category>
            <category><![CDATA[stock-media]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Greg Swanson]]></dc:creator>
            <pubDate>Tue, 29 Aug 2023 14:00:31 GMT</pubDate>
            <atom:updated>2023-08-29T14:00:31.410Z</atom:updated>
            <content:encoded><![CDATA[<p>Building the backend of Face Search at Storyblocks</p><blockquote>Here at Storyblocks, the Data Science &amp; Machine Learning team is always looking to help our customers find or discover content they love. As the custodians of our search, storyboard, and recommendation experiences, we work to improve how we connect our users to our content — whether we’re improving existing experiences or adding new ones. This post will talk about a search problem we’ve recently solved. We’ve used it to improve an existing button on the site, and you can expect other cool new features based on this solution soon!</blockquote><p>Let’s say you see a movie and like one of the actors in it. For example, the latest Indiana Jones movie (for the sake of the example, ignore the bad reviews). You’re watching and you really like the main character: Indiana Jones. You’d like to watch other movies he’s in because you think those movies would be good, too. <strong>Who plays Indiana Jones? </strong>(again for the sake of this bad example, please pretend you don’t know that it’s Harrison Ford).</p><p>How do you find this out? You wait until the end of the movie and <strong>find out it’s Harrison Ford from the credits (</strong><a href="https://www.google.com/search?q=indiana+jones+and+the+dial+of+destiny&amp;rlz=1C5CHFA_enUS903US903&amp;oq=indiana+jones+and+the+&amp;aqs=chrome.0.35i39i650j46i433i512j69i64j69i57j46i512j69i60j69i61l2.3106j0j7&amp;sourceid=chrome&amp;ie=UTF-8"><strong>or you google the movie title and click the cast button on the search results page</strong></a><strong>)</strong>. Once you know the actor’s name, it becomes really easy to see what other movies they’ve been in, so you can go watch those as well.</p><p>A similar situation can play out in stock media. Someone sees a video with a person they like and they want to see other videos with that same person. Stock media isn’t Hollywood though. How can you find other videos with the same person?</p><p>This post continues below, but here’s the trailer for the Indiana Jones movie if you’re curious:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FeQfMbSe7F2g%3Fstart%3D3%26feature%3Doembed%26start%3D3&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DeQfMbSe7F2g&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FeQfMbSe7F2g%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2b225f9ba0d16effd430f5970321a84f/href">https://medium.com/media/2b225f9ba0d16effd430f5970321a84f/href</a></iframe><h3>The problem</h3><figure><img alt="A red bounding box around Indiana Jones’ (Harrison Ford’s) face." src="https://cdn-images-1.medium.com/max/262/1*AqpuFsEkGPt9bTV3DM2N9A.png" /></figure><blockquote>Have you ever seen an actor in a movie that you liked and wondered what else they’ve been in? Resources like I<a href="https://www.imdb.com/name/nm0000148/?ref_=tt_ov_st">mdb</a>, <a href="https://en.wikipedia.org/wiki/Harrison_Ford_filmography">Wikipedia</a>, and <a href="https://www.google.com/search?q=harrison+ford+movies">Google</a> make it easy to quickly find other movies and tv shows an actor has starred in just by finding their name.</blockquote><figure><img alt="Example search for hollywood movies starring harrison ford. Easy to find because Harrison Ford is credited as being a part of the cast. This isn’t easily done for stock footage without cast lists." src="https://cdn-images-1.medium.com/max/1024/1*fTxFQLH6s2pTvKKV434ImQ.png" /></figure><p>The problem at Storyblocks is that our stock media doesn’t come with a list of credits attached to it (whether that should change is a discussion for another post). This means internally we don’t track which videos have which actors, and worse, <strong>we can’t help our users find more videos of the same person when they are looking for them </strong>— at least not directly.</p><h3>Our old work-around needed improvement</h3><p>While we do have ways for our users to <a href="https://www.storyblocks.com/video/portfolio/CactusVP">look through other videos from contributors</a> or search through similar footage, it’s not easy to quickly find all the videos an actor has shown up in. We don’t have anything that allows direct face searching even though we know anecdotally that we have a bunch of stock actors that appear in multiple places across our library.</p><p><strong>Example Contributor Page:</strong></p><figure><img alt="An example contributor page on our site from “CactusVP” that shows all pieces of content in their uploaded portfolio" src="https://cdn-images-1.medium.com/max/1024/1*0O8PIOWplBmXWYPwEGxkSA.png" /></figure><p><strong>Example “More Like This”:</strong></p><figure><img alt="Our old “more like this” functionality before adding face recognition" src="https://cdn-images-1.medium.com/max/1024/1*dem-yblTAs7DB_vG1LZf0A.png" /></figure><p>Notice how “more like this” only shows videos from the same or a very similar shoot and how the contributor page adds a ton of noise from other videos the contributor has uploaded.</p><h3>A new face-based approach seems to help</h3><p>We’ve just released an update to our “more like this” functionality that goes a long way toward solving this problem. We’re now able to find videos with similar faces (in many cases, this leads to finding all or almost all of the other videos a given actor shows up in). Better yet for our users, <strong>We now have a direct way to help them when they find an actor they like and want to get other videos with the same person.</strong></p><p>This was made possible by clustering face embeddings created for all Storyblocks’ content featuring people. More on that in a moment, but here’s a DAG abstraction:</p><figure><img alt="A dag showing a simple diagram of creating embeddings in one step and then clustering them in the next." src="https://cdn-images-1.medium.com/proxy/1*Pujvv7BsrIHAMLrIVzFymw.png" /></figure><h3>It worked like this:</h3><blockquote>1. It started with some research</blockquote><p><a href="https://paperswithcode.com/task/face-recognition"><strong>paperswithcode.com</strong></a></p><figure><img alt="A screenshot of the Papers with code page on Face Recognition" src="https://cdn-images-1.medium.com/max/1024/1*iQ9DXij5078oXjQkj0Tp9g.png" /></figure><blockquote>2. We lucked out. We already had a pipeline that determined which frames have people in them (it made this new task easier)</blockquote><p>Imagine a semi-magical Airflow DAG that uses computer vision and NLP on our videos and contributor metadata to find frames of videos with people’s faces in them. It would look something like this:</p><figure><img alt="A face Detection Pipeline" src="https://cdn-images-1.medium.com/max/1024/1*cQjPOjZ57RVRAFcfwPMbZw.png" /></figure><blockquote>3. Feed detected faces into a model</blockquote><figure><img alt="A diagram showing how you feed an image of a face through the Facenet Model to get an embedding (i.e. a list of numbers)." src="https://cdn-images-1.medium.com/max/1024/1*L4Fr0Ued-0RntyGFGCUNJQ.png" /><figcaption>Reference: <a href="https://arsfutura-production.s3.us-east-1.amazonaws.com/magazine/2019/10/face_recognition/facenet-brki.png">https://arsfutura-production.s3.us-east-1.amazonaws.com/magazine/2019/10/face_recognition/facenet-brki.png</a></figcaption></figure><blockquote>4. Create an embedding for each face</blockquote><p>Basically this is just a list of numbers that uniquely identify an individual face while also giving an indication of how similar that face is to other faces. Conceptually, this is kind of like DNA or a barcode. How the embedding gets created depends on the approach you take. Google wrote a white paper on their approach <a href="https://arxiv.org/pdf/1503.03832.pdf">here</a>. You could visualize an embedding similar to how it’s done here in the <a href="https://github.com/serengil/deepface">Deepface python package</a>:</p><figure><img alt="Example visualization of a face’s embedding. Notice how this makes it look similar to a bar code." src="https://cdn-images-1.medium.com/max/1024/0*OCczLM9wcETXFS1n.jpg" /><figcaption><a href="https://raw.githubusercontent.com/serengil/deepface/master/icon/embedding.jpg">https://raw.githubusercontent.com/serengil/deepface/master/icon/embedding.jpg</a></figcaption></figure><blockquote>5. Cluster the embeddings to get groups of similar faces</blockquote><p>Assuming the face embeddings are correct and your clustering algorithm is tuned well, each group should represent very similar if not the same person based on their facial characteristics.</p><figure><img alt="An example dendrogram showing how agglomerative clustering works visually." src="https://cdn-images-1.medium.com/max/1024/1*BGs4AuEsBBXcrmK3ul5Tgw.png" /><figcaption><a href="https://towardsdatascience.com/hierarchical-clustering-explained-e59b13846da8">https://towardsdatascience.com/hierarchical-clustering-explained-e59b13846da8</a></figcaption></figure><blockquote>6. Push for it to get on the site as long as it takes to get it on the site</blockquote><p>Work with your team &amp; others to get this deployed on the site. This process is kind of hard to describe. Maybe I’ll talk about it in another post.</p><blockquote>7. Finally, display the results in the “more like this” button</blockquote><p>Here’s that same “more like this” button example from earlier now:</p><figure><img alt="Example from earlier in article is slightly improved with additonal scenes with same actor" src="https://cdn-images-1.medium.com/max/1024/1*7VkSBHMyzhPcp9XbM2GA3g.png" /></figure><p>Other examples from our site:</p><figure><img alt="An example of our algorithm finding other faces in other scenes." src="https://cdn-images-1.medium.com/max/1024/1*PglSpWyE8RXcBfGYjMTYYQ.png" /></figure><figure><img alt="Another example of our algorithm finding the same person in other scenes" src="https://cdn-images-1.medium.com/max/1024/1*Rc2GreyH0ksLDeldkX9-Ig.png" /></figure><figure><img alt="Another example of our algorithm finding the same person in other scenes" src="https://cdn-images-1.medium.com/max/1024/1*RV9-EjWT5keKIF2TexMSgw.png" /></figure><h3>What’s next?</h3><p>While it’s exciting to enable this functionality, there is still work to do to make this feature better, like improving our face detection and face searching capabilities. This has also unlocked ideas for new content-based recommendations, and we know there are many more exciting embedding-based features we could implement for our site — things like reverse image search, reverse face search, or even reverse object search. Exciting stuff, but those are ideas for yet another blog post.</p><p>Thanks for reading! If you made it this far, I added some references at the bottom to help you explore Face Recognition and clustering further. And if you like the work you see us doing and want to do it, too, <a href="https://www.wearestoryblocks.com/">check out our general interest application</a>.</p><p><strong>References:</strong></p><ol><li><a href="https://towardsdatascience.com/hierarchical-clustering-explained-e59b13846da8">https://towardsdatascience.com/hierarchical-clustering-explained-e59b13846da8</a></li><li><a href="https://arxiv.org/pdf/1503.03832.pdf">https://arxiv.org/pdf/1503.03832.pdf</a></li><li><a href="https://arsfutura.com/magazine/face-recognition-with-facenet-and-mtcnn/">https://arsfutura.com/magazine/face-recognition-with-facenet-and-mtcnn/</a></li><li><a href="https://github.com/serengil/deepface">https://github.com/serengil/deepface</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4738d183b596" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/find-more-faces-like-this-4738d183b596">Find More Faces Like This</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Authenticating internal applications on Kubernetes via Okta, Istio, and OAuth2-Proxy]]></title>
            <link>https://medium.com/storyblocks-engineering/authenticating-internal-applications-on-kubernetes-via-okta-istio-and-oauth2-proxy-dc800cbb92a5?source=rss----65d5377a1f7c---4</link>
            <guid isPermaLink="false">https://medium.com/p/dc800cbb92a5</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[istio]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[oauth2-proxy]]></category>
            <dc:creator><![CDATA[Anthony Sung]]></dc:creator>
            <pubDate>Fri, 18 Aug 2023 19:34:07 GMT</pubDate>
            <atom:updated>2023-08-18T19:34:07.021Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Storyblocks has recently moved to using Okta for our IAM (Identity and Access Management) needs. This enabled us to leverage Okta in our Kubernetes cluster for accessing internal applications.</blockquote><figure><img alt="Employee client connecting to internal application in Kubernetes and being authenticated via Okta" src="https://cdn-images-1.medium.com/max/732/1*yiHIReuc2pqLE-S-9AvqDg.png" /><figcaption>Employee client connecting to internal application in Kubernetes and being authenticated via Okta</figcaption></figure><h3><strong>General Flow</strong></h3><ol><li>Employee tries to access an internal application (i.e. admin-service).</li><li>The employee connects to the internal application url and the request is sent to the Istio service mesh inside our Kubernetes cluster.</li><li>We have an Istio authorization policy that limits access to the admin service.</li><li>The authorization policy points uses the custom extension provider, Oauth2-Proxy. Oauth2-Proxy is setup to connect with our identity provider which is Okta.</li><li>The employee’s request is redirected to Okta for login. On successful authentication it passes through the Istio authorization policy and connects with the admin service.</li></ol><h3><strong>Technologies Used</strong></h3><ul><li><a href="https://aws.amazon.com/eks/">AWS EKS Kubernetes</a></li><li><a href="https://istio.io/latest/docs/">Istio</a></li><li><a href="https://github.com/oauth2-proxy/oauth2-proxy">OAuth2-Proxy</a></li><li><a href="https://www.okta.com/">Okta</a></li></ul><p>We found a surprisingly small number of tutorials when trying to set this up ourselves so here is our quick tutorial.</p><h3>1. Create an Okta Application for OAuth2-Proxy</h3><p>This Okta application will allow for access management to your application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VAuFvFBSgb4fHajTla8SCA.png" /><figcaption>OAuth2-Proxy application in Okta</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rNyQzEnBZ2W8cjYuIg0bwg.png" /><figcaption>Okta application settings</figcaption></figure><p>Make sure to add the url to your internal application under the sign-in redirect URI. This will authorize Okta to redirect to your internal app after authenticating an employee.</p><p>You can add other internal app urls to this application, but know that any Okta users/groups you add to this Okta application will be able to access every internal application url that is listed here.</p><p>If you want to separate who has access to an application (i.e. only the finance team can access this application and not engineering), you will need to create a separate OAuth2-Proxy app in Okta for each application you want to customize access to.</p><h3>2. Deploy OAuth2-Proxy to Kubernetes</h3><p>This will be the same process as deploying any other application to Kubernetes; you will just need to add a couple of specific configs. The biggest lesson we learned was that you do NOT need the redirect-url. You will be specifying the redirect URL’s in the Okta application later on. We added clientID, clientSecret, and cookieSecret configs via our secrets management process, but I put it in this yaml excerpt for readability.</p><p>Here’s a <a href="https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider#okta">link</a> to the OAuth2-Proxy and Okta documentation — just ignore the fact that they include the redirect-url in their configuration file.</p><pre># Oauth client configuration specifics<br>oauth2-proxy:<br>  config:<br>    clientID: &lt;SEE OKTA OAUTH2-PROXY APP&gt;<br>    clientSecret: &lt;SEE OKTA OAUTH2-PROXY APP&gt;<br>    cookieSecret: &lt;SEE OKTA OAUTH2-PROXY APP&gt;<br>  extraArgs:<br>    provider: oidc<br>    oidc-issuer-url: &quot;&lt;COMPANY_OKTA_URL&gt;&quot;<br>    pass-access-token: true<br>    skip-provider-button: true<br>    reverse-proxy: true<br>    cookie-domain: <br>      - &lt;WHITELIST_COOKIE_DOMAINS&gt;<br>    email-domain: &quot;*&quot;<br>    whitelist-domain: <br>      - &lt;WHITELIST_DOMAINS&gt;<br>    upstream: static://200<br>    # redirect url - not needed since you will have it in your Okta application</pre><h3>3. Add OAuth2-Proxy as an extension provider in Istio</h3><p>Inside your Istio mesh config, you will need to add an <a href="https://istio.io/latest/docs/tasks/security/authorization/authz-custom/#define-the-external-authorizer">extension provider</a>. This will reference the OAuth2-Proxy deployment and will allow us to use OAuth2-Proxy as a CUSTOM action in our Istio Authorization Policy.</p><pre># Istio extension provider specifics<br>istiod:<br>  meshConfig:<br>    extensionProviders:<br>      - name: &quot;oauth2-proxy&quot;<br>        envoyExtAuthzHttp:<br>          service: &quot;&lt;KUBERNETES SERVICE URL i.e.oauth2-proxy.default.svc.cluster.local&gt;&quot;<br>          port: &quot;80&quot;<br>          includeHeadersInCheck: [&quot;authorization&quot;, &quot;cookie&quot;] # headers sent to the oauth2-proxy in the check request.<br>          headersToUpstreamOnAllow: [&quot;authorization&quot;, &quot;path&quot;, &quot;x-auth-request-user&quot;, &quot;x-auth-request-email&quot;, &quot;x-auth-request-access-token&quot;] # headers sent to backend application when request is allowed.<br>          headersToDownstreamOnDeny: [&quot;content-type&quot;, &quot;set-cookie&quot;] # headers sent back to the client when request is denied.</pre><h3>4. Add Istio Authorization policy</h3><p>This CUSTOM authorization policy allows us to restrict access to the host specified and authenticate via Okta and the OAuth2-Proxy. See the Istio reference <a href="https://istio.io/latest/docs/reference/config/security/authorization-policy/">document</a> for more info.</p><pre># Istio authorization policy<br>apiVersion: security.istio.io/v1beta1<br>kind: AuthorizationPolicy<br>metadata:<br>  name: admin-service-okta-auth-policy<br>  namespace: admin-service<br>spec:<br>  action: CUSTOM<br>  provider:<br>    name: oauth2-proxy # reference oauth2-proxy extension provider<br>  rules:<br>    - to:<br>        - operation:<br>            hosts:<br>              - &lt;INTERNAL APPLICATION HOST&gt;<br>  selector:<br>    matchLabels:<br>      app.kubernetes.io/instance: &lt;KUBERNETES SERVICE NAME i.e. admin-service&gt;<br>      app.kubernetes.io/name: &lt;KUBERNETES SERVICE NAME i.e. admin-service&gt;</pre><h3>5. Enjoy secure access to your internal application!</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KlkxOflfL7dcODTNKHMwuQ.png" /><figcaption>Okta login page when trying to access an internal application</figcaption></figure><blockquote>If this work sounds interesting, consider <a href="https://www.wearestoryblocks.com/">joining our team</a>!</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc800cbb92a5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/authenticating-internal-applications-on-kubernetes-via-okta-istio-and-oauth2-proxy-dc800cbb92a5">Authenticating internal applications on Kubernetes via Okta, Istio, and OAuth2-Proxy</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>