jbilcke-hf HF staff commited on
Commit
1425c47
1 Parent(s): 9c63d01

improve Shotcut export

Browse files
src/controllers/io/useIO.ts CHANGED
@@ -409,22 +409,43 @@ export const useIO = create<IOStore>((set, get) => ({
409
 
410
  const timeline: TimelineStore = useTimeline.getState()
411
  const { clap } = timeline
 
412
  const segments: ExportableSegment[] = clap.segments
413
  .map((segment, i) => formatSegmentForExport(segment, i))
414
  .filter(({ isExportableToFile }) => isExportableToFile)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
 
416
  // want to see some colors? install es6-string-html in your VSCode
417
  return /* HTML*/ `<?xml version="1.0" standalone="no"?>
418
- <mlt LC_NUMERIC="C" version="7.24.0" title="Shotcut version 24.04.28" producer="main_bin">
419
  <profile
420
- description="1024:576"
421
- width="1024"
422
- height="576"
423
  progressive="0"
424
  sample_aspect_num="1"
425
  sample_aspect_den="1"
426
  display_aspect_num="16"
427
  display_aspect_den="9"
 
 
 
 
 
428
  frame_rate_num="25"
429
  frame_rate_den="1"
430
  colorspace="709"
@@ -444,9 +465,9 @@ export const useIO = create<IOStore>((set, get) => ({
444
  <playlist id="background">
445
  <entry producer="black" in="00:00:00.000" out="${formatDuration(clap.meta.durationInMs)}" />
446
  </playlist>
447
- ${segments.map(({ segment, fileName, filePath, isExportableToFile }, i) => /* HTML*/ `
448
  <producer
449
- id="producer${i}"
450
  in="${formatDuration(0)}"
451
  out="${formatDuration(clap.meta.durationInMs)}">
452
  <property name="length">${formatDuration(clap.meta.durationInMs)}</property>
@@ -479,15 +500,100 @@ export const useIO = create<IOStore>((set, get) => ({
479
  `).join('')}
480
  <playlist id="playlist0">
481
  <property name="shotcut:video">1</property>
482
- <property name="shotcut:name">V1</property>
483
- ${segments.map(({ segment, fileName, filePath, isExportableToFile }, i) => /* HTML*/ `
484
  <entry
485
- producer="producer${i}"
486
  in="${formatDuration(0)}"
487
  out="${formatDuration(segment.assetDurationInMs)}"
488
  />
489
  `).join('')}
490
  </playlist>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  <tractor
492
  id="tractor0"
493
  title="Shotcut version 24.04.28"
@@ -496,8 +602,13 @@ export const useIO = create<IOStore>((set, get) => ({
496
  <property name="shotcut">1</property>
497
  <property name="shotcut:projectAudioChannels">2</property>
498
  <property name="shotcut:projectFolder">1</property>
 
499
  <track producer="background"/>
500
  <track producer="playlist0"/>
 
 
 
 
501
  <transition id="transition0">
502
  <property name="a_track">0</property>
503
  <property name="b_track">1</property>
@@ -513,6 +624,35 @@ export const useIO = create<IOStore>((set, get) => ({
513
  <property name="threads">0</property>
514
  <property name="disable">1</property>
515
  </transition>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  </tractor>
517
  </mlt>`
518
  },
 
409
 
410
  const timeline: TimelineStore = useTimeline.getState()
411
  const { clap } = timeline
412
+
413
  const segments: ExportableSegment[] = clap.segments
414
  .map((segment, i) => formatSegmentForExport(segment, i))
415
  .filter(({ isExportableToFile }) => isExportableToFile)
416
+
417
+ const videos: ExportableSegment[] = segments
418
+ .filter(({ segment }) => segment.category === ClapSegmentCategory.VIDEO)
419
+
420
+ const storyboards: ExportableSegment[] = segments
421
+ .filter(({ segment }) => segment.category === ClapSegmentCategory.STORYBOARD)
422
+
423
+ const dialogues: ExportableSegment[] = segments
424
+ .filter(({ segment }) => segment.category === ClapSegmentCategory.DIALOGUE)
425
+
426
+ const sounds: ExportableSegment[] = segments
427
+ .filter(({ segment }) => segment.category === ClapSegmentCategory.SOUND)
428
+
429
+ const music: ExportableSegment[] = segments
430
+ .filter(({ segment }) => segment.category === ClapSegmentCategory.MUSIC)
431
 
432
  // want to see some colors? install es6-string-html in your VSCode
433
  return /* HTML*/ `<?xml version="1.0" standalone="no"?>
434
+ <mlt LC_NUMERIC="C" version="7.24.0" title="${clap.meta.title}" producer="main_bin">
435
  <profile
436
+ description="${clap.meta.width}:${clap.meta.height}"
437
+ width="${clap.meta.width}"
438
+ height="${clap.meta.height}"
439
  progressive="0"
440
  sample_aspect_num="1"
441
  sample_aspect_den="1"
442
  display_aspect_num="16"
443
  display_aspect_den="9"
444
+
445
+ ${
446
+ ''
447
+ // a good reminder we should add a feature to keep track of the FPS in Clapper
448
+ }
449
  frame_rate_num="25"
450
  frame_rate_den="1"
451
  colorspace="709"
 
465
  <playlist id="background">
466
  <entry producer="black" in="00:00:00.000" out="${formatDuration(clap.meta.durationInMs)}" />
467
  </playlist>
468
+ ${segments.map(({ segment, shortId, fileName, filePath, index }) => /* HTML*/ `
469
  <producer
470
+ id="${shortId}"
471
  in="${formatDuration(0)}"
472
  out="${formatDuration(clap.meta.durationInMs)}">
473
  <property name="length">${formatDuration(clap.meta.durationInMs)}</property>
 
500
  `).join('')}
501
  <playlist id="playlist0">
502
  <property name="shotcut:video">1</property>
503
+ <property name="shotcut:name">Video clips</property>
504
+ ${videos.map(({ segment, shortId }) => /* HTML*/ `
505
  <entry
506
+ producer="${shortId}"
507
  in="${formatDuration(0)}"
508
  out="${formatDuration(segment.assetDurationInMs)}"
509
  />
510
  `).join('')}
511
  </playlist>
512
+ <playlist id="playlist1">
513
+ <property name="shotcut:video">1</property>
514
+ <property name="shotcut:name">Storyboards</property>
515
+ ${storyboards.map(({ segment, shortId }) => /* HTML*/ `
516
+ <entry
517
+ producer="${shortId}"
518
+ in="${formatDuration(0)}"
519
+ out="${formatDuration(segment.assetDurationInMs)}"
520
+ />
521
+ `).join('')}
522
+ </playlist>
523
+ ${[
524
+ ...dialogues,
525
+ ...sounds,
526
+ ...music
527
+ ].map(({ segment, filePath, fileName, shortId }) => /* HTML*/ `
528
+ <chain id="${shortId}" out="${formatDuration(clap.meta.durationInMs)}">
529
+ <property name="length">${formatDuration(clap.meta.durationInMs)}</property>
530
+ <property name="eof">pause</property>
531
+ <property name="resource">${filePath}</property>
532
+ <property name="mlt_service">avformat-novalidate</property>
533
+ <property name="meta.media.nb_streams">1</property>
534
+ <property name="meta.media.0.stream.type">audio</property>
535
+ ${
536
+ ''
537
+ /*
538
+ I don't think we absolutely need to provide those as this is just meta
539
+
540
+ <property name="meta.media.0.codec.sample_fmt">fltp</property>
541
+ <property name="meta.media.0.codec.sample_rate">44100</property>
542
+ <property name="meta.media.0.codec.channels">2</property>
543
+ <property name="meta.media.0.codec.layout">stereo</property>
544
+ <property name="meta.media.0.codec.name">mp3float</property>
545
+ <property name="meta.media.0.codec.long_name">MP3 (MPEG audio layer 3)</property>
546
+ <property name="meta.media.0.codec.bit_rate">150551</property>
547
+ <property name="meta.attr.0.stream.encoder.markup">Lavc60.3.</property>
548
+ <property name="meta.attr.encoder.markup">Lavf60.3.100</property>
549
+ */
550
+ }
551
+ <property name="seekable">1</property>
552
+ <property name="audio_index">0</property>
553
+ <property name="video_index">-1</property>
554
+ <property name="creation_time">${segment.createdAt}</property>
555
+ <property name="astream">0</property>
556
+ <property name="shotcut:skipConvert">1</property>
557
+ ${
558
+ ''
559
+ // yeah well, let's skip this one as well
560
+ // <property name="shotcut:hash">ee26f27a566e64d5ed116f433012e3d6</property>
561
+ }
562
+ <property name="shotcut:caption">${fileName}</property>
563
+ </chain>`)}
564
+ <playlist id="playlist2">
565
+ <property name="shotcut:audio">1</property>
566
+ <property name="shotcut:name">Dialogues & speech</property>
567
+ ${dialogues.map(({ segment, shortId }) => /* HTML*/ `
568
+ <entry
569
+ producer="${shortId}"
570
+ in="${segment.startTimeInMs}"
571
+ out="${segment.endTimeInMs}"
572
+ />
573
+ `)}
574
+ </playlist>
575
+ <playlist id="playlist3">
576
+ <property name="shotcut:audio">1</property>
577
+ <property name="shotcut:name">Sound effects</property>
578
+ ${sounds.map(({ segment, shortId }) => /* HTML*/ `
579
+ <entry
580
+ producer="${shortId}"
581
+ in="${segment.startTimeInMs}"
582
+ out="${segment.endTimeInMs}"
583
+ />
584
+ `)}
585
+ </playlist>
586
+ <playlist id="playlist4">
587
+ <property name="shotcut:audio">1</property>
588
+ <property name="shotcut:name">Music</property>
589
+ ${music.map(({ segment, shortId }) => /* HTML*/ `
590
+ <entry
591
+ producer="${shortId}"
592
+ in="${segment.startTimeInMs}"
593
+ out="${segment.endTimeInMs}"
594
+ />
595
+ `)}
596
+ </playlist>
597
  <tractor
598
  id="tractor0"
599
  title="Shotcut version 24.04.28"
 
602
  <property name="shotcut">1</property>
603
  <property name="shotcut:projectAudioChannels">2</property>
604
  <property name="shotcut:projectFolder">1</property>
605
+ <property name="shotcut:skipConvert">0</property>
606
  <track producer="background"/>
607
  <track producer="playlist0"/>
608
+ <track producer="playlist1"/>
609
+ <track producer="playlist2" hide="video" />
610
+ <track producer="playlist3" hide="video" />
611
+ <track producer="playlist4" hide="video" />
612
  <transition id="transition0">
613
  <property name="a_track">0</property>
614
  <property name="b_track">1</property>
 
624
  <property name="threads">0</property>
625
  <property name="disable">1</property>
626
  </transition>
627
+ <transition id="transition2">
628
+ <property name="a_track">0</property>
629
+ <property name="b_track">1</property>
630
+ <property name="version">0.1</property>
631
+ <property name="mlt_service">frei0r.cairoblend</property>
632
+ <property name="threads">0</property>
633
+ <property name="disable">1</property>
634
+ </transition>
635
+ <transition id="transition3">
636
+ <property name="a_track">0</property>
637
+ <property name="b_track">2</property>
638
+ <property name="mlt_service">mix</property>
639
+ <property name="always_active">1</property>
640
+ <property name="sum">1</property>
641
+ </transition>
642
+ <transition id="transition4">
643
+ <property name="a_track">0</property>
644
+ <property name="b_track">3</property>
645
+ <property name="mlt_service">mix</property>
646
+ <property name="always_active">1</property>
647
+ <property name="sum">1</property>
648
+ </transition>
649
+ <transition id="transition5">
650
+ <property name="a_track">0</property>
651
+ <property name="b_track">3</property>
652
+ <property name="mlt_service">mix</property>
653
+ <property name="always_active">1</property>
654
+ <property name="sum">1</property>
655
+ </transition>
656
  </tractor>
657
  </mlt>`
658
  },
src/lib/utils/formatSegmentForExport.ts CHANGED
@@ -2,15 +2,35 @@ import { ClapAssetSource, ClapSegment, ClapSegmentCategory } from "@aitube/clap"
2
 
3
  export type ExportableSegment = {
4
  segment: ClapSegment
 
 
 
 
5
  directory: string
 
 
 
 
 
 
6
  index: number
 
7
  prefix: string
 
 
8
  mimetype: string
 
 
9
  format: string
 
10
  filePath: string
 
11
  fileName: string
 
12
  assetUrl: string
 
13
  assetSourceType: ClapAssetSource
 
14
  isExportableToFile: boolean
15
  }
16
 
@@ -37,8 +57,12 @@ export function formatSegmentForExport(segment: ClapSegment, index: number): Exp
37
  format !== "unknown" &&
38
  segment.assetUrl.startsWith("data:")
39
 
 
 
40
  return {
41
  segment,
 
 
42
  directory,
43
  index,
44
  prefix,
 
2
 
3
  export type ExportableSegment = {
4
  segment: ClapSegment
5
+
6
+ // lowercase category name
7
+ category: string
8
+
9
  directory: string
10
+
11
+ // a short id that is neither a hash or a UUID, but instead something like
12
+ // video0, video1, sound100, storyboard435 etc..
13
+ shortId: string
14
+
15
+ // used to create short and unique IDs in the project files for external audio editors
16
  index: number
17
+
18
  prefix: string
19
+
20
+ // eg image/jpeg, audio/mpeg
21
  mimetype: string
22
+
23
+ // eg mp3, mp4
24
  format: string
25
+
26
  filePath: string
27
+
28
  fileName: string
29
+
30
  assetUrl: string
31
+
32
  assetSourceType: ClapAssetSource
33
+
34
  isExportableToFile: boolean
35
  }
36
 
 
57
  format !== "unknown" &&
58
  segment.assetUrl.startsWith("data:")
59
 
60
+ const category = segment.category.toLocaleLowerCase()
61
+
62
  return {
63
  segment,
64
+ category,
65
+ shortId: `${category}${index}`,
66
  directory,
67
  index,
68
  prefix,