#DailyWatch
When political commentary gets intense, but you’re just trying to keep up with the logic... 🤔 😂

The gang breaks down power moves and testing the waters on the latest episode of Vol Stars (Ep. 53)! 🎙️✨

#VolStars #Discussion #TrendingNow #DailyWatch #PodcastClip #ContentCreator #ListenNow #MustWatch
October 17, 2026 at 2:10 PM
Happy Sunday all!

Wrist check… The Seiko Samurai! 🫢What’s your affordable daily?

#watch #watchhunter #rotorcrown #watchfinder #dailywatch
November 17, 2024 at 6:07 PM
Citizen Attesa CB3010-57A

Silver Dial, Titanium, Sapphire with AR💎, 10 ATM🐳, Eco-Drive🌻, Radio Controlled🗼, Perpetual Calendar🗓️ and Milled Clasp with On The Fly Adjustement.👌
.
.
.
.
#citizenwatch #ecodrive #attesa #dailywatch #madeinjapan #silverdial
November 10, 2025 at 5:38 PM
Transition of the finishes on the case and double-sided AR coating Sapphire crystal are incredible for this price range. 🙌
Blue polished indexes and date window. Glowing hands and indexes lasts all night ! 💪
.
.
.
.
.
.
.
#casiooceanus #waveceptor #toughsolar #neobrite #dailywatch #japanesewatches
October 20, 2025 at 4:00 PM
bob's burgers peaked with the first episode youtu.be/hvcXE6PRm90?...
You're the worst kind of autistic Tina ! Bob's burgers - Human flesh 1080p #shorts
YouTube video by DailyWatch
youtu.be
July 12, 2026 at 6:59 PM
SEIKO SKX009 – Pepsi-ikonet i SKX-uken

Pepsi-bezelen, 7S26A og full ISO-sertifisering.
SKX009 er kanskje den mest gjenkjennelige SKX-en av alle – et ikon siden 1996.

#Seiko #SKX009 #SKX #SeikoDivers #WatchFam #Toolwatch #PepsiBezel #DailyWatch
November 20, 2025 at 11:37 AM
🤣
Bobs burgers - Bob and the deli guy l Is Bob secretly Gay?
YouTube video by DailyWatch
youtu.be
December 23, 2024 at 1:17 AM
A rare Rolex coin watch crafted from an actual 20 dollar gold coin. [⌚️ DailyWatch]
January 29, 2026 at 1:45 PM
This 41mm Oyster Perpetual is proof that the smallest details spark the biggest debates💎
Would you call this “Blue”… or something else?
Let us know below⬇️
#rolexoysterperpetual #luxurywatches #watchcollectors #rolexstyle #dailywatch #austintx #atx #bartoncreek #beecavestx
September 24, 2025 at 8:48 PM
Luxury that speaks for itself. This 2025 White Roman Datejust is elegance redefined💎
Like & follow for more luxury watch content!⏱️
#rolexdatejust #luxurywatches #watchcollectors #swisswatches #dailywatch #luxurylife #austintx #atx #bartoncreek #beecavestx #austinluxury
September 23, 2025 at 9:17 PM
A rare Rolex coin watch crafted from an actual 20 dollar gold coin. [⌚️ DailyWatch]

Original post
May 5, 2026 at 5:37 AM
A rare Rolex coin watch crafted from an actual 20 dollar gold coin. [⌚️ DailyWatch]
January 29, 2026 at 1:45 PM
Can’t decide what to wear? Let your phone decide.

Wristpick is a new app for watch collectors. No ads, no cloud, no account needed. Just your watches.

$0.99 / €0.99 on the App Store. Google Play coming soon.

#wristpick #watchcollection #watchapp #dailywatch #watchenthusiast #watchoftheday
March 19, 2026 at 11:56 AM
Building a Video Thumbnail Generator Service in Go With FFmpeg and Sprite Sheets
A user lands on a video page, hovers over the scrubber, and expects a preview frame to appear instantly. Under the hood, that tiny preview is one of the most expensive things a video platform produces. FFmpeg has to seek into a container, decode a keyframe, scale it, and encode a still image. Do that naively — one FFmpeg process per request, on the fly — and a modest traffic spike will pin every CPU core and turn your page loads into 8-second waits. I run DailyWatch, a free video discovery platform, and thumbnails are the single most-requested asset on the whole site: poster frames for the grid, hover-preview sprite sheets for the player, and Open Graph images for social shares. This post is a walkthrough of the Go service I built to generate all three, why FFmpeg's `-ss` placement matters more than any other flag, how to build a sprite sheet in one pass, and how to keep the whole thing from melting under load. Everything here is runnable — no pseudocode. ## The problem with generating thumbnails on request The first version of any thumbnail system tends to look like this: a request comes in for `/thumb/abc123.jpg`, the handler shells out to FFmpeg, waits, and streams the result. It works in development with one user. In production it falls over for three reasons: * **FFmpeg is CPU-bound and unbounded.** Every concurrent request spawns a decoder. Ten concurrent requests on a 4-core box means 10 decoders fighting over 4 cores, and each one gets slower, which increases concurrency, which is a feedback loop straight into an outage. * **Seeking is not free.** Decoding a frame 40 minutes into a 1080p H.264 file means the decoder may have to walk forward from the nearest keyframe. Get the flag order wrong and a single frame extract takes 6 seconds instead of 50 milliseconds. * **You regenerate the same frame forever.** The poster for a given video never changes. Computing it per request is pure waste. The fix is architectural, not a faster flag: generate thumbnails **once** , asynchronously, into content-addressed storage, and let a cache serve them. The Go service below is a worker that pulls jobs, runs FFmpeg with the right flags, writes artifacts, and records what it produced. The web tier only ever serves static files. ## Fast, correct frame extraction with FFmpeg The most important thing to understand about FFmpeg seeking is the difference between putting `-ss` **before** `-i` (input seeking) and **after** `-i` (output seeking). * `-ss` **before** `-i` seeks by jumping to the nearest keyframe before decoding starts. It is fast — often O(1) — because it uses the container index. This is what you want for thumbnails. * `-ss` **after** `-i` decodes every frame from the start of the file and discards them until it reaches the timestamp. It is frame-accurate but can be catastrophically slow on long files. For a preview frame, you do not care whether you land on the exact millisecond — you care that it is fast and that the frame is not black. So use input seeking, and pick a timestamp a little way into the video (the first few seconds of many videos are logos or black fades). Here is the core extraction command I use for a single poster frame at a percentage of the video's duration: ffmpeg -hide_banner -loglevel error \ -ss 12.5 \ -i input.mp4 \ -frames:v 1 \ -vf "scale=640:-2:force_original_aspect_ratio=decrease,setsar=1" \ -q:v 3 \ -y poster.jpg A few notes on the flags that matter: * `-frames:v 1` grabs exactly one frame and exits. Do not use `-vframes` (deprecated alias). * `scale=640:-2` scales to 640px wide and computes a height divisible by 2 (encoders hate odd dimensions). `force_original_aspect_ratio=decrease` keeps it from stretching. * `-q:v 3` is a quality scale for MJPEG where lower is better (2–5 is a good range). It gives smaller files than a fixed bitrate. * `-hide_banner -loglevel error` keeps stderr clean so you can actually parse real errors. To know _where_ 12.5 seconds falls proportionally, you need the duration first. `ffprobe` gives it to you as machine-readable JSON: ffprobe -hide_banner -loglevel error \ -show_entries format=duration \ -of json input.mp4 That returns something like `{"format": {"duration": "642.360000"}}`. We'll parse that in Go and pick a sensible offset. ## Wrapping FFmpeg in a Go service Go is a good fit here for one specific reason: `os/exec` combined with `context.Context` gives you clean per-job timeouts and cancellation. If a corrupt file makes FFmpeg hang, the context kills the process instead of leaking a worker forever. Here is the probe-and-extract core. It reads the duration, computes an offset, and extracts a single frame with a hard timeout: package thumb import ( "context" "encoding/json" "fmt" "os/exec" "strconv" "time" ) type probeResult struct { Format struct { Duration string `json:"duration"` } `json:"format"` } // Duration returns the length of the video in seconds. func Duration(ctx context.Context, path string) (float64, error) { cmd := exec.CommandContext(ctx, "ffprobe", "-hide_banner", "-loglevel", "error", "-show_entries", "format=duration", "-of", "json", path, ) out, err := cmd.Output() if err != nil { return 0, fmt.Errorf("ffprobe: %w", err) } var r probeResult if err := json.Unmarshal(out, &r); err != nil { return 0, fmt.Errorf("parse probe: %w", err) } d, err := strconv.ParseFloat(r.Format.Duration, 64) if err != nil { return 0, fmt.Errorf("parse duration %q: %w", r.Format.Duration, err) } return d, nil } // Poster extracts a single JPEG frame at ~20% into the video (skipping // intros) and writes it to dst. It enforces a hard timeout so a bad // file can never wedge a worker. func Poster(parent context.Context, src, dst string, width int) error { ctx, cancel := context.WithTimeout(parent, 30*time.Second) defer cancel() dur, err := Duration(ctx, src) if err != nil { return err } // Seek to 20% in, but never before 3s and never past 60s. offset := dur * 0.20 if offset < 3 { offset = 3 } if offset > 60 { offset = 60 } vf := fmt.Sprintf( "scale=%d:-2:force_original_aspect_ratio=decrease,setsar=1", width, ) cmd := exec.CommandContext(ctx, "ffmpeg", "-hide_banner", "-loglevel", "error", "-ss", strconv.FormatFloat(offset, 'f', 3, 64), "-i", src, "-frames:v", "1", "-vf", vf, "-q:v", "3", "-y", dst, ) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("ffmpeg poster: %w: %s", err, out) } return nil } The pattern to internalize: `exec.CommandContext` ties the process lifetime to the context. When the timeout fires, Go sends `SIGKILL` to FFmpeg and `CombinedOutput` returns. No zombie processes, no leaked file descriptors. ## Building a hover-preview sprite sheet in one pass The scrubber-preview feature needs many small frames — one every few seconds across the whole video. Extracting them one FFmpeg call at a time would be dozens of process spawns per video. Instead, extract them all in a single pass with the `fps` and `tile` filters, producing one **sprite sheet** : a grid of small thumbnails packed into a single image. This command samples one frame every 10 seconds, scales each to 160px wide, and tiles them into a 5-column grid: ffmpeg -hide_banner -loglevel error \ -i input.mp4 \ -vf "fps=1/10,scale=160:-2,tile=5x0" \ -frames:v 1 \ -q:v 4 \ -y sprite.jpg The magic is `tile=5x0`. The `0` for rows tells FFmpeg to use as many rows as needed to fit every sampled frame, and `-frames:v 1` because after tiling, the entire grid _is_ a single output frame. One process, one file, all your preview frames. The front end then needs to know the geometry to crop the right cell. Store the metadata alongside the image: interval, cell width/height, and columns. Given a playback time `t`, the cell index is `floor(t / interval)`, and its pixel position is `(index % cols) * cellW, (index / cols) * cellH`. Here is the Go function that generates the sprite and returns the metadata to persist: type Sprite struct { Interval int `json:"interval"` // seconds between frames CellWidth int `json:"cellWidth"` Cols int `json:"cols"` Count int `json:"count"` // total frames in the sheet } func BuildSprite(parent context.Context, src, dst string) (*Sprite, error) { ctx, cancel := context.WithTimeout(parent, 120*time.Second) defer cancel() dur, err := Duration(ctx, src) if err != nil { return nil, err } const interval = 10 const cols = 5 const cellW = 160 count := int(dur)/interval + 1 vf := fmt.Sprintf("fps=1/%d,scale=%d:-2,tile=%dx0", interval, cellW, cols) cmd := exec.CommandContext(ctx, "ffmpeg", "-hide_banner", "-loglevel", "error", "-i", src, "-vf", vf, "-frames:v", "1", "-q:v", "4", "-y", dst, ) if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("ffmpeg sprite: %w: %s", err, out) } return &Sprite{ Interval: interval, CellWidth: cellW, Cols: cols, Count: count, }, nil } One caveat worth knowing: `fps=1/10` still requires FFmpeg to decode the full video, because it samples output frames at a rate rather than seeking. For a 2-hour source this is the expensive part, which is exactly why this belongs in an async worker and not a request handler. ## Bounding concurrency so FFmpeg never wins The single most important production decision is limiting how many FFmpeg processes run at once. A weighted semaphore does this cleanly. Size it to `NumCPU - 1` so the OS and your Go runtime keep a core, and jobs queue instead of thrashing. package worker import ( "context" "runtime" "golang.org/x/sync/semaphore" ) type Pool struct { sem *semaphore.Weighted } func NewPool() *Pool { n := int64(runtime.NumCPU() - 1) if n < 1 { n = 1 } return &Pool{sem: semaphore.NewWeighted(n)} } // Run blocks until a slot is free, then executes fn. If ctx is // cancelled while waiting, it returns without running fn. func (p *Pool) Run(ctx context.Context, fn func() error) error { if err := p.sem.Acquire(ctx, 1); err != nil { return err } defer p.sem.Release(1) return fn() } Every FFmpeg call goes through `pool.Run`. When all slots are busy, new jobs wait in `Acquire` instead of spawning. You trade latency for stability — which is the correct trade for asynchronous work nobody is watching in real time. A few more guardrails I learned the hard way: * **Content-address the output.** Name files by a hash of `videoID + variant + params`. If the params never change, the filename never changes, and a CDN can cache it forever with an immutable header. * **Write to a temp file, then rename.** FFmpeg writing directly to the final path means a crash leaves a truncated, half-decoded JPEG that your CDN happily caches. Write to `dst.tmp` and `os.Rename` on success — rename is atomic on the same filesystem. * **Validate output size.** A 0-byte or suspiciously tiny JPEG usually means a black frame or a decode failure. Check `os.Stat` size before declaring success and retry with a different offset. ## Serving the artifacts from the PHP tier On DailyWatch the application is PHP 8.4 behind LiteSpeed and Cloudflare, with SQLite (using FTS5 for search) as the metadata store. The Go worker's only job is to produce files and record a row; the PHP tier serves them. Keeping generation and serving in separate tiers means a burst of new videos never slows page rendering. When the worker finishes, it records the artifact so the front end knows the sprite geometry. A minimal PHP endpoint that returns the metadata for the player looks like this: <?php declare(strict_types=1); // GET /api/preview.php?v=abc123 $videoId = $_GET['v'] ?? ''; if ($videoId === '' || !preg_match('/^[a-zA-Z0-9_-]{1,32}$/', $videoId)) { http_response_code(400); exit; } $db = new PDO('sqlite:/var/data/videos.db'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $db->prepare( 'SELECT sprite_path, interval_s, cell_w, cols, frame_count FROM thumbnails WHERE video_id = :v LIMIT 1' ); $stmt->execute([':v' => $videoId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row === false) { http_response_code(404); exit; } // Immutable: the sprite for a video never changes once generated. header('Content-Type: application/json'); header('Cache-Control: public, max-age=31536000, immutable'); echo json_encode([ 'sprite' => '/thumbs/' . $row['sprite_path'], 'interval' => (int) $row['interval_s'], 'cellW' => (int) $row['cell_w'], 'cols' => (int) $row['cols'], 'count' => (int) $row['frame_count'], ], JSON_THROW_ON_ERROR); Because the sprite image itself is a static file under `/thumbs/`, LiteSpeed serves it directly from disk and Cloudflare caches it at the edge with the `immutable` directive. The origin does almost no work per request — the expensive FFmpeg pass ran once, hours earlier, in the Go worker. The player-side math to show the right frame on hover is small: function cellFor(time, meta) { const i = Math.min(Math.floor(time / meta.interval), meta.count - 1); const x = (i % meta.cols) * meta.cellW; const y = Math.floor(i / meta.cols) * Math.round(meta.cellW * 9 / 16); return `background-position: -${x}px -${y}px`; } ## What I would tell my past self If you are building this, the ordering of decisions that actually matter is: * **Generate once, serve static.** Never run FFmpeg inside a request handler. The web tier should only ever read files. * **Input-seek for single frames, decode-and-sample for sprites.** Know which one each job needs; the performance gap is 100x on long videos. * **Bound concurrency with a semaphore sized to your cores.** This one change is the difference between a service that queues gracefully and one that falls over. * **Content-address and write atomically.** Immutable filenames plus temp-file renames give you free CDN caching and crash safety at the same time. * **Put timeouts on everything.** `context.WithTimeout` around every `exec.CommandContext` means a single malformed upload can never take down a worker. The result on DailyWatch is a thumbnail pipeline that produces poster frames, hover sprites, and share images for every new video during ingest, stores them content-addressed, and serves them as immutable static files. FFmpeg does the hard work exactly once, Go keeps it bounded and cancellable, and the PHP tier never spawns a subprocess in the hot path. That separation — expensive generation offline, cheap serving online — is the whole trick, and it scales far better than any single clever FFmpeg flag ever could.
dev.to
July 15, 2026 at 6:14 AM