@gannochenko/staticstripes 0.0.22 → 0.0.24

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.
Files changed (56) hide show
  1. package/Makefile +8 -0
  2. package/dist/app-builder.d.ts +18 -0
  3. package/dist/app-builder.d.ts.map +1 -0
  4. package/dist/app-builder.js +94 -0
  5. package/dist/app-builder.js.map +1 -0
  6. package/dist/cli/commands/filters.d.ts +3 -0
  7. package/dist/cli/commands/filters.d.ts.map +1 -0
  8. package/dist/cli/commands/filters.js +21 -0
  9. package/dist/cli/commands/filters.js.map +1 -0
  10. package/dist/cli/commands/generate.d.ts.map +1 -1
  11. package/dist/cli/commands/generate.js +6 -1
  12. package/dist/cli/commands/generate.js.map +1 -1
  13. package/dist/cli/instagram/instagram-upload-strategy.d.ts +5 -0
  14. package/dist/cli/instagram/instagram-upload-strategy.d.ts.map +1 -1
  15. package/dist/cli/instagram/instagram-upload-strategy.js +46 -3
  16. package/dist/cli/instagram/instagram-upload-strategy.js.map +1 -1
  17. package/dist/cli.js +2 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/ffmpeg.d.ts +32 -0
  20. package/dist/ffmpeg.d.ts.map +1 -1
  21. package/dist/ffmpeg.js +118 -0
  22. package/dist/ffmpeg.js.map +1 -1
  23. package/dist/html-project-parser.d.ts +36 -1
  24. package/dist/html-project-parser.d.ts.map +1 -1
  25. package/dist/html-project-parser.js +332 -15
  26. package/dist/html-project-parser.js.map +1 -1
  27. package/dist/project.d.ts +4 -1
  28. package/dist/project.d.ts.map +1 -1
  29. package/dist/project.js +50 -1
  30. package/dist/project.js.map +1 -1
  31. package/dist/sample-sequences.d.ts.map +1 -1
  32. package/dist/sample-sequences.js +293 -0
  33. package/dist/sample-sequences.js.map +1 -1
  34. package/dist/sequence.d.ts +4 -1
  35. package/dist/sequence.d.ts.map +1 -1
  36. package/dist/sequence.js +71 -21
  37. package/dist/sequence.js.map +1 -1
  38. package/dist/stream.d.ts +17 -0
  39. package/dist/stream.d.ts.map +1 -1
  40. package/dist/stream.js +28 -0
  41. package/dist/stream.js.map +1 -1
  42. package/dist/type.d.ts +29 -2
  43. package/dist/type.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/app-builder.ts +113 -0
  46. package/src/cli/commands/filters.ts +21 -0
  47. package/src/cli/commands/generate.ts +10 -1
  48. package/src/cli/instagram/instagram-upload-strategy.ts +61 -1
  49. package/src/cli.ts +2 -0
  50. package/src/ffmpeg.ts +161 -0
  51. package/src/html-project-parser.ts +410 -28
  52. package/src/project.ts +62 -0
  53. package/src/sample-sequences.ts +300 -0
  54. package/src/sequence.ts +78 -22
  55. package/src/stream.ts +50 -0
  56. package/src/type.ts +31 -2
@@ -820,6 +820,7 @@ class HTMLProjectParser {
820
820
  let thumbOffset;
821
821
  let coverUrl;
822
822
  let videoUrl;
823
+ let locationId;
823
824
  const localTags = [];
824
825
  if ('children' in element && element.children) {
825
826
  for (const child of element.children) {
@@ -891,6 +892,21 @@ class HTMLProjectParser {
891
892
  }
892
893
  break;
893
894
  }
895
+ case 'location': {
896
+ const id = childAttrs.get('id');
897
+ const city = childAttrs.get('city');
898
+ const country = childAttrs.get('country');
899
+ if (id) {
900
+ // Explicit location ID provided
901
+ locationId = id;
902
+ }
903
+ else if (city && country) {
904
+ // Store search query for later resolution
905
+ // Format: "search:City, Country"
906
+ locationId = `search:${city}, ${country}`;
907
+ }
908
+ break;
909
+ }
894
910
  }
895
911
  }
896
912
  }
@@ -914,6 +930,7 @@ class HTMLProjectParser {
914
930
  thumbOffset,
915
931
  coverUrl,
916
932
  videoUrl,
933
+ locationId,
917
934
  },
918
935
  };
919
936
  }
@@ -1300,16 +1317,28 @@ class HTMLProjectParser {
1300
1317
  // 4. Extract container or app if present (first one only, mutually exclusive)
1301
1318
  const container = this.extractFragmentContainer(element);
1302
1319
  const app = container ? undefined : this.extractFragmentApp(element);
1303
- // 5. Parse trimLeft from -trim-start property
1304
- const trimLeft = this.parseTrimStart(styles['-trim-start']);
1305
- // 5b. Parse trimRight from -trim-end property
1306
- const trimRight = this.parseTrimEnd(styles['-trim-end']);
1307
- // 6. Parse duration from -duration property
1308
- const duration = this.parseDurationProperty(styles['-duration'], assetName, assets, trimLeft, trimRight);
1309
- // 7. Parse -offset-start for overlayLeft (can be number or expression)
1310
- const overlayLeft = this.parseOffsetStart(styles['-offset-start']);
1311
- // 8. Parse -offset-end for overlayRight (temporary, will be normalized)
1312
- const overlayRight = this.parseOffsetEnd(styles['-offset-end']);
1320
+ // 4b. Parse data-timing attribute (short syntax) - takes precedence over CSS
1321
+ const dataTiming = this.parseDataTiming(attrs.get('data-timing'));
1322
+ // 5. Parse trimLeft from data-timing or -trim-start property
1323
+ const trimLeft = dataTiming.trimStart !== undefined
1324
+ ? dataTiming.trimStart
1325
+ : this.parseTrimStart(styles['-trim-start']);
1326
+ // 5b. Parse trimRight from data-timing or -trim-end property
1327
+ const trimRight = dataTiming.trimEnd !== undefined
1328
+ ? dataTiming.trimEnd
1329
+ : this.parseTrimEnd(styles['-trim-end']);
1330
+ // 6. Parse duration from data-timing or -duration property
1331
+ const duration = dataTiming.duration !== undefined
1332
+ ? dataTiming.duration
1333
+ : this.parseDurationProperty(styles['-duration'], assetName, assets, trimLeft, trimRight);
1334
+ // 7. Parse overlayLeft from data-timing or -offset-start property
1335
+ const overlayLeft = dataTiming.offsetStart !== undefined
1336
+ ? dataTiming.offsetStart
1337
+ : this.parseOffsetStart(styles['-offset-start']);
1338
+ // 8. Parse overlayRight from data-timing or -offset-end property
1339
+ const overlayRight = dataTiming.offsetEnd !== undefined
1340
+ ? dataTiming.offsetEnd
1341
+ : this.parseOffsetEnd(styles['-offset-end']);
1313
1342
  // 9. Parse -overlay-start-z-index for overlayZIndex
1314
1343
  const overlayZIndex = this.parseZIndex(styles['-overlay-start-z-index']);
1315
1344
  // 10. Parse -overlay-end-z-index for overlayZIndexRight (temporary)
@@ -1322,9 +1351,13 @@ class HTMLProjectParser {
1322
1351
  const objectFitData = this.parseObjectFitProperty(styles['-object-fit']);
1323
1352
  // 14. Parse -chromakey
1324
1353
  const chromakeyData = this.parseChromakeyProperty(styles['-chromakey']);
1354
+ // 14b. Parse -object-fit-ken-burns
1355
+ const kenBurnsData = this.parseKenBurnsProperty(styles['-object-fit-ken-burns']);
1325
1356
  // 15. Parse filter (for visual filters)
1326
1357
  const visualFilter = this.parseVisualFilterProperty(styles['filter']);
1327
- // 16. Extract timecode label from data-timecode attribute
1358
+ // 16. Parse sound property (on/off)
1359
+ const sound = this.parseSoundProperty(styles['-sound']);
1360
+ // 17. Extract timecode label from data-timecode attribute
1328
1361
  const timecodeLabel = attrs.get('data-timecode') || undefined;
1329
1362
  return {
1330
1363
  id,
@@ -1350,6 +1383,17 @@ class HTMLProjectParser {
1350
1383
  chromakeyBlend: chromakeyData.chromakeyBlend,
1351
1384
  chromakeySimilarity: chromakeyData.chromakeySimilarity,
1352
1385
  chromakeyColor: chromakeyData.chromakeyColor,
1386
+ objectFitKenBurns: kenBurnsData.objectFitKenBurns,
1387
+ objectFitKenBurnsZoom: kenBurnsData.objectFitKenBurnsZoom,
1388
+ objectFitKenBurnsEffectDuration: kenBurnsData.objectFitKenBurnsEffectDuration,
1389
+ objectFitKenBurnsEasing: kenBurnsData.objectFitKenBurnsEasing,
1390
+ objectFitKenBurnsFocalX: kenBurnsData.objectFitKenBurnsFocalX,
1391
+ objectFitKenBurnsFocalY: kenBurnsData.objectFitKenBurnsFocalY,
1392
+ objectFitKenBurnsPanStartX: kenBurnsData.objectFitKenBurnsPanStartX,
1393
+ objectFitKenBurnsPanStartY: kenBurnsData.objectFitKenBurnsPanStartY,
1394
+ objectFitKenBurnsPanEndX: kenBurnsData.objectFitKenBurnsPanEndX,
1395
+ objectFitKenBurnsPanEndY: kenBurnsData.objectFitKenBurnsPanEndY,
1396
+ sound,
1353
1397
  ...(visualFilter && { visualFilter }), // Add visualFilter if present
1354
1398
  ...(container && { container }), // Add container if present
1355
1399
  ...(app && { app }), // Add app if present
@@ -1370,6 +1414,21 @@ class HTMLProjectParser {
1370
1414
  // Validation will happen in the Stream.filter() method
1371
1415
  return trimmed || undefined;
1372
1416
  }
1417
+ /**
1418
+ * Parses -sound property
1419
+ * Can be: "on" (default) or "off"
1420
+ * When "off", replaces audio stream with silence
1421
+ */
1422
+ parseSoundProperty(sound) {
1423
+ if (!sound) {
1424
+ return 'on'; // Default: use audio
1425
+ }
1426
+ const trimmed = sound.trim().toLowerCase();
1427
+ if (trimmed === 'off') {
1428
+ return 'off';
1429
+ }
1430
+ return 'on'; // Default for any other value
1431
+ }
1373
1432
  /**
1374
1433
  * Extracts the first <container> child from a fragment element
1375
1434
  */
@@ -1540,9 +1599,14 @@ class HTMLProjectParser {
1540
1599
  }
1541
1600
  return Math.max(0, asset.duration - trimLeft - trimRight);
1542
1601
  }
1602
+ const trimmed = duration.trim();
1603
+ // Check if it's a calc() expression
1604
+ if (trimmed.startsWith('calc(')) {
1605
+ return (0, expression_parser_1.parseValueLazy)(trimmed);
1606
+ }
1543
1607
  // Handle percentage (e.g., "100%", "50%")
1544
- if (duration.endsWith('%')) {
1545
- const percentage = parseFloat(duration);
1608
+ if (trimmed.endsWith('%')) {
1609
+ const percentage = parseFloat(trimmed);
1546
1610
  if (isNaN(percentage)) {
1547
1611
  return 0;
1548
1612
  }
@@ -1554,11 +1618,11 @@ class HTMLProjectParser {
1554
1618
  return Math.round((asset.duration * percentage) / 100);
1555
1619
  }
1556
1620
  // Handle time value (e.g., "5000ms", "5s")
1557
- return this.parseMilliseconds(duration);
1621
+ return this.parseMilliseconds(trimmed);
1558
1622
  }
1559
1623
  /**
1560
1624
  * Parses time value into milliseconds
1561
- * Supports: "5s", "5000ms", "1.5s", etc.
1625
+ * Supports: "5s", "5000ms", "1.5s", or raw numbers (defaults to milliseconds)
1562
1626
  */
1563
1627
  parseMilliseconds(value) {
1564
1628
  if (!value) {
@@ -1579,6 +1643,11 @@ class HTMLProjectParser {
1579
1643
  return Math.round(seconds * 1000);
1580
1644
  }
1581
1645
  }
1646
+ // Handle raw numbers (default to milliseconds)
1647
+ const num = parseFloat(trimmed);
1648
+ if (!isNaN(num)) {
1649
+ return Math.round(num);
1650
+ }
1582
1651
  return 0;
1583
1652
  }
1584
1653
  /**
@@ -1613,6 +1682,62 @@ class HTMLProjectParser {
1613
1682
  // Otherwise parse as time value
1614
1683
  return this.parseMilliseconds(trimmed);
1615
1684
  }
1685
+ /**
1686
+ * Parses data-timing attribute with short syntax
1687
+ * Format: "ts=3000,te=5000,d=2000,os=1000,oe=7000"
1688
+ * Where:
1689
+ * ts = -trim-start
1690
+ * te = -trim-end
1691
+ * d = -duration
1692
+ * os = -offset-start
1693
+ * oe = -offset-end
1694
+ * Values default to milliseconds if no unit specified
1695
+ * Supports calc() expressions: ts=calc(url(#id.time.start))
1696
+ */
1697
+ parseDataTiming(dataTiming) {
1698
+ const result = {};
1699
+ if (!dataTiming) {
1700
+ return result;
1701
+ }
1702
+ // Split by comma and parse each key-value pair
1703
+ const pairs = dataTiming.split(',').map((pair) => pair.trim());
1704
+ for (const pair of pairs) {
1705
+ const [key, value] = pair.split('=').map((s) => s.trim());
1706
+ if (!key || !value) {
1707
+ continue;
1708
+ }
1709
+ // Parse value - check if it's a calc() expression or a simple value
1710
+ let parsedValue;
1711
+ if (value.startsWith('calc(')) {
1712
+ // It's a calc() expression
1713
+ parsedValue = (0, expression_parser_1.parseValueLazy)(value);
1714
+ }
1715
+ else {
1716
+ // It's a simple time value - parse with parseMilliseconds
1717
+ // which handles units like 's' and 'ms', defaulting to ms
1718
+ parsedValue = this.parseMilliseconds(value);
1719
+ }
1720
+ // Map short names to result properties
1721
+ switch (key) {
1722
+ case 'ts':
1723
+ result.trimStart = parsedValue;
1724
+ break;
1725
+ case 'te':
1726
+ result.trimEnd = parsedValue;
1727
+ break;
1728
+ case 'd':
1729
+ result.duration = parsedValue;
1730
+ break;
1731
+ case 'os':
1732
+ result.offsetStart = parsedValue;
1733
+ break;
1734
+ case 'oe':
1735
+ result.offsetEnd = parsedValue;
1736
+ break;
1737
+ }
1738
+ }
1739
+ return result;
1740
+ }
1616
1741
  /**
1617
1742
  * Parses z-index values (-overlay-start-z-index, -overlay-end-z-index)
1618
1743
  */
@@ -1674,6 +1799,10 @@ class HTMLProjectParser {
1674
1799
  if (type === 'cover') {
1675
1800
  return { ...defaults, objectFit: 'cover' };
1676
1801
  }
1802
+ // Handle "ken-burns"
1803
+ if (type === 'ken-burns') {
1804
+ return { ...defaults, objectFit: 'ken-burns' };
1805
+ }
1677
1806
  // Handle "contain" with sub-options
1678
1807
  if (type === 'contain') {
1679
1808
  const subType = parts[1];
@@ -1780,6 +1909,194 @@ class HTMLProjectParser {
1780
1909
  chromakeyColor: color,
1781
1910
  };
1782
1911
  }
1912
+ /**
1913
+ * Parses -object-fit-ken-burns property
1914
+ * Format:
1915
+ * Zoom effects: "<effect> <focal-x> <focal-y> <zoom%> <duration> [easing]"
1916
+ * Pan effects: "<effect> <zoom-factor> [easing]"
1917
+ * Examples:
1918
+ * - "zoom-in 50% 50% 30% 1000ms ease-in-out" - zoom 30% over 1s
1919
+ * - "zoom-out 30% 30% 50% 2000ms ease-out" - zoom out 50% over 2s
1920
+ * - "pan-left 1.3 linear" - pan with 1.3x zoom
1921
+ * Effects: zoom-in, zoom-out (require focal points + zoom% + duration)
1922
+ * pan-left, pan-right, pan-top, pan-bottom (require zoom factor only)
1923
+ * Zoom: for zoom effects: percentage (30 = 30%), for pan: factor (1.3)
1924
+ * Duration: effect duration in milliseconds (zoom effects only)
1925
+ * Easing: linear, ease-in, ease-out, ease-in-out (default: linear)
1926
+ */
1927
+ parseKenBurnsProperty(kenBurns) {
1928
+ // Defaults
1929
+ const defaults = {
1930
+ objectFitKenBurns: 'zoom-in',
1931
+ objectFitKenBurnsZoom: 30, // 30% zoom
1932
+ objectFitKenBurnsEffectDuration: 0, // 0 = use fragment duration
1933
+ objectFitKenBurnsEasing: 'linear',
1934
+ objectFitKenBurnsFocalX: 50, // center (for zoom effects)
1935
+ objectFitKenBurnsFocalY: 50, // center (for zoom effects)
1936
+ objectFitKenBurnsPanStartX: 0, // left edge (for horizontal pan)
1937
+ objectFitKenBurnsPanStartY: 0, // top edge (for vertical pan)
1938
+ objectFitKenBurnsPanEndX: 100, // right edge (for horizontal pan)
1939
+ objectFitKenBurnsPanEndY: 100, // bottom edge (for vertical pan)
1940
+ };
1941
+ if (!kenBurns) {
1942
+ return defaults;
1943
+ }
1944
+ const trimmed = kenBurns.trim();
1945
+ const parts = this.splitCssValue(trimmed);
1946
+ if (parts.length === 0) {
1947
+ return defaults;
1948
+ }
1949
+ // Parse effect (first part)
1950
+ const effect = parts[0];
1951
+ const validEffects = ['zoom-in', 'zoom-out', 'pan-left', 'pan-right', 'pan-top', 'pan-bottom'];
1952
+ if (!validEffects.includes(effect)) {
1953
+ return defaults;
1954
+ }
1955
+ let focalX = defaults.objectFitKenBurnsFocalX;
1956
+ let focalY = defaults.objectFitKenBurnsFocalY;
1957
+ let zoom = defaults.objectFitKenBurnsZoom;
1958
+ let effectDuration = defaults.objectFitKenBurnsEffectDuration;
1959
+ let easing = defaults.objectFitKenBurnsEasing;
1960
+ let panStartX = defaults.objectFitKenBurnsPanStartX;
1961
+ let panStartY = defaults.objectFitKenBurnsPanStartY;
1962
+ let panEndX = defaults.objectFitKenBurnsPanEndX;
1963
+ let panEndY = defaults.objectFitKenBurnsPanEndY;
1964
+ const isZoomEffect = effect === 'zoom-in' || effect === 'zoom-out';
1965
+ const isPanEffect = effect === 'pan-left' || effect === 'pan-right' || effect === 'pan-top' || effect === 'pan-bottom';
1966
+ // Parse remaining parts based on effect type
1967
+ let nextIndex = 1;
1968
+ if (isZoomEffect) {
1969
+ // Zoom effects: "<effect> <focal-x%> <focal-y%> <zoom%> <duration> [easing]"
1970
+ // Parse focal points
1971
+ if (parts.length >= 3 && parts[1].includes('%') && parts[2].includes('%')) {
1972
+ const focalXStr = parts[1].split('%')[0];
1973
+ const focalYStr = parts[2].split('%')[0];
1974
+ const parsedX = parseFloat(focalXStr);
1975
+ const parsedY = parseFloat(focalYStr);
1976
+ if (!isNaN(parsedX) && !isNaN(parsedY)) {
1977
+ focalX = Math.max(0, Math.min(100, parsedX));
1978
+ focalY = Math.max(0, Math.min(100, parsedY));
1979
+ nextIndex = 3;
1980
+ // Check if parts[2] had a concatenated value after '%'
1981
+ const remainder = parts[2].split('%')[1];
1982
+ if (remainder && remainder.trim()) {
1983
+ parts.splice(3, 0, remainder.trim());
1984
+ }
1985
+ }
1986
+ }
1987
+ // Parse zoom percentage (required)
1988
+ if (parts.length > nextIndex && parts[nextIndex].includes('%')) {
1989
+ const zoomStr = parts[nextIndex].split('%')[0];
1990
+ const parsedZoom = parseFloat(zoomStr);
1991
+ if (!isNaN(parsedZoom) && parsedZoom >= 0) {
1992
+ zoom = parsedZoom; // Store as percentage (e.g., 30 for 30%)
1993
+ nextIndex++;
1994
+ // Check for concatenated value after '%'
1995
+ const remainder = parts[nextIndex - 1].split('%')[1];
1996
+ if (remainder && remainder.trim()) {
1997
+ parts.splice(nextIndex, 0, remainder.trim());
1998
+ }
1999
+ }
2000
+ }
2001
+ // Parse effect duration (required)
2002
+ if (parts.length > nextIndex) {
2003
+ const durationStr = parts[nextIndex];
2004
+ // Simple inline parser for "1000ms" or "1s" format
2005
+ const match = durationStr.match(/^(\d+(?:\.\d+)?)(ms|s)$/);
2006
+ if (match) {
2007
+ const value = parseFloat(match[1]);
2008
+ const parsedDuration = match[2] === 's' ? value * 1000 : value;
2009
+ if (parsedDuration > 0) {
2010
+ effectDuration = parsedDuration;
2011
+ nextIndex++;
2012
+ }
2013
+ }
2014
+ }
2015
+ // Parse easing (optional)
2016
+ if (parts.length > nextIndex) {
2017
+ const easingStr = parts[nextIndex];
2018
+ if (['linear', 'ease-in', 'ease-out', 'ease-in-out'].includes(easingStr)) {
2019
+ easing = easingStr;
2020
+ }
2021
+ }
2022
+ }
2023
+ else if (isPanEffect) {
2024
+ // Pan effects: "<effect> <zoom%> <start%> <end%> <duration> [easing]"
2025
+ // Example: pan-left 50% 20% 80% 2000ms ease-in
2026
+ // Parse zoom percentage (required)
2027
+ if (parts.length > nextIndex && parts[nextIndex].includes('%')) {
2028
+ const zoomStr = parts[nextIndex].split('%')[0];
2029
+ const parsedZoom = parseFloat(zoomStr);
2030
+ if (!isNaN(parsedZoom) && parsedZoom >= 0) {
2031
+ zoom = parsedZoom; // Store as percentage (e.g., 50 for 50%)
2032
+ nextIndex++;
2033
+ }
2034
+ }
2035
+ // Parse start position percentage (required)
2036
+ if (parts.length > nextIndex && parts[nextIndex].includes('%')) {
2037
+ const startStr = parts[nextIndex].split('%')[0];
2038
+ const parsedStart = parseFloat(startStr);
2039
+ if (!isNaN(parsedStart) && parsedStart >= 0 && parsedStart <= 100) {
2040
+ // Store in appropriate variable based on pan direction
2041
+ if (effect === 'pan-left' || effect === 'pan-right') {
2042
+ panStartX = parsedStart;
2043
+ }
2044
+ else {
2045
+ panStartY = parsedStart;
2046
+ }
2047
+ nextIndex++;
2048
+ }
2049
+ }
2050
+ // Parse end position percentage (required)
2051
+ if (parts.length > nextIndex && parts[nextIndex].includes('%')) {
2052
+ const endStr = parts[nextIndex].split('%')[0];
2053
+ const parsedEnd = parseFloat(endStr);
2054
+ if (!isNaN(parsedEnd) && parsedEnd >= 0 && parsedEnd <= 100) {
2055
+ // Store in appropriate variable based on pan direction
2056
+ if (effect === 'pan-left' || effect === 'pan-right') {
2057
+ panEndX = parsedEnd;
2058
+ }
2059
+ else {
2060
+ panEndY = parsedEnd;
2061
+ }
2062
+ nextIndex++;
2063
+ }
2064
+ }
2065
+ // Parse effect duration (required)
2066
+ if (parts.length > nextIndex) {
2067
+ const durationStr = parts[nextIndex];
2068
+ // Simple inline parser for "1000ms" or "1s" format
2069
+ const match = durationStr.match(/^(\d+(?:\.\d+)?)(ms|s)$/);
2070
+ if (match) {
2071
+ const value = parseFloat(match[1]);
2072
+ const parsedDuration = match[2] === 's' ? value * 1000 : value;
2073
+ if (parsedDuration > 0) {
2074
+ effectDuration = parsedDuration;
2075
+ nextIndex++;
2076
+ }
2077
+ }
2078
+ }
2079
+ // Parse easing (optional)
2080
+ if (parts.length > nextIndex) {
2081
+ const easingStr = parts[nextIndex];
2082
+ if (['linear', 'ease-in', 'ease-out', 'ease-in-out'].includes(easingStr)) {
2083
+ easing = easingStr;
2084
+ }
2085
+ }
2086
+ }
2087
+ return {
2088
+ objectFitKenBurns: effect,
2089
+ objectFitKenBurnsZoom: zoom,
2090
+ objectFitKenBurnsEffectDuration: effectDuration,
2091
+ objectFitKenBurnsEasing: easing,
2092
+ objectFitKenBurnsFocalX: focalX,
2093
+ objectFitKenBurnsFocalY: focalY,
2094
+ objectFitKenBurnsPanStartX: panStartX,
2095
+ objectFitKenBurnsPanStartY: panStartY,
2096
+ objectFitKenBurnsPanEndX: panEndX,
2097
+ objectFitKenBurnsPanEndY: panEndY,
2098
+ };
2099
+ }
1783
2100
  }
1784
2101
  exports.HTMLProjectParser = HTMLProjectParser;
1785
2102
  //# sourceMappingURL=html-project-parser.js.map