More progress

This commit is contained in:
Rob Watson 2021-11-02 17:20:47 +01:00
parent 49099b12d6
commit 5cbcfe22cf
13 changed files with 281 additions and 176 deletions

View File

@ -44,9 +44,14 @@ func main() {
// Create a MediaSetService
mediaSetService := media.NewMediaSetService(store, &youtubeClient, s3Client)
mediaSet, err := mediaSetService.Get(ctx, videoID)
if err != nil {
log.Fatalf("error calling fetch service: %v", err)
}
// Create a progressReader
// TODO: fix
progressReader, err := mediaSetService.GetAudio(ctx, uuid.New(), 100)
id := uuid.MustParse(mediaSet.ID)
progressReader, err := mediaSetService.GetAudio(ctx, id, 100)
if err != nil {
log.Fatalf("error calling fetch service: %v", err)
}

View File

@ -22,6 +22,7 @@ const (
rawAudioCodec = "pcm_s16le"
rawAudioFormat = "s16le"
rawAudioSampleRate = 48_000
rawAudioMimeType = "audio/raw"
)
const (
@ -50,6 +51,7 @@ type Store interface {
GetMediaSet(ctx context.Context, id uuid.UUID) (store.MediaSet, error)
GetMediaSetByYoutubeID(ctx context.Context, youtubeID string) (store.MediaSet, error)
CreateMediaSet(ctx context.Context, arg store.CreateMediaSetParams) (store.MediaSet, error)
SetAudioUploaded(ctx context.Context, arg store.SetAudioUploadedParams) (store.MediaSet, error)
}
// S3Client wraps the AWS S3 service client.
@ -127,7 +129,7 @@ func (s *MediaSetService) createMediaSet(ctx context.Context, youtubeID string)
AudioYoutubeItag: int32(audioMetadata.YoutubeItag),
AudioChannels: int32(audioMetadata.Channels),
AudioFramesApprox: audioMetadata.ApproxFrames,
AudioSampleRateRaw: int32(audioMetadata.SampleRate),
AudioSampleRate: int32(audioMetadata.SampleRate),
AudioMimeTypeEncoded: audioMetadata.MimeType,
VideoYoutubeItag: int32(videoMetadata.YoutubeItag),
VideoMimeType: videoMetadata.MimeType,
@ -162,8 +164,8 @@ func (s *MediaSetService) findMediaSet(ctx context.Context, youtubeID string) (*
Bytes: 0, // DEPRECATED
Channels: int(mediaSet.AudioChannels),
ApproxFrames: int64(mediaSet.AudioFramesApprox),
Frames: mediaSet.AudioFramesRaw.Int64,
SampleRate: int(mediaSet.AudioSampleRateRaw),
Frames: mediaSet.AudioFrames.Int64,
SampleRate: int(mediaSet.AudioSampleRate),
MimeType: mediaSet.AudioMimeTypeEncoded,
},
Video: Video{
@ -258,13 +260,13 @@ func (s *MediaSetService) GetAudio(ctx context.Context, id uuid.UUID, numBins in
return nil, fmt.Errorf("error creating ffmpegreader: %v", err)
}
// set up uploader, this is writer 1
s3Key := fmt.Sprintf("media_sets/%s/audio.webm", id)
uploader, err := newMultipartUploadWriter(
ctx,
s.s3,
s3Bucket,
fmt.Sprintf("media_sets/%s/audio.webm", id),
"application/octet-stream",
s3Key,
rawAudioMimeType,
)
if err != nil {
return nil, fmt.Errorf("error creating uploader: %v", err)
@ -276,24 +278,29 @@ func (s *MediaSetService) GetAudio(ctx context.Context, id uuid.UUID, numBins in
100,
)
state := fetchAudioState{
state := getAudioState{
fetchAudioProgressReader: fetchAudioProgressReader,
ffmpegReader: ffmpegReader,
uploader: uploader,
s3Bucket: s3Bucket,
s3Key: s3Key,
store: s.store,
}
go state.run(ctx)
return &state, nil
}
type fetchAudioState struct {
type getAudioState struct {
*fetchAudioProgressReader
ffmpegReader io.ReadCloser
uploader *multipartUploadWriter
ffmpegReader io.ReadCloser
uploader *multipartUploadWriter
s3Bucket, s3Key string
store Store
}
func (s *fetchAudioState) run(ctx context.Context) {
func (s *getAudioState) run(ctx context.Context) {
mw := io.MultiWriter(s, s.uploader)
done := make(chan error)
var err error
@ -326,6 +333,17 @@ outer:
}
}
if err == nil {
_, updateErr := s.store.SetAudioUploaded(ctx, store.SetAudioUploadedParams{
AudioS3Bucket: sqlString(s.s3Bucket),
AudioS3Key: sqlString(s.s3Key),
})
if updateErr != nil {
err = updateErr
}
}
if err != nil {
newCtx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
@ -341,3 +359,7 @@ outer:
log.Printf("error closing peak iterator: %v", iterErr)
}
}
func sqlString(s string) sql.NullString {
return sql.NullString{String: s, Valid: true}
}

View File

@ -45,7 +45,8 @@ func newMultipartUploadWriter(ctx context.Context, s3Client S3Client, bucket, ke
return nil, fmt.Errorf("error creating multipart upload: %v", err)
}
b := make([]byte, 0, targetPartSizeBytes+16_384)
const bufferOverflowSize = 16_384
b := make([]byte, 0, targetPartSizeBytes+bufferOverflowSize)
return &multipartUploadWriter{
ctx: ctx,

View File

@ -65,20 +65,17 @@ func (c *mediaSetServiceController) Get(ctx context.Context, request *pbMediaSet
}
result := pbMediaSet.MediaSet{
Id: mediaSet.YoutubeID,
Audio: &pbMediaSet.MediaSet_Audio{
Channels: int32(mediaSet.Audio.Channels),
Frames: mediaSet.Audio.Frames,
ApproxFrames: mediaSet.Audio.ApproxFrames,
SampleRate: int32(mediaSet.Audio.SampleRate),
YoutubeItag: int32(mediaSet.Audio.YoutubeItag),
MimeType: mediaSet.Audio.MimeType,
},
Video: &pbMediaSet.MediaSet_Video{
Duration: durationpb.New(mediaSet.Video.Duration),
YoutubeItag: int32(mediaSet.Video.YoutubeItag),
MimeType: mediaSet.Video.MimeType,
},
Id: mediaSet.ID,
YoutubeId: mediaSet.YoutubeID,
AudioChannels: int32(mediaSet.Audio.Channels),
AudioFrames: mediaSet.Audio.Frames,
AudioApproxFrames: mediaSet.Audio.ApproxFrames,
AudioSampleRate: int32(mediaSet.Audio.SampleRate),
AudioYoutubeItag: int32(mediaSet.Audio.YoutubeItag),
AudioMimeType: mediaSet.Audio.MimeType,
VideoDuration: durationpb.New(mediaSet.Video.Duration),
VideoYoutubeItag: int32(mediaSet.Video.YoutubeItag),
VideoMimeType: mediaSet.Video.MimeType,
}
return &result, nil

View File

@ -5,6 +5,11 @@ SELECT * FROM media_sets WHERE id = $1;
SELECT * FROM media_sets WHERE youtube_id = $1;
-- name: CreateMediaSet :one
INSERT INTO media_sets (youtube_id, audio_youtube_itag, audio_channels, audio_frames_approx, audio_sample_rate_raw, audio_mime_type_encoded, video_youtube_itag, video_mime_type, video_duration_nanos, created_at, updated_at)
INSERT INTO media_sets (youtube_id, audio_youtube_itag, audio_channels, audio_frames_approx, audio_sample_rate, audio_mime_type_encoded, video_youtube_itag, video_mime_type, video_duration_nanos, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())
RETURNING *;
-- name: SetAudioUploaded :one
UPDATE media_sets
SET audio_s3_bucket = $1, audio_s3_key = $2, audio_s3_uploaded_at = NOW(), updated_at = NOW()
RETURNING *;

View File

@ -15,6 +15,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"ts-proto": "^1.83.3",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
@ -49,6 +50,6 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.25.1",
"prettier": "2.4.0",
"ts-protoc-gen": "^0.15.0"
"rxjs": "^7.4.0"
}
}

View File

@ -1,12 +1,15 @@
import { grpc } from '@improbable-eng/grpc-web';
// import {
// MediaSet as MediaSetPb,
// GetRequest,
// GetAudioRequest,
// GetAudioProgress,
// } from './generated/media_set_pb';
import {
MediaSet as MediaSetPb,
GetRequest,
GetAudioRequest,
GetAudioProgress,
} from './generated/media_set_pb';
import { GetMediaSet } from './GrpcWrapper';
MediaSet,
GrpcWebImpl,
MediaSetServiceClientImpl,
} from './generated/media_set';
import { useState, useEffect } from 'react';
import { VideoPreview } from './VideoPreview';
@ -15,32 +18,13 @@ import { Waveform } from './Waveform';
import { ControlBar } from './ControlBar';
import { SeekBar } from './SeekBar';
import './App.css';
import { Duration } from './generated/google/protobuf/duration';
const grpcHost = 'http://localhost:8888';
// Audio corresponds to media.Audio.
export interface Audio {
bytes: number;
channels: number;
frames: number;
sampleRate: number;
}
// Video corresponds to media.Video.
export interface Video {
bytes: number;
thumbnailWidth: number;
thumbnailHeight: number;
durationMillis: number;
}
// MediaSet corresponds to media.MediaSet.
export interface MediaSet {
id: string;
source: string;
audio: Audio;
video: Video;
}
// ported from backend, where should they live?
const thumbnailWidth = 177;
const thumbnailHeight = 100;
// Frames represents a selection of audio frames.
export interface Frames {
@ -65,12 +49,14 @@ function App(): JSX.Element {
// fetch mediaset on page load:
useEffect(() => {
(async function () {
const request = new GetRequest();
request.setYoutubeId(videoID);
const rpc = new GrpcWebImpl('http://localhost:8888', {});
const service = new MediaSetServiceClientImpl(rpc);
const mediaSet = await service.Get({ youtubeId: videoID });
const mediaSet = await GetMediaSet(grpcHost, request);
console.log('got media set:', mediaSet);
setMediaSet(mediaSet);
// const handleProgress = (progress: GetAudioProgress) => {
// console.log('got progress', progress);
// };
@ -122,6 +108,8 @@ function App(): JSX.Element {
return;
}
return;
video.src = `http://localhost:8888/api/media_sets/${videoID}/video`;
video.muted = false;
video.volume = 1;
@ -134,7 +122,7 @@ function App(): JSX.Element {
return;
}
setViewport({ start: 0, end: mediaSet.audio.frames });
setViewport({ start: 0, end: mediaSet.audioFrames });
}, [mediaSet]);
useEffect(() => {
@ -150,7 +138,7 @@ function App(): JSX.Element {
}
if (selection.start >= selection.end) {
setViewport({ start: 0, end: mediaSet.audio.frames });
setViewport({ start: 0, end: mediaSet.audioFrames });
return;
}
@ -169,10 +157,7 @@ function App(): JSX.Element {
flexDirection: 'column',
} as React.CSSProperties;
let offsetPixels = 75;
if (mediaSet != null) {
offsetPixels = Math.floor(mediaSet.video.thumbnailWidth / 2);
}
const offsetPixels = Math.floor(thumbnailWidth / 2);
if (mediaSet == null) {
// TODO: improve
@ -212,7 +197,7 @@ function App(): JSX.Element {
<SeekBar
position={video.currentTime}
duration={mediaSet.audio.frames / mediaSet.audio.sampleRate}
duration={mediaSet.audioFrames / mediaSet.audioSampleRate}
offsetPixels={offsetPixels}
onPositionChanged={(position: number) => {
video.currentTime = position;
@ -222,8 +207,8 @@ function App(): JSX.Element {
<VideoPreview
video={video}
position={position}
duration={mediaSet.video.durationMillis}
height={mediaSet.video.thumbnailHeight}
duration={millisFromDuration(mediaSet.videoDuration)}
height={thumbnailHeight}
/>
</div>
</div>
@ -232,3 +217,10 @@ function App(): JSX.Element {
}
export default App;
function millisFromDuration(dur?: Duration): number {
if (dur == undefined) {
return 0;
}
return Math.floor(dur.seconds * 1000.0 + dur.nanos / 1000.0 / 1000.0);
}

View File

@ -1,55 +0,0 @@
import { grpc } from '@improbable-eng/grpc-web';
import { MediaSetService } from './generated/media_set_pb_service';
import {
MediaSet,
GetRequest,
GetAudioProgress,
GetAudioRequest,
} from './generated/media_set_pb';
export const GetMediaSet = (
host: string,
request: GetRequest
): Promise<MediaSet> => {
return new Promise<MediaSet>((resolve, reject) => {
let result: MediaSet;
grpc.invoke(MediaSetService.Get, {
host: host,
request: request,
onMessage: (mediaSet: MediaSet) => {
result = mediaSet;
},
onEnd: (
code: grpc.Code,
msg: string | undefined,
_trailers: grpc.Metadata
) => {
if (code != 0) {
reject(new Error(`unexpected grpc code: ${code}, message: ${msg}`));
return;
}
resolve(result);
},
});
});
};
// export const etchMediaSetAudio = (
// host: string,
// request: FetchAudioRequest,
// onProgress: { (progress: FetchAudioProgress): void }
// ) => {
// grpc.invoke(FetchService.FetchAudio, {
// host: 'http://localhost:8888',
// request: request,
// onMessage: onProgress,
// onEnd: (
// code: grpc.Code,
// msg: string | undefined,
// trailers: grpc.Metadata
// ) => {
// console.log('fetch audio request ended');
// },
// });
// };

View File

@ -1,5 +1,6 @@
import { useState, useEffect, useRef, MouseEvent } from 'react';
import { MediaSet, Frames } from './App';
import { MediaSet } from './generated/media_set';
import { Frames } from './App';
import { WaveformCanvas } from './WaveformCanvas';
import { mouseEventToCanvasX } from './Helpers';
import { secsToCanvasX } from './Helpers';
@ -49,11 +50,11 @@ export const Overview: React.FC<Props> = ({
return;
}
const resp = await fetch(
`http://localhost:8888/api/media_sets/${mediaSet.id}/peaks?start=0&end=${mediaSet.audio.frames}&bins=${CanvasLogicalWidth}`
);
const peaks = await resp.json();
setPeaks(peaks);
// const resp = await fetch(
// `http://localhost:8888/api/media_sets/${mediaSet.id}/peaks?start=0&end=${mediaSet.audioFrames}&bins=${CanvasLogicalWidth}`
// );
// const peaks = await resp.json();
// setPeaks(peaks);
})();
}, [mediaSet]);
@ -84,9 +85,9 @@ export const Overview: React.FC<Props> = ({
if (currentSelection.start < currentSelection.end) {
const x1 =
(currentSelection.start / mediaSet.audio.frames) * CanvasLogicalWidth;
(currentSelection.start / mediaSet.audioFrames) * CanvasLogicalWidth;
const x2 =
(currentSelection.end / mediaSet.audio.frames) * CanvasLogicalWidth;
(currentSelection.end / mediaSet.audioFrames) * CanvasLogicalWidth;
ctx.beginPath();
ctx.strokeStyle = 'red';
@ -98,10 +99,10 @@ export const Overview: React.FC<Props> = ({
}
// draw position marker:
const fullSelection = { start: 0, end: mediaSet.audio.frames }; // constantize?
const fullSelection = { start: 0, end: mediaSet.audioFrames }; // constantize?
const x = secsToCanvasX(
position,
mediaSet.audio.sampleRate,
mediaSet.audioSampleRate,
fullSelection
);
// should never happen:
@ -135,7 +136,7 @@ export const Overview: React.FC<Props> = ({
}
const frame = Math.floor(
mediaSet.audio.frames *
mediaSet.audioFrames *
(mouseEventToCanvasX(evt) / evt.currentTarget.width)
);
@ -155,7 +156,7 @@ export const Overview: React.FC<Props> = ({
}
const frame = Math.floor(
mediaSet.audio.frames *
mediaSet.audioFrames *
(mouseEventToCanvasX(evt) / evt.currentTarget.width)
);
@ -164,8 +165,8 @@ export const Overview: React.FC<Props> = ({
const frameCount = selection.end - selection.start;
let start = Math.max(0, selection.start + diff);
let end = start + frameCount;
if (end > mediaSet.audio.frames) {
end = mediaSet.audio.frames;
if (end > mediaSet.audioFrames) {
end = mediaSet.audioFrames;
start = end - frameCount;
}
setNewSelection({

View File

@ -1,5 +1,6 @@
import { useEffect, useState, useRef } from 'react';
import { Frames, MediaSet } from './App';
import { Frames } from './App';
import { MediaSet } from './generated/media_set';
import { WaveformCanvas } from './WaveformCanvas';
import { secsToCanvasX } from './Helpers';
@ -33,14 +34,14 @@ export const Waveform: React.FC<Props> = ({
let endFrame = viewport.end;
if (endFrame <= viewport.start) {
endFrame = mediaSet.audio.frames;
endFrame = mediaSet.audioFrames;
}
const resp = await fetch(
`http://localhost:8888/api/media_sets/${mediaSet.id}/peaks?start=${viewport.start}&end=${endFrame}&bins=${CanvasLogicalWidth}`
);
const newPeaks = await resp.json();
setPeaks(newPeaks);
// const resp = await fetch(
// `http://localhost:8888/api/media_sets/${mediaSet.id}/peaks?start=${viewport.start}&end=${endFrame}&bins=${CanvasLogicalWidth}`
// );
// const newPeaks = await resp.json();
// setPeaks(newPeaks);
})();
}, [mediaSet, viewport]);
@ -63,7 +64,7 @@ export const Waveform: React.FC<Props> = ({
return;
}
const x = secsToCanvasX(position, mediaSet.audio.sampleRate, viewport);
const x = secsToCanvasX(position, mediaSet.audioSampleRate, viewport);
if (x == null) {
return;
}

View File

@ -1484,6 +1484,59 @@
schema-utils "^2.6.5"
source-map "^0.7.3"
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
"@protobufjs/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
"@protobufjs/codegen@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
"@protobufjs/eventemitter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
"@protobufjs/fetch@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
dependencies:
"@protobufjs/aspromise" "^1.1.1"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/float@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
"@protobufjs/inquire@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
"@protobufjs/path@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
"@protobufjs/pool@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@rollup/plugin-node-resolve@^7.1.1":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca"
@ -1812,6 +1865,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -1822,6 +1880,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055"
integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==
"@types/node@>=13.7.0":
version "16.11.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
"@types/node@^12.0.0":
version "12.20.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.23.tgz#d0d5885bb885ee9b1ed114a04ea586540a1b2e2a"
@ -1832,11 +1895,21 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
"@types/object-hash@^1.3.0":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b"
integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prettier@^1.19.0":
version "1.19.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
"@types/prettier@^2.0.0":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd"
@ -4046,6 +4119,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
dataloader@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -5618,7 +5696,7 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
google-protobuf@^3.15.5, google-protobuf@^3.19.0:
google-protobuf@^3.19.0:
version "3.19.0"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.19.0.tgz#97f474323c92f19fd6737af1bb792e396991e0b8"
integrity sha512-qXGAiv3OOlaJXJNeKOBKxbBAwjsxzhx+12ZdKOkZTsqsRkyiQRmr/nBkAkqnuQ8cmA9X5NVXvObQTpHVnXE2DQ==
@ -7365,6 +7443,11 @@ loglevel@^1.6.8:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -7940,6 +8023,11 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-hash@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
object-inspect@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
@ -9155,6 +9243,11 @@ prettier@2.4.0:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.0.tgz#85bdfe0f70c3e777cf13a4ffff39713ca6f64cba"
integrity sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==
prettier@^2.0.2:
version "2.4.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
pretty-bytes@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@ -9232,6 +9325,25 @@ prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
protobufjs@^6.8.8:
version "6.11.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/long" "^4.0.1"
"@types/node" ">=13.7.0"
long "^4.0.0"
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@ -9981,6 +10093,13 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^7.4.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68"
integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==
dependencies:
tslib "~2.1.0"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@ -11060,12 +11179,34 @@ ts-pnp@1.2.0, ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
ts-protoc-gen@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz#2fec5930b46def7dcc9fa73c060d770b7b076b7b"
integrity sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g==
ts-poet@^4.5.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3"
integrity sha512-DXJ+mBJIDp+jiaUgB4N5I/sczHHDU2FWacdbDNVAVS4Mh4hb7ckpvUWVW7m7/nAOcjR0r4Wt+7AoO7FeJKExfA==
dependencies:
google-protobuf "^3.15.5"
"@types/prettier" "^1.19.0"
lodash "^4.17.15"
prettier "^2.0.2"
ts-proto-descriptors@^1.2.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz#760ebaaa19475b03662f7b358ffea45b9c5348f5"
integrity sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw==
dependencies:
long "^4.0.0"
protobufjs "^6.8.8"
ts-proto@^1.83.3:
version "1.83.3"
resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.83.3.tgz#ada7483035ddc946aa686dad1049e4fe45ae1d0f"
integrity sha512-r6MKFjoc4Og2kB4cNJ/bddLebdIwhouG5plu0Rry1jJMEqp2GKA7AE4FrR/FnTCIGbNPYP4622lBqckZd7UHcQ==
dependencies:
"@types/object-hash" "^1.3.0"
dataloader "^1.4.0"
object-hash "^1.3.1"
protobufjs "^6.8.8"
ts-poet "^4.5.0"
ts-proto-descriptors "^1.2.1"
tsconfig-paths@^3.9.0:
version "3.9.0"
@ -11082,7 +11223,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3:
tslib@^2.0.3, tslib@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==

View File

@ -6,25 +6,19 @@ option go_package = "pb/media_set";
import "google/protobuf/duration.proto";
message MediaSet {
message Audio {
int32 channels = 1;
int64 approx_frames = 2;
int64 frames = 3;
int32 sample_rate = 4;
int32 youtube_itag = 5;
string mime_type = 6;
};
message Video {
google.protobuf.Duration duration = 1;
int32 youtube_itag = 2;
string mime_type = 3;
};
string id = 1;
string youtube_id = 2;
Audio audio = 3;
Video video = 4;
int32 audio_channels = 3;
int64 audio_approx_frames = 4;
int64 audio_frames = 5;
int32 audio_sample_rate = 6;
int32 audio_youtube_itag = 7;
string audio_mime_type = 8;
google.protobuf.Duration video_duration = 9;
int32 video_youtube_itag = 10;
string video_mime_type = 11;
};
message GetAudioProgress {

View File

@ -5,7 +5,7 @@
# protobuf (pacman -S protobuf)
# protoc-gen-go (go install google.golang.org/protobuf/cmd/protoc-gen-go@latest)
# protoc-gen-go-grpc (go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest)
# protoc-gen-ts (uses local copy from node_modules)
# ts-proto (uses local copy from node_modules)
#
set -ex
@ -17,9 +17,9 @@ mkdir -p $TARGET_FRONTEND
protoc \
-I./proto/ \
--plugin="protoc-gen-ts=./frontend/node_modules/.bin/protoc-gen-ts" \
--plugin="./frontend/node_modules/.bin/protoc-gen-ts_proto" \
--go_out="$TARGET_BACKEND" \
--go-grpc_out="$TARGET_BACKEND" \
--js_out="import_style=commonjs,binary:$TARGET_FRONTEND" \
--ts_out="service=grpc-web:$TARGET_FRONTEND" \
--ts_proto_out="outputClientImpl=grpc-web,useOptionals=true,esModuleInterop=true:$TARGET_FRONTEND" \
./proto/*