diff --git a/.dagger/.gitattributes b/.dagger/.gitattributes new file mode 100644 index 0000000..8274184 --- /dev/null +++ b/.dagger/.gitattributes @@ -0,0 +1 @@ +/sdk/** linguist-generated diff --git a/.dagger/.gitignore b/.dagger/.gitignore new file mode 100644 index 0000000..040187c --- /dev/null +++ b/.dagger/.gitignore @@ -0,0 +1,4 @@ +/sdk +/**/node_modules/** +/**/.pnpm-store/** +/.env diff --git a/.dagger/package.json b/.dagger/package.json new file mode 100644 index 0000000..3184033 --- /dev/null +++ b/.dagger/package.json @@ -0,0 +1,7 @@ +{ + "type": "module", + "dependencies": { + "typescript": "^5.5.4" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts new file mode 100644 index 0000000..ff8ab69 --- /dev/null +++ b/.dagger/src/index.ts @@ -0,0 +1,393 @@ +/** + * A generated module for TestCard functions + * + * This module has been generated via dagger init and serves as a reference to + * basic module structure as you get started with Dagger. + * + * Two functions have been pre-created. You can modify, delete, or add to them, + * as needed. They demonstrate usage of arguments and return types using simple + * echo and grep commands. The functions can be called from the dagger CLI or + * from one of the SDKs. + * + * The first line in this comment block is a short description line and the + * rest is a long description with more detail on the module's purpose or usage, + * if appropriate. All modules should have a short description. + */ +import { + dag, + Container, + Directory, + File, + Secret, + object, + func, + argument, + Platform +} from '@dagger.io/dagger'; + +@object() +export class TestCard { + // ============ Helpers ============ + private auxMediaBase(): Container { + // Equivalent of Earthly target: aux-media + return dag + .container() + .from('debian:bookworm') + .withExec([ + 'bash', + '-lc', + 'apt-get update && apt-get install -y ffmpeg sox && rm -rf /var/lib/apt/lists/*' + ]); + } + + private nodeWithBunBase(platform?: Platform): Container { + return dag.container({ platform }).from('node:lts').withExec(['npm', 'install', '-g', 'bun']); + } + + // ============ site ============ + /** + * Build the website (equivalent to Earthly target `site`). + * Returns the built `build/` directory as a Dagger Directory. + */ + @func() + async site(@argument({ defaultPath: '/' }) source: Directory): Promise { + const assets = await this.assetsGenerated(source); + + const ctr = this.nodeWithBunBase() + .withMountedDirectory('/src', source) + .withMountedDirectory('/assets-generated', assets) + .withWorkdir('/src') + .withExec(['bun', 'install', '--frozen-lockfile']) + .withExec([ + 'bash', + '-lc', + 'mkdir -p /src/assets/generated && cp -a /assets-generated/. /src/assets/generated' + ]) + .withExec([ + 'sh', + '-lc', + 'export VITE_BUILD_DATE=$(date -Iminutes -u | sed "s/+00:00//") && bun x svelte-kit sync && bun run build' + ]); + + return ctr.directory('/src/build'); + } + + // ============ deploy ============ + /** + * Deploy the built site via rsync over SSH (equivalent to Earthly target `deploy`). + * Provide secrets corresponding to SSH config and target. + */ + @func() + async deploy( + sshConfig: Secret, + sshUploadKey: Secret, + sshKnownHosts: Secret, + sshTarget: Secret, + @argument({ defaultPath: '/build' }) build: Directory + ): Promise { + const ctr = dag + .container() + .from('alpine:latest') + .withExec(['sh', '-lc', 'apk add --no-cache openssh-client rsync']) + .withMountedDirectory('/build', build) + .withSecretVariable('SSH_CONFIG', sshConfig) + .withSecretVariable('SSH_UPLOAD_KEY', sshUploadKey) + .withSecretVariable('SSH_KNOWN_HOSTS', sshKnownHosts) + .withSecretVariable('SSH_TARGET', sshTarget) + .withExec([ + 'sh', + '-lc', + 'mkdir -p "$HOME/.ssh" && echo "$SSH_CONFIG" > $HOME/.ssh/config && echo "$SSH_UPLOAD_KEY" > $HOME/.ssh/id_rsa && echo "$SSH_KNOWN_HOSTS" > $HOME/.ssh/known_hosts && chmod 600 $HOME/.ssh/*' + ]) + .withExec(['sh', '-lc', 'rsync -cvrz --delete /build/ "$SSH_TARGET"']); + + return ctr.stdout(); + } + + // ============ avsync-video-components ============ + /** + * Render AV sync frames with Bun. Returns the frames directory. + * Mirrors the `avsync-video-components` video part. + */ + @func() + async avsyncFrames( + fps: number = 60, + size: number = 1200, + @argument({ defaultPath: '/' }) source: Directory + ): Promise { + const ctr = this.nodeWithBunBase() + // pptr troubleshooting libs + .withExec([ + 'bash', + '-lc', + 'apt-get update && apt-get -y install chromium libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 && rm -rf /var/lib/apt/lists/*' + ]) + .withExec([ + 'bash', + '-lc', + 'groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser && mkdir -p /home/pptruser && chown -R pptruser:pptruser /home/pptruser' + ]) + .withUser('pptruser') + .withDirectory('/site', source, { owner: 'pptruser', include: ['package.json', 'bun.lock'] }) + .withWorkdir('/site') + .withExec(['bun', 'install', '--frozen-lockfile', '--verbose']) + .withEnvVariable('PUPPETEER_EXECUTABLE_PATH', '/usr/bin/chromium') + .withEnvVariable('PUPPETEER_SKIP_DOWNLOAD', '1') + .withDirectory('/site', source, { owner: 'pptruser' }) + .withExec([ + 'bun', + 'av:render:video', + '--fps', + String(fps), + '--cycles', + '1', + '--size', + String(size), + '--output', + '/var/tmp/frames' + ]); + + return ctr.directory('/var/tmp/frames'); + } + + /** + * Render AV sync audio track with Bun. Returns the WAV track file. + * Mirrors the `avsync-video-components` audio part. + */ + @func() + async avsyncTrack( + cycles: number = 16, + @argument({ defaultPath: '/' }) source: Directory + ): Promise { + const ctr = this.nodeWithBunBase() + .withDirectory('/site', source) + .withWorkdir('/site') + .withExec(['bun', 'install', '--frozen-lockfile']) + .withExec([ + 'bun', + 'av:render:audio', + '-i', + 'beep.wav', + '-o', + '/var/tmp/track.wav', + '--repeats', + String(cycles) + ]); + + return ctr.file('/var/tmp/track.wav'); + } + + // ============ avsync-video ============ + /** + * Compose frames and audio track into avsync.webm. Returns the resulting file. + * Mirrors the Earthly `avsync-video` target. + */ + @func() + async avsyncVideo( + fps: number = 60, + cycles: number = 16, + @argument({ defaultPath: '/' }) source: Directory + ): Promise { + const frames = await this.avsyncFrames(fps, 1200, source); + const track = await this.avsyncTrack(cycles, source); + + const ctr = this.auxMediaBase() + .withMountedDirectory('/frames', frames) + .withMountedFile('/track.wav', track) + .withExec(['sh', '-lc', 'find /frames -type f | sort | sed "s#^#file #" > /frames.txt']) + .withEnvVariable('CYCLES', String(cycles)) + .withExec([ + 'sh', + '-lc', + 'for i in $(seq 1 $CYCLES); do cat /frames.txt >> /final-frames.txt; done' + ]) + .withEnvVariable('FPS', String(fps)) + .withExec([ + 'sh', + '-lc', + 'ffmpeg -r "$FPS" -f concat -safe 0 -i /final-frames.txt -i /track.wav -c:v libvpx-vp9 -pix_fmt yuva420p -shortest /avsync.webm' + ]); + + return ctr.file('/avsync.webm'); + } + + // ============ audio-channel-tracks (WAV) ============ + /** + * Generate stereo/5.1/7.1 WAV channel tracks from assets/audio/channels input. + * Mirrors the Earthly `audio-channel-tracks` target. + */ + @func() + async audioChannelTracksWav(@argument({ defaultPath: '/' }) src: Directory): Promise { + const ctr = this.auxMediaBase() + .withMountedDirectory('/raw', src.directory('/assets/audio/channels')) + .withExec([ + 'bash', + '-lc', + 'mkdir -p /input /output && cd /raw && for file in *.wav; do sox "$file" "/input/$file" silence 1 0.1 0.1% reverse silence 1 0.1 0.1% reverse; done' + ]) + .withWorkdir('/input') + .withExec(['bash', '-lc', 'mkdir -p /output/wav/stereo /output/wav/5.1 /output/wav/7.1']) + // stereo + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Left.wav -af "pan=stereo|FL=c0" /output/wav/stereo/Left.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Right.wav -af "pan=stereo|FR=c0" /output/wav/stereo/Right.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Center.wav -af "pan=stereo|FL=c0|FR=c0" /output/wav/stereo/Center.wav -hide_banner -loglevel error' + ]) + // 5.1 + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Front_Left.wav -af "pan=5.1|FL=c0" /output/wav/5.1/Front_Left.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Front_Right.wav -af "pan=5.1|FR=c0" /output/wav/5.1/Front_Right.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Front_Center.wav -af "pan=5.1|FC=c0" /output/wav/5.1/Front_Center.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Noise.wav -af "pan=5.1|LFE=c0" /output/wav/5.1/LFE_Noise.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Rear_Left.wav -af "pan=5.1|BL=c0" /output/wav/5.1/Rear_Left.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Rear_Right.wav -af "pan=5.1|BR=c0" /output/wav/5.1/Rear_Right.wav -hide_banner -loglevel error' + ]) + // 7.1 + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Front_Left.wav -af "pan=7.1|FL=c0" /output/wav/7.1/Front_Left.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Front_Right.wav -af "pan=7.1|FR=c0" /output/wav/7.1/Front_Right.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Front_Center.wav -af "pan=7.1|FC=c0" /output/wav/7.1/Front_Center.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Noise.wav -af "pan=7.1|LFE=c0" /output/wav/7.1/LFE_Noise.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Side_Left.wav -af "pan=7.1|SL=c0" /output/wav/7.1/Side_Left.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Side_Right.wav -af "pan=7.1|SR=c0" /output/wav/7.1/Side_Right.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Rear_Left.wav -af "pan=7.1|BL=c0" /output/wav/7.1/Rear_Left.wav -hide_banner -loglevel error' + ]) + .withExec([ + 'bash', + '-lc', + 'ffmpeg -i Rear_Right.wav -af "pan=7.1|BR=c0" /output/wav/7.1/Rear_Right.wav -hide_banner -loglevel error' + ]); + + return ctr.directory('/output/wav'); + } + + // ============ audio-channel-tracks-ogg ============ + @func() + async audioChannelTracksOgg( + @argument({ defaultPath: '/' }) source: Directory + ): Promise { + const wavDir = await this.audioChannelTracksWav(source); + + const ctr = this.auxMediaBase() + .withMountedDirectory('/output/wav', wavDir) + .withExec(['bash', '-lc', 'mkdir -p /output/ogg/stereo /output/ogg/5.1 /output/ogg/7.1']) + .withExec([ + 'bash', + '-lc', + 'for file in /output/wav/stereo/*.wav; do ffmpeg -i "$file" -c:a libvorbis "/output/ogg/stereo/$(basename "$file" .wav).ogg" -hide_banner -loglevel error; done' + ]) + .withExec([ + 'bash', + '-lc', + 'for file in /output/wav/5.1/*.wav; do ffmpeg -i "$file" -c:a libvorbis "/output/ogg/5.1/$(basename "$file" .wav).ogg" -hide_banner -loglevel error; done' + ]) + .withExec([ + 'bash', + '-lc', + 'for file in /output/wav/7.1/*.wav; do ffmpeg -i "$file" -c:a libvorbis "/output/ogg/7.1/$(basename "$file" .wav).ogg" -hide_banner -loglevel error; done' + ]); + + return ctr.directory('/output/ogg'); + } + + // ============ audio-channel-tracks-mp3 ============ + @func() + async audioChannelTracksMp3( + @argument({ defaultPath: '/' }) source: Directory + ): Promise { + const wavDir = await this.audioChannelTracksWav(source); + + const ctr = this.auxMediaBase() + .withMountedDirectory('/output/wav', wavDir) + .withExec(['bash', '-lc', 'mkdir -p /output/mp3/stereo /output/mp3/5.1 /output/mp3/7.1']) + .withExec([ + 'bash', + '-lc', + 'for file in /output/wav/stereo/*.wav; do ffmpeg -i "$file" -c:a libmp3lame "/output/mp3/stereo/$(basename "$file" .wav).mp3" -hide_banner -loglevel error; done' + ]) + .withExec([ + 'bash', + '-lc', + 'for file in /output/wav/5.1/*.wav; do ffmpeg -i "$file" -c:a libmp3lame "/output/mp3/5.1/$(basename "$file" .wav).mp3" -hide_banner -loglevel error; done' + ]) + .withExec([ + 'bash', + '-lc', + 'for file in /output/wav/7.1/*.wav; do ffmpeg -i "$file" -c:a libmp3lame "/output/mp3/7.1/$(basename "$file" .wav).mp3" -hide_banner -loglevel error; done' + ]); + + return ctr.directory('/output/mp3'); + } + + // ============ assets-generated ============ + /** + * Aggregate generated assets into a directory containing: + * - avsync.webm + * - audio/ (MP3 variants) + */ + @func() + async assetsGenerated(@argument({ defaultPath: '/' }) source: Directory): Promise { + const avsync = await this.avsyncVideo(60, 16, source); + const audioMp3 = await this.audioChannelTracksMp3(source); + + return dag.directory().withFile('avsync.webm', avsync).withDirectory('audio', audioMp3); + } +} diff --git a/.dagger/tsconfig.json b/.dagger/tsconfig.json new file mode 100644 index 0000000..4ec0c2d --- /dev/null +++ b/.dagger/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "moduleResolution": "Node", + "experimentalDecorators": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "@dagger.io/dagger": ["./sdk/index.ts"], + "@dagger.io/dagger/telemetry": ["./sdk/telemetry.ts"] + } + } +} diff --git a/.dagger/yarn.lock b/.dagger/yarn.lock new file mode 100644 index 0000000..728ac8d --- /dev/null +++ b/.dagger/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.5.4: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== diff --git a/Earthfile b/Earthfile deleted file mode 100644 index 95e743e..0000000 --- a/Earthfile +++ /dev/null @@ -1,110 +0,0 @@ -VERSION 0.7 -FROM node:lts - -site: - RUN npm install -g bun - COPY package.json bun.lock /site - WORKDIR /site - RUN bun install --frozen-lockfile - COPY --dir src static vite.config.ts tsconfig.json svelte.config.js /site - COPY +assets-generated/ /site/assets/generated - RUN export VITE_BUILD_DATE=$(date -Iminutes -u | sed 's/+00:00//') && bun x svelte-kit sync && bun run build - SAVE ARTIFACT build AS LOCAL build - -deploy: - FROM alpine - RUN apk add openssh-client rsync - RUN --secret SSH_CONFIG --secret SSH_UPLOAD_KEY --secret SSH_KNOWN_HOSTS \ - mkdir -p $HOME/.ssh && \ - echo "$SSH_CONFIG" > $HOME/.ssh/config && \ - echo "$SSH_UPLOAD_KEY" > $HOME/.ssh/id_rsa && \ - echo "$SSH_KNOWN_HOSTS" > $HOME/.ssh/known_hosts && \ - chmod 600 $HOME/.ssh/* - COPY +site/build /build - RUN --secret SSH_TARGET --push rsync -cvrz --delete /build/ $SSH_TARGET - - -avsync-video-components: - FROM --platform=linux/amd64 node:lts - # https://pptr.dev/troubleshooting - RUN apt-get update && apt-get -y install libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 && rm -rf /var/lib/apt/lists/* - RUN npm install -g bun - RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser && mkdir /home/pptruser && chown -R pptruser:pptruser /home/pptruser - USER pptruser - COPY package.json bun.lock /site - WORKDIR /site - RUN bun install --frozen-lockfile - COPY av-sync av-sync - ARG FPS=60 - ARG CYCLES=16 - ARG SIZE=1200 - RUN bun av:render:video --fps $FPS --cycles 1 --size $SIZE --output /var/tmp/frames - SAVE ARTIFACT /var/tmp/frames - RUN bun av:render:audio -i beep.wav -o /var/tmp/track.wav --repeats $CYCLES - SAVE ARTIFACT /var/tmp/track.wav - -aux-media: - FROM debian:bookworm - RUN apt-get update && apt-get install -y ffmpeg sox && rm -rf /var/lib/apt/lists/* - -avsync-video: - FROM +aux-media - RUN apt-get update && apt-get install -y ffmpeg sox && rm -rf /var/lib/apt/lists/* - COPY +avsync-video-components/track.wav /track.wav - COPY +avsync-video-components/frames /frames - RUN find frames -type f | sort | xargs -I {} sh -c 'echo "file {}" >> /frames.txt' - ARG CYCLES=16 - RUN for i in $(seq 1 $CYCLES); do cat /frames.txt >> /final-frames.txt; done - ARG FPS=60 - RUN ffmpeg -r $FPS -f concat -i /final-frames.txt -i track.wav -c:v libvpx-vp9 -pix_fmt yuva420p -shortest avsync.webm - SAVE ARTIFACT avsync.webm - -audio-channel-tracks: - FROM +aux-media - RUN mkdir -p /input /output - COPY assets/audio/channels /raw - WORKDIR /raw - RUN for file in *.wav; do sox $file /input/$file silence 1 0.1 0.1% reverse silence 1 0.1 0.1% reverse; done - WORKDIR /input - RUN mkdir -p /output/wav/stereo /output/wav/5.1 /output/wav/7.1 - RUN ffmpeg -i Left.wav -af "pan=stereo|FL=c0" /output/wav/stereo/Left.wav -hide_banner -loglevel error && \ - ffmpeg -i Right.wav -af "pan=stereo|FR=c0" /output/wav/stereo/Right.wav -hide_banner -loglevel error && \ - ffmpeg -i Center.wav -af "pan=stereo|FL=c0|FR=c0" /output/wav/stereo/Center.wav -hide_banner -loglevel error && \ - # 5.1 - ffmpeg -i Front_Left.wav -af "pan=5.1|FL=c0" /output/wav/5.1/Front_Left.wav -hide_banner -loglevel error && \ - ffmpeg -i Front_Right.wav -af "pan=5.1|FR=c0" /output/wav/5.1/Front_Right.wav -hide_banner -loglevel error && \ - ffmpeg -i Front_Center.wav -af "pan=5.1|FC=c0" /output/wav/5.1/Front_Center.wav -hide_banner -loglevel error && \ - ffmpeg -i Noise.wav -af "pan=5.1|LFE=c0" /output/wav/5.1/LFE_Noise.wav -hide_banner -loglevel error && \ - ffmpeg -i Rear_Left.wav -af "pan=5.1|BL=c0" /output/wav/5.1/Rear_Left.wav -hide_banner -loglevel error && \ - ffmpeg -i Rear_Right.wav -af "pan=5.1|BR=c0" /output/wav/5.1/Rear_Right.wav -hide_banner -loglevel error && \ - # 7.1 - ffmpeg -i Front_Left.wav -af "pan=7.1|FL=c0" /output/wav/7.1/Front_Left.wav -hide_banner -loglevel error && \ - ffmpeg -i Front_Right.wav -af "pan=7.1|FR=c0" /output/wav/7.1/Front_Right.wav -hide_banner -loglevel error && \ - ffmpeg -i Front_Center.wav -af "pan=7.1|FC=c0" /output/wav/7.1/Front_Center.wav -hide_banner -loglevel error && \ - ffmpeg -i Noise.wav -af "pan=7.1|LFE=c0" /output/wav/7.1/LFE_Noise.wav -hide_banner -loglevel error && \ - ffmpeg -i Side_Left.wav -af "pan=7.1|SL=c0" /output/wav/7.1/Side_Left.wav -hide_banner -loglevel error && \ - ffmpeg -i Side_Right.wav -af "pan=7.1|SR=c0" /output/wav/7.1/Side_Right.wav -hide_banner -loglevel error && \ - ffmpeg -i Rear_Left.wav -af "pan=7.1|BL=c0" /output/wav/7.1/Rear_Left.wav -hide_banner -loglevel error && \ - ffmpeg -i Rear_Right.wav -af "pan=7.1|BR=c0" /output/wav/7.1/Rear_Right.wav -hide_banner -loglevel error - SAVE ARTIFACT /output/wav/ - -audio-channel-tracks-ogg: - FROM +audio-channel-tracks - RUN mkdir -p /output/ogg/stereo /output/ogg/5.1 /output/ogg/7.1 - RUN for file in /output/wav/stereo/*.wav; do ffmpeg -i $file -c:a libvorbis /output/ogg/stereo/$(basename $file .wav).ogg -hide_banner -loglevel error; done && \ - for file in /output/wav/5.1/*.wav; do ffmpeg -i $file -c:a libvorbis /output/ogg/5.1/$(basename $file .wav).ogg -hide_banner -loglevel error; done && \ - for file in /output/wav/7.1/*.wav; do ffmpeg -i $file -c:a libvorbis /output/ogg/7.1/$(basename $file .wav).ogg -hide_banner -loglevel error; done - SAVE ARTIFACT /output/ogg - -audio-channel-tracks-mp3: - FROM +audio-channel-tracks - RUN mkdir -p /output/mp3/stereo /output/mp3/5.1 /output/mp3/7.1 - RUN for file in /output/wav/stereo/*.wav; do ffmpeg -i $file -c:a libmp3lame /output/mp3/stereo/$(basename $file .wav).mp3 -hide_banner -loglevel error; done && \ - for file in /output/wav/5.1/*.wav; do ffmpeg -i $file -c:a libmp3lame /output/mp3/5.1/$(basename $file .wav).mp3 -hide_banner -loglevel error; done && \ - for file in /output/wav/7.1/*.wav; do ffmpeg -i $file -c:a libmp3lame /output/mp3/7.1/$(basename $file .wav).mp3 -hide_banner -loglevel error; done - SAVE ARTIFACT /output/mp3 - -assets-generated: - COPY +avsync-video/avsync.webm /assets/avsync.webm - COPY +audio-channel-tracks-mp3/mp3 /assets/audio/ - SAVE ARTIFACT /assets/* AS LOCAL assets/generated/ diff --git a/dagger.json b/dagger.json new file mode 100644 index 0000000..1aad4ba --- /dev/null +++ b/dagger.json @@ -0,0 +1,8 @@ +{ + "name": "test-card", + "engineVersion": "v0.18.19", + "sdk": { + "source": "typescript" + }, + "source": ".dagger" +}