{"version":3,"file":"7311.eb3db2283be103db.js","mappings":"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","names":["PAGE","ClipsService","_ClipsService","constructor","store","sentryService","clipsApi","youtubeService","analyticsService","this","getPoster","clip","res","poster","DEFAULT_POSTER","youtube_id","getThumbnail","url","createClipFromFilestack","filestackResult","projectId","userId","_this","_asyncToGenerator","uploadTags","lastModifiedDate","duration","environment","production","console","log","warn","Error","newVideo","filename","type","mimetype","size","Date","toISOString","loading","originalFile","lastModified","convertVideoFileToClip","source","src","filestackConfig","storeTo","path","orig","key","name","file","id","Utils","removeFileExt","filmingDate","getDateTimeString","createClip","then","newClip","subClipsUpdated","dispatch","mystackActions","toFixed","seconds","durationInSeconds","clipUpload","error","DEBUG_LOGS","hlsMeta","HLS_META_TRANSCODE","Clip","clipActions","catch","err","Array","isArray","errors","length","errorType","info","message","params","sub","subscribeClipsUpdated","subscribe","next","updates","JSON","parse","hlsSrc","sources","Object","keys","isHlsComplete","unsubscribe","captureError","errorMessage","complete","getPosterTimeFromPoster","sNum","substring","lastIndexOf","parsed","parseInt","updatePosterToPosterTime","posterTime","newPoster","slice","startIndex","endIndex","handleSubClips","i0","i1","i2","i3","i4","i5","factory","ɵfac","providedIn","ClipUploadDirective","_ClipUploadDirective","BaseFileUpload","filestackService","translate","platform","super","allowedFileTypes","customPickerOptions","maxSize","MAX_VIDEO_CAPTURE_SIZE_MB","maxFiles","accept","container","region","uploadConfig","tags","fromSources","onClick","event","openFilePicker","makeFileUploadName","makeCaptureClipFilename","onFileSelected","Promise","resolve","reject","instant","includes","not_supported_file_type","supported_file_types","join","checkVideoLength","durationOK","durationMessage","minutes","MAX_VIDEO_CAPTURE_LENGTH_MINUTES","originalName","filelastModified","FilelastModified","now","pinpointEvent","platforms","toString","lastModifiedDateISO","filesize","video","document","createElement","preload","checkDuration","maxCaptureLength","MAX_VIDEO_CAPTURE_LENGTH_SECONDS","removeEventListener","window","URL","webkitURL","revokeObjectURL","addEventListener","captureMessage","createObjectURL","selectors","hostBindings","rf","ctx","$event"],"ignoreList":[],"sourceRoot":"webpack:///","sources":["./src/app/clips/shared/services/clips.service.ts","./src/app/core/filestack/directives/clip-upload.directive.ts"],"sourcesContent":["/** @format */\n\nimport { Injectable } from '@angular/core';\nimport { Store } from '@ngrx/store';\nimport { State } from '@store/reducers';\nimport * as clipActions from '@store/actions/clips.actions';\nimport * as mystackActions from '@store/actions/mystack.actions';\nimport { ClipsApiService } from '@app/core/api/clips-api.service';\nimport { AnalyticsService } from '@app/core/services/analytics/analytics.service';\nimport { SentryService } from '@app/core/services/sentry.service';\nimport { YoutubeService } from '@app/core/services/youtube.service';\nimport { Utils } from '@app/shared/utils';\nimport { ClipVideoFile, Clip } from '@app/shared/models/clip.model';\nimport { convertVideoFileToClip, isHlsComplete } from '@app/shared/models/clip.model';\nimport { HLS_META_TRANSCODE, DEFAULT_POSTER } from '@app/shared/models/clip.model';\nimport { environment, filestackConfig } from 'src/environments/environment';\n\nconst DEBUG_LOGS = false;\nconst PAGE = '[ClipsService]';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class ClipsService {\n constructor(\n private store: Store<State>,\n private sentryService: SentryService,\n private clipsApi: ClipsApiService,\n private youtubeService: YoutubeService,\n private analyticsService: AnalyticsService\n ) {}\n\n getPoster(clip: Clip, res: string = 'default') {\n let poster = DEFAULT_POSTER;\n if (clip) {\n if (clip.poster) {\n poster = clip.poster;\n } else if (clip.youtube_id) {\n poster = this.youtubeService.getThumbnail(clip.youtube_id, res).url;\n }\n }\n return poster;\n }\n\n /**\n * Create a new Clip from the FileStack Upload\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async createClipFromFilestack(filestackResult: any, projectId: string, userId: string) {\n DEBUG_LOGS && console.log(`createClipFromFilestack filestackResult =`, filestackResult);\n const {\n uploadTags: {\n lastModifiedDate = null, // = (new Date()).toISOString(),\n // filesize = filestackResult.size, // don't use the stringified from uploadTags\n duration = 0,\n } = {},\n } = filestackResult;\n // MVP-1169 file selected metadata\n !environment.production &&\n console.log(`get size & updatedDate from filestackResult:`, { lastModifiedDate, duration, filestackResult });\n\n if (!filestackResult || !projectId) {\n console.warn(`${PAGE} createClipFromFilestack missing required params`, { filestackResult, projectId });\n throw new Error('Missing required parameters - unable to create clip.');\n }\n\n const newVideo: ClipVideoFile = {\n filename: filestackResult.filename,\n type: filestackResult.mimetype,\n size: filestackResult.size,\n lastModifiedDate: new Date().toISOString(),\n loading: true,\n };\n\n // get additional metadata if it exists.\n if (lastModifiedDate) {\n newVideo.lastModifiedDate = lastModifiedDate;\n } else if (filestackResult.originalFile) {\n if (\n filestackResult.originalFile.lastModifiedDate &&\n typeof filestackResult.originalFile.lastModifiedDate.toISOString === 'function'\n ) {\n newVideo.lastModifiedDate = filestackResult.originalFile.lastModifiedDate.toISOString();\n } else if (filestackResult.originalFile.lastModified) {\n newVideo.lastModifiedDate = new Date(filestackResult.originalFile.lastModified).toISOString();\n }\n }\n\n // create our clip...\n const clip = convertVideoFileToClip(newVideo);\n clip.source = clip.source || {};\n // it is important that this startsWith(filestackConfig.storeTo.path) in the clipApi logic\n clip.source.src = filestackConfig.storeTo.clip.path + newVideo.filename;\n clip.source.orig = filestackResult.key;\n clip.source.filename = newVideo.filename;\n clip.source.type = newVideo.type;\n clip.source.loading = newVideo.loading;\n clip.source.lastModifiedDate = newVideo.lastModifiedDate;\n\n if (filestackResult.originalFile && filestackResult.originalFile.name) {\n clip.source.file = filestackResult.originalFile;\n }\n // this must match for api to do its magic...\n clip.id = Utils.removeFileExt(newVideo.filename);\n clip.projectId = projectId;\n\n if (typeof clip.filmingDate !== 'string') {\n clip.filmingDate = Utils.getDateTimeString(clip.filmingDate);\n }\n\n DEBUG_LOGS && console.log(`${PAGE} createClipFromFilestack -> createClip:`, clip);\n\n // kick off the createClip flow, subscribe to updates, add to mystack\n // as soon as we return, the Capture Page will nav to the Stack Editor\n return this.createClip(clip).then((newClip) => {\n DEBUG_LOGS &&\n console.log(`${PAGE} createClipFromFilestack -> now watch updates & addToStack (createClip result:`, newClip);\n // watch changes and push to store\n this.subClipsUpdated(newClip.projectId, newClip.id, userId);\n // add the clip to the Editor\n this.store.dispatch(mystackActions.addClip({ clip: newClip }));\n\n try {\n // analytics\n const size =\n newVideo && newVideo.size && typeof newVideo.size.toFixed === 'function' ? newVideo.size.toFixed(0) : false;\n const seconds =\n clip.source && clip.source.durationInSeconds && typeof clip.source.durationInSeconds.toFixed === 'function'\n ? clip.source.durationInSeconds.toFixed(3)\n : false;\n this.analyticsService.clipUpload({\n projectId: newClip.projectId,\n id: newClip.id,\n userId: newClip.userId,\n ...(seconds && { seconds }),\n ...(size && { size }),\n });\n } catch (error) {\n console.warn(`Analytics error:`, error);\n }\n });\n }\n\n /**\n * upload clip and add to store\n * @param clip\n * 1. Create the clip in DB\n * 2.\n */\n createClip(clip: Clip): Promise<Clip> {\n DEBUG_LOGS && console.log(`${PAGE} createClip`, clip);\n\n // add the new HLS transcoding instruction\n clip.hlsMeta = {\n ...(clip.hlsMeta || {}),\n ...HLS_META_TRANSCODE,\n };\n\n return this.clipsApi\n .createClip(clip)\n .then((res) => {\n DEBUG_LOGS && console.log(`${PAGE} createClip res:`, res);\n // add the clip to the Store\n const newClip = new Clip(res);\n this.store.dispatch(clipActions.addClip({ clip: newClip }));\n return newClip;\n })\n .catch((err) => {\n if (\n err &&\n Array.isArray(err.errors) &&\n err.errors.length > 0 &&\n err.errors[0] &&\n err.errors[0].errorType &&\n err.errors[0].errorType === 'DynamoDB:ConditionalCheckFailedException'\n ) {\n console.info(`${PAGE} createClip already Exists:`, err);\n return new Clip(clip);\n }\n console.error(`${PAGE} createClip err:`, err);\n if (err && Array.isArray(err.errors) && err.errors.length > 0 && err.errors[0].message) {\n throw err.errors[0].message;\n }\n throw err;\n });\n }\n\n /**\n * clipUpdate GraphQL Subscription\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n subClipsUpdated(projectId: string, id = '', userId = '') {\n const params: { projectId: string; id?: string } = {\n projectId,\n };\n if (id) {\n params.id = id;\n }\n // not currently using userId on this GQL subscription in the API\n // if (userId) { params.userId = userId; }\n\n // keeping the sub variable local so there can be multiple open subs\n const sub = this.clipsApi.subscribeClipsUpdated(params).subscribe({\n next: (res) => {\n // todo: convert to handleSubClips(res) when this not needed by editor\n DEBUG_LOGS && console.log(`${PAGE} clipsUpdatedSubscription next:`, res);\n if (!res || !res.id || !res.projectId) {\n throw new Error('Missing clip id in subscription?');\n }\n const updates: Partial<Clip> = {};\n let hlsMeta;\n if (res.hlsMeta) {\n try {\n hlsMeta = typeof res.hlsMeta === 'string' ? JSON.parse(res.hlsMeta) : res.hlsMeta;\n updates.hlsMeta = hlsMeta;\n } catch (error) {\n console.log(`Caught error during JSON.parse(res.hlsMeta):`, {\n hlsMeta: res.hlsMeta,\n error: error.message || error,\n });\n }\n }\n if (res.hlsSrc) {\n updates.hlsSrc = res.hlsSrc;\n }\n if (Array.isArray(res.sources) && res.sources.length > 0) {\n updates.sources = res.sources;\n }\n if (res.poster) {\n updates.poster = res.poster;\n }\n if (res.duration) {\n updates.duration = res.duration;\n }\n\n if (Object.keys(updates).length > 0) {\n // take the updates and flow to store...\n this.store.dispatch(\n clipActions.updateClipTranscoding({\n projectId: res.projectId,\n id: res.id,\n updates,\n })\n );\n DEBUG_LOGS &&\n console.log(`${PAGE} clipsUpdatedSubscription updateClipTranscoding:`, {\n projectId: res.projectId,\n id: res.id,\n updates,\n });\n }\n\n if (isHlsComplete(new Clip(updates))) {\n // when complete, this.unsubClipsUpdated()\n sub.unsubscribe();\n }\n },\n error: (err) => {\n console.warn(`${PAGE} clipsUpdatedSubscription ERROR:`, err);\n this.sentryService.captureError(err);\n\n if (projectId && id) {\n this.store.dispatch(\n clipActions.updateClipTranscoding({\n projectId,\n id,\n updates: {\n hlsMeta: {\n errorMessage: err.message || err.errorMessage || err,\n },\n },\n })\n );\n }\n sub.unsubscribe();\n },\n complete: () => {\n DEBUG_LOGS && console.log(`${PAGE} clipsUpdatedSubscription => Complete`);\n },\n });\n }\n //end GraphQL Subscription\n\n /**\n * Take the poster string and return poster time from the transcoded name\n * example poster path:\n * https://videos.filmstacker.com/public/filmstacker-dev/jd_-_jd-note_202203021348/jd_-_jd-note_202203021348_thumb.0000000.jpg\n */\n getPosterTimeFromPoster(clip: Clip) {\n if (clip && clip.poster) {\n const sNum = clip.poster.substring(clip.poster.lastIndexOf('_thumb.'), clip.poster.lastIndexOf('.'));\n // there shouldn't be any dashes anymore\n // const sTime = sNum.replace(/-/g, '').replace(/0/gi, '');\n try {\n const parsed = parseInt(sNum, 10);\n return parsed > 0 ? parsed : 0;\n } catch (error) {\n return 0;\n }\n }\n return 0;\n }\n\n /**\n * Update the poster image to match the new posterTime\n *\n * @todo verify it exists in s3\n * potential ref: https://stackoverflow.com/questions/9815762/detect-when-an-image-fails-to-load-in-javascript\n *\n * example poster path:\n * https://videos.filmstacker.com/public/filmstacker-dev/jd_-_jd-note_202203021348/jd_-_jd-note_202203021348_thumb.0000000.jpg\n */\n updatePosterToPosterTime(clip: Clip, posterTime: number) {\n if (clip && clip.poster) {\n let newPoster = clip.poster;\n const sNum = ('0000000' + posterTime).slice(-7);\n const startIndex = newPoster.lastIndexOf('_thumb.') + '_thumb.'.length;\n const endIndex = newPoster.lastIndexOf('.');\n newPoster = newPoster.slice(0, startIndex) + sNum + newPoster.slice(endIndex);\n console.log(`${PAGE} change clip.poster (TODO: verify it exists in s3) posterTime: ${posterTime} to:`, {\n sNum,\n newPoster,\n });\n return newPoster;\n } else {\n console.warn(`${PAGE} missing clip.poster - unable to change`, clip);\n return null;\n }\n }\n\n /**\n * @deprecated unused - use subClipsUpdated\n * Do what we need with the clip we just received\n */\n private handleSubClips(res) {\n DEBUG_LOGS && console.log(`${PAGE} handleSubClips next:`, res);\n if (!res || !res.id || !res.projectId) {\n throw new Error('Missing clip id in subscription?');\n }\n // if any values exist not null, add them as changes?\n const updates: Partial<Clip> = {};\n let hlsMeta;\n if (res.hlsMeta) {\n try {\n hlsMeta = typeof res.hlsMeta === 'string' ? JSON.parse(res.hlsMeta) : res.hlsMeta;\n updates.hlsMeta = hlsMeta;\n } catch (error) {\n console.log(`Caught error during JSON.parse(res.hlsMeta):`, {\n hlsMeta: res.hlsMeta,\n error: error.message || error,\n });\n }\n }\n if (res.hlsSrc) {\n updates.hlsSrc = res.hlsSrc;\n }\n if (Array.isArray(res.sources) && res.sources.length > 0) {\n updates.sources = res.sources;\n }\n if (res.poster) {\n updates.poster = res.poster;\n }\n if (res.duration) {\n updates.duration = res.duration;\n }\n\n // take the updates and flow to store...\n // this.store.dispatch(clipActions.updateClipTranscoding({\n // projectId: res.projectId,\n // id: res.id,\n // updates,\n // }));\n }\n}\n","/** @format */\n\nimport { Directive, HostListener, Input } from '@angular/core';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Platform } from '@ionic/angular/standalone';\nimport {\n MAX_VIDEO_CAPTURE_SIZE_MB,\n MAX_VIDEO_CAPTURE_LENGTH_MINUTES,\n MAX_VIDEO_CAPTURE_LENGTH_SECONDS,\n} from '@app/app.config';\nimport { SentryService } from '@app/core/services/sentry.service';\nimport { FilestackService, PickerFileMetadata } from '@app/core/filestack/filestack.service';\nimport { makeCaptureClipFilename } from '@app/clips/shared/clips.model';\nimport { environment, filestackConfig } from 'src/environments/environment';\nimport { BaseFileUpload } from './base-file-upload';\n\n@Directive({\n selector: '[clipUpload]',\n standalone: true,\n})\nexport class ClipUploadDirective extends BaseFileUpload {\n @Input() projectId: string = '';\n @Input() userId: string = 'anon';\n\n /**\n * Needs to align with transcode-pipeline-step-functions,\n * only includes these:\n * .mpg, .mp4, .m4v, .mov, .m2ts\n *\n * @note we can add video/mpeg (need to enforce .mpg ext & not .mpeg) & video/MP2T (enforce .m2ts ext) if desired\n */\n protected allowedFileTypes: string[] = ['video/mp4', 'video/x-m4v', 'video/quicktime'];\n protected customPickerOptions = {\n maxSize: MAX_VIDEO_CAPTURE_SIZE_MB * 1024 * 1024, // 600 MB\n maxFiles: 24,\n accept: 'video/*',\n storeTo: {\n path: filestackConfig.storeTo.clip.path, // note that projectId is not ready yet\n container: filestackConfig.storeTo.clip.container,\n region: filestackConfig.storeTo.clip.region,\n },\n uploadConfig: {\n tags: {},\n },\n fromSources: ['local_file_system', 'video', 'googledrive', 'onedrive', 'facebook', 'box', 'url'],\n };\n\n constructor(\n filestackService: FilestackService,\n translate: TranslateService,\n platform: Platform,\n sentryService: SentryService\n ) {\n super(filestackService, translate, platform, sentryService);\n }\n\n @HostListener('click', ['$event'])\n onClick(event: Event) {\n // we need to get the projectId Input which is not available at construct...\n if (this.projectId) {\n const path = this.customPickerOptions.storeTo.path;\n this.customPickerOptions.storeTo.path = path + this.projectId + '/';\n this.customPickerOptions.uploadConfig.tags = { projectId: this.projectId };\n }\n this.openFilePicker(event);\n }\n\n makeFileUploadName(file: PickerFileMetadata, userId = this.userId) {\n return makeCaptureClipFilename(userId, file.filename);\n }\n\n /**\n * Called whenever user selects a file, used to verify the selected file before upload\n * If you throw any error in this function it will reject the file selection.\n * The error message will be displayed to the user as an alert.\n * https://filestack.github.io/filestack-js/interfaces/pickeroptions.html#onfileselected\n *\n * @todo This currently only works for local uploads. Should migrate to acceptFn callback usage for files uploaded from local_source, as well as, from cloud providers.\n * https://www.filestack.com/docs/uploads/pickers/web/#acceptFn\n */\n onFileSelected(file: PickerFileMetadata): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (!file || !file.originalFile) {\n reject(this.translate.instant('FILE_UPLOAD.FILE_NOT_FOUND'));\n }\n if (this.allowedFileTypes && !this.allowedFileTypes.includes(file.mimetype)) {\n reject(\n this.translate.instant('FILE_UPLOAD.FORMAT_NOT_SUPPORTED', {\n not_supported_file_type: file.mimetype,\n supported_file_types: this.allowedFileTypes.join(', '),\n })\n );\n }\n this.checkVideoLength(file.originalFile)\n .then(({ durationOK, durationMessage }) => {\n console.log({ durationOK, durationMessage });\n if (!durationOK) {\n console.log(durationMessage);\n reject(\n `${this.translate.instant('FILE_UPLOAD.DURATION_EXCEEDS_LIMIT', {\n minutes: MAX_VIDEO_CAPTURE_LENGTH_MINUTES,\n duration: durationMessage,\n })}. ${this.translate.instant('FILE_UPLOAD.FILENAME')}: '${file.filename || 'Unknown'}'`\n );\n }\n const name = this.makeFileUploadName(file, this.userId);\n console.log(`[Capture] onFileSelected filename:`, {\n name,\n durationMessage,\n originalName: file.filename,\n file,\n });\n\n // MVP-1092 Capture date not correct on iOS\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const originalFile = file.originalFile as any;\n const lastModifiedDate = originalFile?.lastModifiedDate ?? new Date();\n const filelastModified = originalFile?.FilelastModified ?? Date.now();\n\n /** @todo send Pinpoint event with details MVP-730 */\n const pinpointEvent = {\n platforms: this.platform.platforms(),\n file: {\n filename: file.filename || '',\n name,\n mimetype: file.mimetype || 'unknown',\n FilelastModified: filelastModified,\n lastModifiedDate: lastModifiedDate.toString(),\n lastModifiedDateISO: lastModifiedDate.toISOString(),\n filesize: file.size || 0,\n source: file.source || 'unknown',\n },\n };\n\n !environment.production && console.log(`todo: pinpoint event with clip details:`, pinpointEvent);\n\n // add the size and modified date to the clip file so we can get it back when done\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const upload_tags = this.customPickerOptions.uploadConfig.tags;\n\n this.customPickerOptions.uploadConfig.tags = {\n ...upload_tags,\n lastModifiedDate: pinpointEvent.file.lastModifiedDateISO,\n duration: durationMessage,\n // filesize: pinpointEvent.file.filesize.toString(), // not needed...\n };\n\n // Return original object with updated filename:\n resolve({ ...file, name });\n // Or reject the selection with reject()\n })\n .catch((error) => {\n console.error(error);\n reject(this.translate.instant('ERRORS.CAUGHT_ERROR'));\n });\n });\n }\n\n /**\n * Check the Duration of the File does not exceed the max\n */\n checkVideoLength(file): Promise<{ durationOK: boolean; durationMessage: string }> {\n // return Promise.resolve({ durationOK: true, durationMessage: 'Skip for better UX?' });\n return new Promise((resolve) => {\n try {\n // const reader = new FileReader(), // create a FileReader (not needed, instead using video element onDurationChange)\n const video = document.createElement('video'); // create video element;\n video.preload = 'metadata'; // more efficient\n\n const checkDuration = () => {\n if (!video || !video.duration) {\n throw new Error('checkVideoLength checkDuration missing video or duration?');\n }\n const duration = video.duration;\n console.log(`getDuration`, { video, duration, maxCaptureLength: MAX_VIDEO_CAPTURE_LENGTH_SECONDS });\n\n // clean up\n video.removeEventListener('durationchange', checkDuration);\n (window.URL || window.webkitURL).revokeObjectURL(video.src);\n\n if (duration <= MAX_VIDEO_CAPTURE_LENGTH_SECONDS) {\n resolve({ durationOK: true, durationMessage: 'Ok to go!' });\n } else {\n try {\n const durationMessage = (duration / 60).toFixed(1);\n resolve({\n durationOK: false,\n durationMessage: `${durationMessage}`,\n });\n } catch (error) {\n console.log(`parseFloat duration (${duration}) caught`, error);\n resolve({\n durationOK: false,\n durationMessage: this.translate.instant('ERRORS.ERROR'),\n });\n }\n }\n };\n\n video.addEventListener('durationchange', checkDuration);\n // video.addEventListener('loadedmetadata', checkMetadata); // duration is more efficient and has all we need...\n\n video.addEventListener('error', (event) => {\n console.error(`checkVideoLength video listener caught error`, event);\n const { name = 'unknown', type = 'unknown', size = 'unknown' } = file;\n this.sentryService.captureMessage(\n `checkVideoLength video listener caught error filename='${name}' fileType='${type}' filesize='${size}'`\n );\n resolve({\n durationOK: false,\n durationMessage: this.translate.instant('ERRORS.ERROR'),\n });\n });\n\n video.src = (window.URL || window.webkitURL).createObjectURL(file);\n } catch (error) {\n const { name = 'unknown', type = 'unknown', size = 'unknown' } = file;\n console.error(`checkVideoLength caught error filename='${name}' fileType='${type}' filesize='${size}'`);\n console.error(`checkVideoLength caught`, error);\n this.sentryService.captureError(error);\n resolve({\n durationOK: false,\n durationMessage: this.translate.instant('ERRORS.ERROR'),\n });\n }\n });\n }\n}\n"],"x_google_ignoreList":[]}