> ## Documentation Index
> Fetch the complete documentation index at: https://docs.videodb.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Word Counter

> Visualize keyword occurrences in videos

<a href="https://colab.research.google.com/github/video-db/videodb-cookbook/blob/main/examples/Keyword_Search_Counter.ipynb" target="_blank">
  <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" noZoom />
</a>

### Introduction

With an endless stream of new video content on our feeds, engaging the audience with dynamic visual elements can make educational and promotional videos much more impactful. VideoDB's suite of features allows you to enhance videos with programmatic editing.

In this tutorial, we'll explore how to create a video that visually counts and displays instances of a specified word as it's spoken. We'll use VideoDB's [Keyword Search](/examples-and-tutorials/video-rag/keyword-search) to index spoken words, and then apply audio and [text overlays](/pages/act/programmable-editing/text-asset) to show a counter updating in real-time with synchronized audio cues.

## Setup

### Installing packages

<CodeGroup>
  ```python Python theme={null}
  !pip install videodb
  ```

  ```javascript Node.js theme={null}
  npm install videodb
  ```
</CodeGroup>

### API Keys

Before proceeding, ensure access to [VideoDB](https://videodb.io) and set up

Get your API key from [VideoDB Console](https://console.videodb.io/). ( Free for first 50 uploads, No credit card required)

## Steps

### Step 1: Connect to VideoDB

Establish a session for uploading videos. Import the necessary modules from VideoDB library to access functionalities.

<CodeGroup>
  ```python Python theme={null}
  import videodb

  # Set your API key
  api_key = "your_api_key"

  # Connect to VideoDB
  conn = videodb.connect(api_key=api_key)
  coll = conn.get_collection()
  ```

  ```javascript Node.js theme={null}
  import { connect } from 'videodb';

  const conn = await connect({ apiKey: process.env.VIDEO_DB_API_KEY });
  const coll = await conn.getCollection();
  ```
</CodeGroup>

### Step 2: Upload Video

Upload and play the video to ensure it's correctly loaded. We'll be using [this video](https://www.youtube.com/watch?v=Js4rTM2Z1Eg) for the purpose of this tutorial.

<CodeGroup>
  ```python Python theme={null}
  video = coll.upload(url="https://www.youtube.com/watch?v=Js4rTM2Z1Eg")
  video.play()
  ```

  ```javascript Node.js theme={null}
  const video = await coll.uploadURL({ url: "https://www.youtube.com/watch?v=Js4rTM2Z1Eg" });
  console.log(video.playerUrl);
  ```
</CodeGroup>

### Step 3: Indexing Spoken Words

Index the video to identify and timestamp all spoken words.

<CodeGroup>
  ```python Python theme={null}
  video.index_spoken_words()
  ```

  ```javascript Node.js theme={null}
  await video.indexSpokenWords();
  ```
</CodeGroup>

### Step 4:  Keyword Search

Search within the video for the keyword *("education" in this example)*, and note each occurrence.

<CodeGroup>
  ```python Python theme={null}
  from videodb import SearchType

  result = video.search(query="education", search_type=SearchType.keyword)
  ```

  ```javascript Node.js theme={null}
  import { SearchTypeValues } from 'videodb';

  const result = await video.search({
      query: "education",
      searchType: SearchTypeValues.keyword
  });
  ```
</CodeGroup>

### Step 5:  Setup Timeline and Audio

Initialize the timeline and prepare an audio asset to use for each word occurrence.

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import Timeline, Track, Clip, AudioAsset, VideoAsset, TextAsset
  from videodb.editor import Font, Background, Alignment, HorizontalAlignment, VerticalAlignment, Position, Offset
  from videodb import MediaType

  timeline = Timeline(conn)

  # Upload the twink sound effect
  audio = coll.upload(url="https://github.com/video-db/videodb-cookbook-assets/raw/main/audios/twink.mp3", media_type=MediaType.audio)
  ```

  ```javascript Node.js theme={null}
  import {
      EditorTimeline, Track, Clip, EditorAudioAsset, EditorVideoAsset, EditorTextAsset,
      Font, Background, Alignment, HorizontalAlignment, VerticalAlignment, Position, Offset
  } from 'videodb';
  import { MediaType } from 'videodb';

  const timeline = new EditorTimeline(conn);

  // Upload the twink sound effect
  const audio = await coll.uploadURL({
      url: "https://github.com/video-db/videodb-cookbook-assets/raw/main/audios/twink.mp3",
      mediaType: MediaType.audio
  });
  ```
</CodeGroup>

### Step 6:  Overlay Text and Audio

Add text and audio overlays at each instance where the word is spoken using the `Track` and `Clip` pattern.

Note: Adding the 'padding' is an optional step. It helps in adding a little more context to the exact instance identified, thus resulting in a better compiled output.

<CodeGroup>
  ```python Python theme={null}
  video_duration = min(300, int(video.length))  # First 5 minutes only
  audio_offset = 1  # Delay audio/text update by 1 second for better sync

  # Create timeline and tracks
  timeline = Timeline(conn)
  video_track = Track()
  text_track = Track()
  audio_track = Track()

  # Add video clip (first 5 minutes)
  video_clip = Clip(
      asset=VideoAsset(id=video.id, start=0),
      duration=video_duration)
  video_track.add_clip(0, video_clip)

  # Filter shots within our duration
  shots_in_range = [s for s in result.shots if int(s.start) + audio_offset < video_duration]

  # Add text overlays that update at each word occurrence
  for i, shot in enumerate(shots_in_range):
      trigger_time = int(shot.start) + audio_offset

      # Initial "Count-0" from start until first word
      if i == 0 and trigger_time > 0:
          text_asset = TextAsset(
              text="Count-0",
              font=Font(family="Do Hyeon", size=72, color="#000100"),
              background=Background(color="#F702A4", opacity=1.0),
              alignment=Alignment(horizontal=HorizontalAlignment.right, vertical=VerticalAlignment.top),)
          text_clip = Clip(asset=text_asset, duration=trigger_time,
            position=Position.top_right, offset=Offset(x=-0.05, y=0.05))
          text_track.add_clip(0, text_clip)

      # Duration until next word or end of video
      if i + 1 < len(shots_in_range):
          next_trigger = int(shots_in_range[i + 1].start) + audio_offset
      else:
          next_trigger = video_duration

      text_dur = next_trigger - trigger_time

      # Text overlay with updated count
      text_asset = TextAsset(
          text=f"Count-{i + 1}",
          font=Font(family="Do Hyeon", size=72, color="#000100"),
          background=Background(color="#F702A4", opacity=1.0),
          alignment=Alignment(horizontal=HorizontalAlignment.right, vertical=VerticalAlignment.top),)
      text_clip = Clip(asset=text_asset, duration=text_dur, position=Position.top_right, offset=Offset(x=-0.05, y=0.05))
      text_track.add_clip(trigger_time, text_clip)

      # Audio cue at same trigger time
      if trigger_time < video_duration - 2:
          audio_clip = Clip(asset=AudioAsset(id=audio.id), duration=2)
          audio_track.add_clip(trigger_time, audio_clip)

  # Add all tracks to timeline
  timeline.add_track(video_track)
  timeline.add_track(text_track)
  timeline.add_track(audio_track)
  ```

  ```javascript Node.js theme={null}
  const videoDuration = Math.min(300, parseInt(video.length));  // First 5 minutes only
  const audioOffset = 1;  // Delay audio/text update by 1 second for better sync

  // Create timeline and tracks
  const timeline = new EditorTimeline(conn);
  const videoTrack = new Track();
  const textTrack = new Track();
  const audioTrack = new Track();

  // Add video clip (first 5 minutes)
  const videoClip = new Clip({
      asset: new EditorVideoAsset({ id: video.id, start: 0 }),
      duration: videoDuration
  });
  videoTrack.addClip(0, videoClip);

  // Filter shots within our duration
  const shotsInRange = result.shots.filter(s => parseInt(s.start) + audioOffset < videoDuration);

  // Add text overlays that update at each word occurrence
  shotsInRange.forEach((shot, i) => {
      const triggerTime = parseInt(shot.start) + audioOffset;

      // Initial "Count-0" from start until first word
      if (i === 0 && triggerTime > 0) {
          const textAsset = new EditorTextAsset({
              text: "Count-0",
              font: new Font({ family: "Do Hyeon", size: 72, color: "#000100" }),
              background: new Background({ color: "#F702A4", opacity: 1.0 }),
              alignment: new Alignment({ horizontal: HorizontalAlignment.right, vertical: VerticalAlignment.top })
          });
          const textClip = new Clip({
              asset: textAsset,
              duration: triggerTime,
              position: Position.topRight,
              offset: new Offset({ x: -0.05, y: 0.05 })
          });
          textTrack.addClip(0, textClip);
      }

      // Duration until next word or end of video
      const nextTrigger = i + 1 < shotsInRange.length
          ? parseInt(shotsInRange[i + 1].start) + audioOffset
          : videoDuration;

      const textDur = nextTrigger - triggerTime;

      // Text overlay with updated count
      const textAsset = new EditorTextAsset({
          text: `Count-${i + 1}`,
          font: new Font({ family: "Do Hyeon", size: 72, color: "#000100" }),
          background: new Background({ color: "#F702A4", opacity: 1.0 }),
          alignment: new Alignment({ horizontal: HorizontalAlignment.right, vertical: VerticalAlignment.top })
      });
      const textClip = new Clip({
          asset: textAsset,
          duration: textDur,
          position: Position.topRight,
          offset: new Offset({ x: -0.05, y: 0.05 })
      });
      textTrack.addClip(triggerTime, textClip);

      // Audio cue at same trigger time
      if (triggerTime < videoDuration - 2) {
          const audioClip = new Clip({
              asset: new EditorAudioAsset({ id: audio.id }),
              duration: 2
          });
          audioTrack.addClip(triggerTime, audioClip);
      }
  });

  // Add all tracks to timeline
  timeline.addTrack(videoTrack);
  timeline.addTrack(textTrack);
  timeline.addTrack(audioTrack);
  ```
</CodeGroup>

### Step 7: Generate and Play the Stream

Finally, generate a streaming URL for your edited video and play it.

<CodeGroup>
  ```python Python theme={null}
  from videodb import play_stream

  stream_url = timeline.generate_stream()
  play_stream(stream_url)
  ```

  ```javascript Node.js theme={null}
  const streamUrl = await timeline.generateStream();
  console.log(streamUrl);
  ```
</CodeGroup>

Here's a preview of showing occurrence of the word **Education**

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/riw1SZSlsU4" title="Word Counter Video Output" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

### Conclusion

This tutorial showcases VideoDB’s capabilities to create a video that programmatically counts and displays the frequency of a specific keyword spoken throughout the video. This method can be adapted for various applications where dynamic text overlays add significant value to video content.

### Tips and Tricks

* Use different text styles and positions based on your video's theme.
* Add background sounds or effects to enhance the viewer's experience.

<Card icon="notebook" title="Explore Full Notebook" href="https://colab.research.google.com/github/video-db/videodb-cookbook/blob/main/examples/Keyword_Search_Counter.ipynb">
  Open the complete implementation in Google Colab with all code examples.
</Card>

## Related Tutorials

<CardGroup cols={2}>
  <Card title="Brand Elements" icon="image" href="/examples-and-tutorials/programmatic-editing/brand-elements">
    Add logos, watermarks, and graphics
  </Card>

  <Card title="Audio Overlay" icon="volume-2" href="/examples-and-tutorials/programmatic-editing/audio-overlay">
    Add background music and sound effects
  </Card>
</CardGroup>
