free-short-video 5.2.0 โ†’ 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,13 @@ free-short-video
33
33
 
34
34
  > **๐Ÿ–ฅ๏ธ Try it now โ€” no install needed:** Visit [video.lichuanyang.top](https://video.lichuanyang.top) and experience **Simple Video** mode directly in your browser. Just enter a prompt and generate a free AI video instantly.
35
35
 
36
+ ## ๐Ÿš€ Two Ways to Use โ€” Both Completely Free
37
+
38
+ | Project | Run Where | Features | Link |
39
+ |---------|-----------|----------|------|
40
+ | **[Agnes Video Generator](https://github.com/lcy362/agnes-video-generator)** (this project) | **Download & run locally** | **More powerful** โ€” TTS narration, auto subtitles, digital anchor, image-to-video, keyframes animation, manuscript-to-video, checkpoint resume & more | [GitHub](https://github.com/lcy362/agnes-video-generator) |
41
+ | **[FreeShortVideoStudio](https://github.com/lcy362/free-short-video-studio)** | **Fully online, in the browser** | Lightweight, zero install โ€” no setup at all, **features under active construction** | [video.lichuanyang.top/studio](https://video.lichuanyang.top/studio) ยท [GitHub](https://github.com/lcy362/free-short-video-studio) |
42
+
36
43
  ## โญ Support & Contribute
37
44
 
38
45
  If you find this project useful, please **star the [GitHub repository](https://github.com/lcy362/agnes-video-generator)** โญ โ€” your support helps more people discover this free and open-source AI video generator.
@@ -82,7 +82,7 @@ class AgnesImageAPI:
82
82
  prompt: str,
83
83
  reference_image_paths: List[str] = [],
84
84
  size: Optional[str] = None,
85
- max_retries: int = 3,
85
+ max_retries: int = 4,
86
86
  retry_base_delay: float = 20.0,
87
87
  **kwargs,
88
88
  ) -> ImageOutput:
@@ -114,8 +114,8 @@ class AgnesImageAPI:
114
114
  try:
115
115
  # ๅ…จๅฑ€้™้€Ÿ๏ผšๅœจๅ‘่ตท HTTP ่ฏทๆฑ‚ๅ‰่Žทๅ–ไปค็‰Œ
116
116
  await asyncio.to_thread(get_rate_limiter().acquire)
117
- # ๅŠจๆ€่ถ…ๆ—ถ๏ผš็ฌฌไธ€ๆฌก 60s๏ผŒๅŽ็ปญ้€ๆญฅๅขžๅŠ 
118
- read_timeout = 60 * (attempt + 1)
117
+ # ๅŠจๆ€่ถ…ๆ—ถ๏ผš็ฌฌไธ€ๆฌก 120s๏ผŒๅŽ็ปญ้€ๆญฅๅขžๅŠ ๏ผˆๅ›พๅƒ็”Ÿๆˆ่พƒๆ…ข๏ผŒๆ”พๅฎฝ่ฏป่ถ…ๆ—ถ๏ผ‰
118
+ read_timeout = 120 * (attempt + 1)
119
119
  resp = await asyncio.to_thread(
120
120
  requests.post,
121
121
  f"{get_agnes_base_url()}/images/generations",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-short-video",
3
- "version": "5.2.0",
3
+ "version": "5.2.1",
4
4
  "description": "Free AI short-video generator โ€” run the full free-short-video service locally with one command. Text-to-video, image generation, Edge-TTS narration, subtitles, and compositing, all free.",
5
5
  "bin": {
6
6
  "free-short-video": "bin/cli.js",
package/static/index.html CHANGED
@@ -1552,6 +1552,78 @@
1552
1552
  </div>
1553
1553
 
1554
1554
  <script>
1555
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1556
+ // GA4 ๅŸ‹็‚น๏ผˆๅ›บๅฎš Measurement ID๏ผŒ็ปŸไธ€ๆ”ถ้›†็”จๆˆทๆ“ไฝœๆ•ฐๆฎ๏ผ‰
1557
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1558
+
1559
+ const GA_MEASUREMENT_ID = 'G-RQW2189QSK';
1560
+ let _gaEnabled = false;
1561
+
1562
+ // ๅŠจๆ€ๅŠ ่ฝฝ gtag.js๏ผˆๅน‚็ญ‰๏ผšๅทฒๅˆๅง‹ๅŒ–ๅˆ™่ทณ่ฟ‡๏ผ‰
1563
+ function initGA() {
1564
+ if (_gaEnabled) return;
1565
+ _gaEnabled = true;
1566
+ try {
1567
+ window.dataLayer = window.dataLayer || [];
1568
+ window.gtag = function(){ window.dataLayer.push(arguments); };
1569
+ const s = document.createElement('script');
1570
+ s.async = true;
1571
+ s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(GA_MEASUREMENT_ID);
1572
+ document.head.appendChild(s);
1573
+ window.gtag('js', new Date());
1574
+ window.gtag('config', GA_MEASUREMENT_ID);
1575
+ } catch(e) {
1576
+ console.warn('GA init failed:', e);
1577
+ _gaEnabled = false;
1578
+ }
1579
+ }
1580
+
1581
+ // ็ปŸไธ€ไบ‹ไปถไธŠๆŠฅ๏ผšGA ๆœชๅฏ็”จๆˆ–ๅˆๅง‹ๅŒ–ๅคฑ่ดฅๆ—ถ้™้ป˜่ทณ่ฟ‡
1582
+ function trackEvent(name, params) {
1583
+ if (!_gaEnabled || typeof window.gtag !== 'function') return;
1584
+ try { window.gtag('event', name, params || {}); } catch(e) { /* ้™้ป˜ */ }
1585
+ }
1586
+
1587
+ // ไปปๅŠก็ป“ๆžœๅŽป้‡ไธŠๆŠฅ๏ผš่ฝฎ่ฏขไผšๅๅค่ฐƒ็”จ๏ผŒๅŒไธ€ไปปๅŠกๅชไธŠๆŠฅไธ€ๆฌก็ปˆๆ€
1588
+ const _trackedTaskResults = {};
1589
+ function trackTaskResultOnce(name, taskId, params) {
1590
+ if (_trackedTaskResults[taskId] === name) return;
1591
+ _trackedTaskResults[taskId] = name;
1592
+ trackEvent(name, params || {});
1593
+ }
1594
+
1595
+ // ๅ…จๅฑ€ๆŠฅ้”™ๆ•่Žท โ†’ GA4 exception ไบ‹ไปถ๏ผˆdescription + fatal ๅ‚ๆ•ฐ๏ผ‰
1596
+ let _lastErrSig = '';
1597
+ let _lastErrTime = 0;
1598
+ function reportException(description, fatal) {
1599
+ const now = Date.now();
1600
+ // ๅŒ็ฑป้”™่ฏฏ 10 ็ง’ๅ†…ๅชไธŠๆŠฅไธ€ๆฌก๏ผŒ้ฟๅ…่ฝฎ่ฏข/ๅพช็Žฏๅฏผ่‡ด็š„ๅˆทๅฑ
1601
+ if (description === _lastErrSig && now - _lastErrTime < 10000) return;
1602
+ _lastErrSig = description;
1603
+ _lastErrTime = now;
1604
+ trackEvent('exception', {
1605
+ description: (description || '').slice(0, 500),
1606
+ fatal: fatal ? '1' : '0',
1607
+ });
1608
+ }
1609
+
1610
+ // JS ่ฟ่กŒๆ—ถ้”™่ฏฏ๏ผˆๅซๅ †ๆ ˆไฝ็ฝฎ๏ผ‰
1611
+ window.addEventListener('error', function(e) {
1612
+ reportException(
1613
+ (e.message || 'Unknown error') + ' @ ' + (e.filename || 'inline') + ':' + (e.lineno || 0),
1614
+ true
1615
+ );
1616
+ });
1617
+
1618
+ // ๆœชๅค„็†็š„ Promise ๆ‹’็ป
1619
+ window.addEventListener('unhandledrejection', function(e) {
1620
+ let msg = '';
1621
+ const r = e.reason;
1622
+ if (r instanceof Error) msg = r.message;
1623
+ else if (r != null) msg = String(r);
1624
+ reportException('Unhandled promise rejection: ' + (msg || 'unknown'), false);
1625
+ });
1626
+
1555
1627
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1556
1628
  // i18n
1557
1629
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -3283,6 +3355,8 @@ const STEPS = {
3283
3355
  updateDomainCompact(d.agnes_domain);
3284
3356
  updateDomainStatus(d.agnes_domain);
3285
3357
  }
3358
+ // GA4 ๅŸ‹็‚น๏ผšๅ›บๅฎš Measurement ID๏ผŒ้กต้ขๅŠ ่ฝฝๅณๅฏ็”จ๏ผˆ็ปŸไธ€ๆ”ถ้›†ๆ“ไฝœๆ•ฐๆฎ๏ผ‰
3359
+ initGA();
3286
3360
  } catch(e) { console.error('init config load error:', e); }
3287
3361
 
3288
3362
  // Character count for manuscript
@@ -3562,6 +3636,7 @@ function showToast(msg, duration = 2500) {
3562
3636
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3563
3637
 
3564
3638
  function switchMainTab(tab) {
3639
+ trackEvent('ui_action', { action: 'switch_main_tab', tab });
3565
3640
  document.querySelectorAll('.mtab-btn').forEach(b => {
3566
3641
  b.className = b.className.replace(/tab-active/, 'tab-inactive');
3567
3642
  });
@@ -3581,6 +3656,7 @@ function switchMainTab(tab) {
3581
3656
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3582
3657
 
3583
3658
  function switchTaskType(type) {
3659
+ trackEvent('ui_action', { action: 'switch_task_type', type });
3584
3660
  currentTaskType = type;
3585
3661
  document.querySelectorAll('.ttype-btn').forEach(b => {
3586
3662
  b.className = b.className.replace(/tab-active/, 'tab-inactive');
@@ -3712,6 +3788,12 @@ async function submitSimple() {
3712
3788
  const r = await fetch('/api/tasks/simple', { method: 'POST', body: form });
3713
3789
  const d = await r.json();
3714
3790
  if (!d.ok) throw new Error(d.detail || t('failCreate'));
3791
+ trackEvent('create_task', {
3792
+ task_type: 'simple',
3793
+ mode: document.getElementById('s_mode').value,
3794
+ duration: document.getElementById('s_duration').value,
3795
+ resolution: document.getElementById('s_resolution').value,
3796
+ });
3715
3797
  currentTaskType = 'simple';
3716
3798
  setRunning(d.task_id);
3717
3799
  currentDirName = d.dir_name;
@@ -3719,6 +3801,7 @@ async function submitSimple() {
3719
3801
  await startPolling(d.task_id);
3720
3802
  showToast(t('submitted'), 5000);
3721
3803
  } catch(e) {
3804
+ trackEvent('create_task_failed', { task_type: 'simple', error: (e.message || '').slice(0, 120) });
3722
3805
  alert(t('failCreate') + ': ' + e.message);
3723
3806
  btn.disabled = false; btn.textContent = t('startGenerate');
3724
3807
  }
@@ -3830,6 +3913,15 @@ async function submitCreative() {
3830
3913
  const r = await fetch('/api/tasks/creative', { method: 'POST', body: form });
3831
3914
  const d = await r.json();
3832
3915
  if (!d.ok) throw new Error(d.detail || t('failCreate'));
3916
+ trackEvent('create_task', {
3917
+ task_type: 'creative',
3918
+ style: document.getElementById('c_style').value,
3919
+ chaining_mode: document.getElementById('c_chaining').value,
3920
+ duration_source: durationSource,
3921
+ resolution: document.getElementById('c_resolution').value,
3922
+ audio: document.getElementById('c_audio_enabled').checked ? 'on' : 'off',
3923
+ subtitle: document.getElementById('c_subtitle_enabled').checked ? 'on' : 'off',
3924
+ });
3833
3925
  currentTaskType = 'creative';
3834
3926
  setRunning(d.task_id);
3835
3927
  currentDirName = d.dir_name;
@@ -3837,6 +3929,7 @@ async function submitCreative() {
3837
3929
  await startPolling(d.task_id);
3838
3930
  showToast(t('submitted'), 5000);
3839
3931
  } catch(e) {
3932
+ trackEvent('create_task_failed', { task_type: 'creative', error: (e.message || '').slice(0, 120) });
3840
3933
  alert(t('failCreate') + ': ' + e.message);
3841
3934
  btn.disabled = false; btn.textContent = t('startGenerate');
3842
3935
  }
@@ -3877,6 +3970,13 @@ async function submitManuscript() {
3877
3970
  const r = await fetch('/api/tasks/manuscript', { method: 'POST', body: form });
3878
3971
  const d = await r.json();
3879
3972
  if (!d.ok) throw new Error(d.detail || t('failCreate'));
3973
+ trackEvent('create_task', {
3974
+ task_type: 'manuscript',
3975
+ resolution: document.getElementById('m_resolution').value,
3976
+ text_len: (text || '').length,
3977
+ audio: document.getElementById('m_audio_enabled').checked ? 'on' : 'off',
3978
+ subtitle: document.getElementById('m_subtitle_enabled').checked ? 'on' : 'off',
3979
+ });
3880
3980
  currentTaskType = 'manuscript';
3881
3981
  setRunning(d.task_id);
3882
3982
  currentDirName = d.dir_name;
@@ -3884,6 +3984,7 @@ async function submitManuscript() {
3884
3984
  await startPolling(d.task_id);
3885
3985
  showToast(t('submitted'), 5000);
3886
3986
  } catch(e) {
3987
+ trackEvent('create_task_failed', { task_type: 'manuscript', error: (e.message || '').slice(0, 120) });
3887
3988
  alert(t('failCreate') + ': ' + e.message);
3888
3989
  btn.disabled = false; btn.textContent = t('startGenerate');
3889
3990
  }
@@ -3931,6 +4032,13 @@ async function submitAnchor() {
3931
4032
  const r = await fetch('/api/tasks/anchor', { method: 'POST', body: form });
3932
4033
  const d = await r.json();
3933
4034
  if (!d.ok) throw new Error(d.detail || t('failCreate'));
4035
+ trackEvent('create_task', {
4036
+ task_type: 'anchor',
4037
+ resolution: document.getElementById('a_resolution').value,
4038
+ audio_source: (audioSource ? audioSource.value : 'post_stitch'),
4039
+ audio: document.getElementById('a_audio_enabled').checked ? 'on' : 'off',
4040
+ subtitle: document.getElementById('a_subtitle_enabled').checked ? 'on' : 'off',
4041
+ });
3934
4042
  currentTaskType = 'anchor';
3935
4043
  setRunning(d.task_id);
3936
4044
  currentDirName = d.dir_name;
@@ -3938,6 +4046,7 @@ async function submitAnchor() {
3938
4046
  await startPolling(d.task_id);
3939
4047
  showToast(t('submitted'), 5000);
3940
4048
  } catch(e) {
4049
+ trackEvent('create_task_failed', { task_type: 'anchor', error: (e.message || '').slice(0, 120) });
3941
4050
  alert(t('failCreate') + ': ' + e.message);
3942
4051
  btn.disabled = false; btn.textContent = t('startGenerate');
3943
4052
  }
@@ -3990,6 +4099,14 @@ async function submitPoetry() {
3990
4099
  const r = await fetch('/api/tasks/poetry', { method: 'POST', body: form });
3991
4100
  const d = await r.json();
3992
4101
  if (!d.ok) throw new Error(d.detail || t('failCreate'));
4102
+ trackEvent('create_task', {
4103
+ task_type: 'poetry',
4104
+ style: document.getElementById('p_style').value,
4105
+ scene_count: sceneCount,
4106
+ resolution: document.getElementById('p_resolution').value,
4107
+ audio: document.getElementById('p_audio_enabled').checked ? 'on' : 'off',
4108
+ subtitle: document.getElementById('p_subtitle_enabled').checked ? 'on' : 'off',
4109
+ });
3993
4110
  currentTaskType = 'poetry';
3994
4111
  setRunning(d.task_id);
3995
4112
  currentDirName = d.dir_name;
@@ -3997,6 +4114,7 @@ async function submitPoetry() {
3997
4114
  await startPolling(d.task_id);
3998
4115
  showToast(t('submitted'), 5000);
3999
4116
  } catch(e) {
4117
+ trackEvent('create_task_failed', { task_type: 'poetry', error: (e.message || '').slice(0, 120) });
4000
4118
  alert(t('failCreate') + ': ' + e.message);
4001
4119
  btn.disabled = false; btn.textContent = t('startGenerate');
4002
4120
  }
@@ -4144,10 +4262,15 @@ async function submitImage() {
4144
4262
  const d = await r.json();
4145
4263
  if (!d.ok) throw new Error(d.detail || t('failCreate'));
4146
4264
 
4265
+ trackEvent('create_task', {
4266
+ task_type: 'image',
4267
+ size: document.getElementById('i_size').value,
4268
+ });
4147
4269
  document.getElementById('result-image').src = '/api/image/' + d.task_id;
4148
4270
  document.getElementById('image-result').classList.remove('hidden');
4149
4271
  showToast(t('imgComplete'), 5000);
4150
4272
  } catch(e) {
4273
+ trackEvent('create_task_failed', { task_type: 'image', error: (e.message || '').slice(0, 120) });
4151
4274
  alert(t('failCreate') + ': ' + e.message);
4152
4275
  } finally {
4153
4276
  btn.disabled = false; btn.textContent = t('imgGenerate');
@@ -4278,6 +4401,9 @@ async function pollTaskProgress(taskId) {
4278
4401
 
4279
4402
  // ไปปๅŠกๅฎŒๆˆ
4280
4403
  if (state.status === 'completed') {
4404
+ trackTaskResultOnce('task_completed', taskId, {
4405
+ task_type: state.task_type || currentTaskType,
4406
+ });
4281
4407
  showResult(state.final_video_file);
4282
4408
  clearRunning();
4283
4409
  scheduleArtifactRefresh();
@@ -4285,6 +4411,10 @@ async function pollTaskProgress(taskId) {
4285
4411
 
4286
4412
  // ไปปๅŠกๅคฑ่ดฅ
4287
4413
  if (state.status === 'failed' || (step === 'error' && status === 'failed')) {
4414
+ trackTaskResultOnce('task_failed', taskId, {
4415
+ task_type: state.task_type || currentTaskType,
4416
+ error: (state.current_message || '').slice(0, 120),
4417
+ });
4288
4418
  clearRunning();
4289
4419
  document.getElementById('progress-message').innerHTML = `
4290
4420
  <div class="p-4 bg-red-950 border border-red-800 rounded-lg space-y-2">
@@ -4532,6 +4662,7 @@ async function stopTask() {
4532
4662
  const r = await fetch('/api/tasks/' + currentTaskId + '/stop', { method: 'POST' });
4533
4663
  const d = await r.json();
4534
4664
  if (!d.ok) throw new Error(d.detail || t('failStop'));
4665
+ trackEvent('task_stopped', { task_type: currentTaskType || '', source: 'progress' });
4535
4666
  showToast(t('stopped'), 4000);
4536
4667
  } catch(e) {
4537
4668
  alert(t('failStop') + ': ' + e.message);
@@ -4545,6 +4676,7 @@ async function stopTaskById(taskId) {
4545
4676
  const r = await fetch('/api/tasks/' + taskId + '/stop', { method: 'POST' });
4546
4677
  const d = await r.json();
4547
4678
  if (!d.ok) throw new Error(d.detail || t('failStop'));
4679
+ trackEvent('task_stopped', { task_type: currentTaskType || '', source: 'list' });
4548
4680
  showToast(t('stoppedById'), 3000);
4549
4681
  loadTaskList();
4550
4682
  } catch(e) { alert(t('failStop') + ': ' + e.message); }
@@ -5019,6 +5151,7 @@ document.getElementById('apikey-save').onclick = async () => {
5019
5151
  form.append('api_key', key);
5020
5152
  const r = await fetch('/api/config', { method: 'POST', body: form });
5021
5153
  if (r.ok) {
5154
+ trackEvent('config_action', { action: 'save_api_key' });
5022
5155
  setApiKeyConfiguredUI('config', true);
5023
5156
  }
5024
5157
  };
@@ -5032,6 +5165,7 @@ document.getElementById('apikey-clear').onclick = async () => {
5032
5165
  try {
5033
5166
  const r = await fetch('/api/config', { method: 'DELETE' });
5034
5167
  if (r.ok) {
5168
+ trackEvent('config_action', { action: 'clear_api_key' });
5035
5169
  resetApiKeyUI();
5036
5170
  } else {
5037
5171
  const d = await r.json();
@@ -5182,6 +5316,7 @@ document.getElementById('model-save').onclick = async () => {
5182
5316
  try {
5183
5317
  const r = await fetch('/api/config/models', { method: 'POST', body: form });
5184
5318
  if (r.ok) {
5319
+ trackEvent('config_action', { action: 'save_models', text_model: text });
5185
5320
  okEl.classList.remove('hidden');
5186
5321
  updateModelCompact({ text });
5187
5322
  setTimeout(() => okEl.classList.add('hidden'), 2000);
@@ -5235,6 +5370,7 @@ document.getElementById('domain-save').onclick = async () => {
5235
5370
  try {
5236
5371
  const r = await fetch('/api/config/domain', { method: 'POST', body: form });
5237
5372
  if (r.ok) {
5373
+ trackEvent('config_action', { action: 'save_domain', domain });
5238
5374
  okEl.classList.remove('hidden');
5239
5375
  updateDomainCompact(domain);
5240
5376
  updateDomainStatus(domain);
@@ -5261,6 +5397,7 @@ async function toggleWatermark(el) {
5261
5397
  try {
5262
5398
  const r = await fetch('/api/config/watermark', { method: 'POST', body: form });
5263
5399
  if (r.ok) {
5400
+ trackEvent('config_action', { action: 'toggle_watermark', enabled: enabled ? 'on' : 'off' });
5264
5401
  document.querySelectorAll('.watermark-toggle').forEach(t => t.checked = enabled);
5265
5402
  showToast(enabled ? t('watermarkEnabled') : t('watermarkDisabled'));
5266
5403
  }