Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
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="
|
419 |
<profile
|
420 |
-
description="
|
421 |
-
width="
|
422 |
-
height="
|
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,
|
448 |
<producer
|
449 |
-
id="
|
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">
|
483 |
-
${
|
484 |
<entry
|
485 |
-
producer="
|
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,
|