Skip to content

Commit b3e7944

Browse files
authored
feat! Improve chunk generation speed, watermark rendering, and visual grading (#20)
* feat: Enhance audio management and dashboard UI - Introduced functions to format audio durations and manage audio file caching, improving audio file handling. - Updated the dashboard to display audio files with their durations, allowing for better user interaction. - Added filtering and sorting options for audio files based on duration, enhancing usability. - Improved the layout of the stats page and dashboard for a more cohesive user experience. * feat: Enhance chunk metadata handling and dashboard improvements - Introduced caching for chunk metadata to optimize performance and reduce redundant file reads. - Added functions to normalize source video entries and parse chunk metadata from .meta.json files. - Updated the dashboard to improve the display of source videos and model information. - Enhanced the video generation script to support a new video wall layout for multi-clip displays. - Improved error handling and user feedback in the chunk generation process. - Added a configuration option for video wall layout in the Docker setup. * feat: Enhance audio management and dashboard UI - Added timezone configuration to Docker and environment files for consistent time handling. - Improved audio stats tracking by normalizing audio entries and formatting display times. - Updated the stats page to reflect changes in audio play counts and durations, enhancing user experience. - Introduced caching mechanisms for video durations and model labels to optimize performance during chunk generation.
1 parent f1fe5b2 commit b3e7944

9 files changed

Lines changed: 419 additions & 139 deletions

File tree

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ PORT=8081
2525
# HLS streaming port (for VLC, Safari, or external players)
2626
HLS_PORT=8082
2727

28+
# Container timezone (used by cron run history, chunk metadata timestamps, UI time display)
29+
# TZ=Europe/Amsterdam
30+
2831

2932
# ── 3. Chunk Generation Tuning ───────────────────────────────────
3033
# Hardware Acceleration: "nvidia" for GPU acceleration or "none" for CPU encoding
@@ -53,3 +56,6 @@ CLIP_MAX=6
5356
# "Model - <url>" from descriptions. Requires both URL and token.
5457
# TUBEARCHIVIST_URL=https://ta.example.com
5558
# TUBEARCHIVIST_TOKEN=your_api_token
59+
# If TubeArchivist returns no model, use this as the watermark text (optional).
60+
# Test mode (/generate_chunk.sh test) uses "Sample watermark" when both are empty.
61+
# WATERMARK_FALLBACK=My channel

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ build/
1414

1515
.cursor/
1616
.cursorrules
17+
*.mdc
1718

1819
# Configuration
1920
.env
@@ -45,3 +46,4 @@ chunks/
4546
stats/
4647

4748
deploy/
49+

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ FROM python:3.11-slim
44
RUN apt-get update && apt-get install -y \
55
ffmpeg \
66
curl \
7+
tzdata \
78
&& rm -rf /var/lib/apt/lists/*
89

910
# Set working directory
@@ -25,6 +26,7 @@ EXPOSE 8080
2526

2627
# Set Python to unbuffered mode for logs
2728
ENV PYTHONUNBUFFERED=1
29+
ENV TZ=Europe/Amsterdam
2830

2931
# Switch to non-root user
3032
USER appuser

Dockerfile.generator

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
FROM linuxserver/ffmpeg:version-6.1.1-cli
22

3-
# Install required packages for generation script + segment tracker (Python) + font for drawtext
4-
RUN apt-get update && apt-get install -y bash bc python3 fonts-dejavu-core fontconfig && \
3+
# Install required packages for generation script + segment tracker (Python) + fonts.
4+
# Install distro ffmpeg so drawtext is available in the runtime binary.
5+
RUN apt-get update && apt-get install -y bash bc python3 ffmpeg fonts-dejavu-core fontconfig tzdata && \
56
apt-get clean && rm -rf /var/lib/apt/lists/*
7+
ENV PATH="/usr/bin:${PATH}"
8+
ENV TZ=Europe/Amsterdam
69

710
# Suppress "Cannot load default config file" from libass/subtitles filter
811
RUN mkdir -p /etc/fonts

clip_pusher.py

Lines changed: 136 additions & 81 deletions
Large diffs are not rendered by default.

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ services:
4141
- ${STATS_DIR:-./stats}:/app/stats
4242
- trigger_volume:/app/trigger
4343
environment:
44+
- TZ=${TZ:-Europe/Amsterdam}
4445
- CHUNK_DURATION=${CHUNK_DURATION:-300}
4546
- CLIP_MIN=${CLIP_MIN:-5}
4647
- CLIP_MAX=${CLIP_MAX:-10}
@@ -87,6 +88,7 @@ services:
8788
- ./templates:/app/templates:ro
8889
- /var/run/docker.sock:/var/run/docker.sock
8990
environment:
91+
- TZ=${TZ:-Europe/Amsterdam}
9092
- CHUNK_FOLDER=/chunks
9193
- PORT=8080
9294
- EXTERNAL_PORT=${PORT:-8081}

generate_chunk.sh

Lines changed: 196 additions & 51 deletions
Large diffs are not rendered by default.

scripts/segment_tracker.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,59 @@ def pick_start(
106106
return random.randint(fallback_min, fallback_max)
107107

108108

109+
def pick_and_record_start(
110+
json_path: str,
111+
video_path: str,
112+
duration_sec: float,
113+
clip_len_sec: int,
114+
zone_min: Optional[float] = None,
115+
zone_max: Optional[float] = None,
116+
) -> int:
117+
"""Pick a start and immediately record [start, start+clip_len] in one file read/write cycle."""
118+
data = load_used(json_path)
119+
used = data.get("videos", {}).get(video_path, [])
120+
duration_sec = max(0, duration_sec)
121+
clip_len_sec = max(1, clip_len_sec)
122+
max_start = int(duration_sec - clip_len_sec)
123+
if max_start <= 0:
124+
start = 0
125+
else:
126+
free = free_intervals(duration_sec, used)
127+
long_enough = [(a, b) for a, b in free if (b - a) >= clip_len_sec]
128+
129+
if zone_min is not None and zone_max is not None:
130+
z_min, z_max = float(zone_min), float(zone_max)
131+
long_enough = [
132+
(max(a, z_min), min(b - clip_len_sec, z_max))
133+
for a, b in long_enough
134+
if a <= z_max and b >= z_min + clip_len_sec
135+
]
136+
long_enough = [(a, b) for a, b in long_enough if b >= a]
137+
fallback_min = max(0, int(z_min))
138+
fallback_max = min(max_start, int(z_max))
139+
if fallback_max < fallback_min:
140+
fallback_max = fallback_min
141+
else:
142+
fallback_min, fallback_max = 0, max_start
143+
144+
if long_enough:
145+
a, b = random.choice(long_enough)
146+
start = int(a + random.random() * max(0, b - a))
147+
else:
148+
start = random.randint(fallback_min, fallback_max)
149+
150+
videos = data.setdefault("videos", {})
151+
intervals = videos.setdefault(video_path, [])
152+
end = float(start + clip_len_sec)
153+
intervals.append([float(start), end])
154+
merged = merge_intervals(intervals)
155+
if len(merged) > MAX_INTERVALS_PER_VIDEO:
156+
merged = merged[-MAX_INTERVALS_PER_VIDEO:]
157+
videos[video_path] = merged
158+
save_used(json_path, data)
159+
return int(start)
160+
161+
109162
def record_used(json_path: str, video_path: str, start_sec: float, end_sec: float) -> None:
110163
data = load_used(json_path)
111164
videos = data.setdefault("videos", {})
@@ -133,6 +186,15 @@ def main() -> None:
133186
zone_max = float(sys.argv[7]) if len(sys.argv) >= 8 else None
134187
start = pick_start(json_path, video_path, float(dur_s), int(float(clip_s)), zone_min, zone_max)
135188
print(start)
189+
elif cmd == "pick_record":
190+
if len(sys.argv) not in (6, 8):
191+
print(f"Usage: {FILENAME} pick_record <json_path> <video_path> <duration_sec> <clip_len_sec> [zone_min] [zone_max]", file=sys.stderr)
192+
sys.exit(2)
193+
json_path, video_path, dur_s, clip_s = sys.argv[2:6]
194+
zone_min = float(sys.argv[6]) if len(sys.argv) >= 8 else None
195+
zone_max = float(sys.argv[7]) if len(sys.argv) >= 8 else None
196+
start = pick_and_record_start(json_path, video_path, float(dur_s), int(float(clip_s)), zone_min, zone_max)
197+
print(start)
136198
elif cmd == "record":
137199
if len(sys.argv) != 6:
138200
print(f"Usage: {FILENAME} record <json_path> <video_path> <start_sec> <end_sec>", file=sys.stderr)

templates/stats.html

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,21 +192,24 @@ <h3 class="text-sm font-medium text-gray-400 uppercase tracking-wider">
192192
<!-- Top audio by play count -->
193193
<div class="bg-gray-800 rounded-lg shadow border border-gray-700 overflow-hidden" x-data="{ collapsed: true }">
194194
<div @click="collapsed = !collapsed" class="px-6 py-4 cursor-pointer hover:bg-gray-700/50 transition-colors flex items-center justify-between">
195-
<h3 class="text-sm font-medium text-gray-400 uppercase tracking-wider">Music (most played)</h3>
195+
<h3 class="text-sm font-medium text-gray-400 uppercase tracking-wider">Music (most streamed)</h3>
196196
<span class="text-gray-500" x-text="collapsed ? '▶' : '▼'"></span>
197197
</div>
198198
<div x-show="!collapsed" x-cloak class="px-6 pb-6">
199199
{% if play_counts.audio %}
200200
<ul class="grid grid-cols-2 gap-3 text-sm text-gray-300">
201-
{% for name, count in play_counts.audio %}
201+
{% for row in play_counts.audio %}
202202
<li class="flex justify-between items-center gap-2 border-b border-gray-700 pb-3 odd:border-r odd:border-gray-700 odd:pr-3">
203-
<span class="truncate min-w-0" title="{{ name }}">{{ name }}</span>
204-
<span class="font-mono text-gray-400 shrink-0">{{ count }}</span>
203+
<span class="truncate min-w-0" title="{{ row.name }}">{{ row.name }}</span>
204+
<span class="text-right shrink-0">
205+
<span class="font-mono text-gray-300">{{ row.time_display }}</span>
206+
<span class="block text-[10px] text-gray-500 font-mono">{{ row.chunks }} chunks · {{ row.seconds }}s</span>
207+
</span>
205208
</li>
206209
{% endfor %}
207210
</ul>
208211
{% else %}
209-
<p class="text-gray-500 text-sm">No audio play data yet. Counts increment per chunk played while each track is active.</p>
212+
<p class="text-gray-500 text-sm">No audio stats yet. Time and chunk counts accrue while each track plays on stream.</p>
210213
{% endif %}
211214
</div>
212215
</div>

0 commit comments

Comments
 (0)