Commit 99176a92 by Jonathan Thomas

Integrate Caption effect into Export API

parent a91843ad
......@@ -79,7 +79,7 @@
<div class="modal-footer">
<button type="button" class="btn btn-secondary" ref="Close" data-bs-dismiss="modal">Cancel</button>
<a v-if="current_export.output" :href="current_export.output" class="btn btn-info" download target="_blank">Download</a>
<button v-if="!current_export.output" type="button" class="btn btn-primary" @click="startExport">Export</button>
<button v-if="!current_export.output" type="button" class="btn btn-primary" @click="startExport" :disabled="!current_export.output && current_export.progress > 0">Export</button>
<button v-if="current_export.output" type="button" class="btn btn-primary" @click="startExport">Export Again</button>
</div>
</div>
......@@ -90,6 +90,7 @@
<script>
import {mapActions, mapState, mapMutations, mapGetters} from "vuex"
import { Modal } from "bootstrap"
import caption from "../data/caption.json"
export default {
name: "Clips.vue",
......@@ -116,6 +117,51 @@ export default {
this.editClip(payload)
},
async startExport() {
// Search for existing caption effect (we can re-use it)
let caption_results = this.effects.filter(e => e.layer == 999 && e.position == 0.0 && e.type == 'Caption')
let effect_payload = {}
let is_effect_new = false
// Get or create caption effect object
if (caption_results.length == 1) {
effect_payload = caption_results[0]
effect_payload.end = 0
effect_payload.json = caption
} else {
is_effect_new = true
effect_payload = {
position: 0,
start: 0,
end: 0,
layer: 999,
project: this.project.url,
title: 'Caption',
type: 'Caption',
json: caption
}
}
// Generate caption text (w/ timestamps)
caption.caption_text = ""
for (let clip of this.thumbnailedClips) {
if (clip.json.text) {
let clip_right_edge = clip.position + (clip.end - clip.start)
if (clip_right_edge > effect_payload.end) {
effect_payload.end = clip_right_edge
}
let start_timestamp = new Date(clip.position * 1000).toISOString().substr(11, 12)
let end_timestamp = new Date((clip.position + (clip.end - clip.start)) * 1000).toISOString().substr(11, 12)
caption.caption_text += `${start_timestamp} --> ${end_timestamp}\n${clip.json.text}\n\n`
}
}
// Save caption effect
if (is_effect_new) {
await this.createEffect(effect_payload)
} else {
await this.updateEffect(effect_payload)
}
// Create export
let payload = {
"export_type": "video",
"video_format": "mp4",
......@@ -164,12 +210,12 @@ export default {
let myModal = new Modal(this.$refs.exportModal)
myModal.show()
},
...mapActions(['loadClips', 'createClip', 'deleteClip', 'editClip', 'moveClip', 'createExport']),
...mapActions(['loadClips', 'createClip', 'deleteClip', 'editClip', 'moveClip', 'createExport', 'createEffect', 'updateEffect']),
...mapMutations((['setPreviewClip', 'setClips', 'setScrollToClip']))
},
computed: {
...mapGetters(['totalClipDuration', 'thumbnailedClips']),
...mapState(['clips', 'preview', 'scrollToClip', 'current_export'])
...mapState(['clips', 'preview', 'scrollToClip', 'current_export', 'effects'])
},
watch: {
scrollToClip() {
......
{
"background": {
"alpha": {
"Points": [
{
"co": {
"X": 1,
"Y": 255
},
"handle_type": 0,
"interpolation": 0
}
]
},
"blue": {
"Points": [
{
"co": {
"X": 1,
"Y": 0
},
"handle_type": 0,
"interpolation": 0
}
]
},
"green": {
"Points": [
{
"co": {
"X": 1,
"Y": 0
},
"handle_type": 0,
"interpolation": 0
}
]
},
"red": {
"Points": [
{
"co": {
"X": 1,
"Y": 0
},
"handle_type": 0,
"interpolation": 0
}
]
}
},
"background_alpha": {
"Points": [
{
"co": {
"X": 1,
"Y": 0
},
"handle_type": 0,
"interpolation": 0
}
]
},
"background_corner": {
"Points": [
{
"co": {
"X": 1,
"Y": 10
},
"handle_type": 0,
"interpolation": 0
}
]
},
"background_padding": {
"Points": [
{
"co": {
"X": 1,
"Y": 20
},
"handle_type": 0,
"interpolation": 0
}
]
},
"caption_font": "sans",
"caption_text": "00:00:00:000 --> 00:10:00:000\\nEdit this caption with our caption editor",
"class_name": "Caption",
"color": {
"alpha": {
"Points": [
{
"co": {
"X": 1,
"Y": 255
},
"handle_type": 0,
"interpolation": 0
}
]
},
"blue": {
"Points": [
{
"co": {
"X": 1,
"Y": 255
},
"handle_type": 0,
"interpolation": 0
}
]
},
"green": {
"Points": [
{
"co": {
"X": 1,
"Y": 255
},
"handle_type": 0,
"interpolation": 0
}
]
},
"red": {
"Points": [
{
"co": {
"X": 1,
"Y": 255
},
"handle_type": 0,
"interpolation": 0
}
]
}
},
"fade_in": {
"Points": [
{
"co": {
"X": 1,
"Y": 0.3499999940395355
},
"handle_type": 0,
"interpolation": 0
}
]
},
"fade_out": {
"Points": [
{
"co": {
"X": 1,
"Y": 0.3499999940395355
},
"handle_type": 0,
"interpolation": 0
}
]
},
"font_alpha": {
"Points": [
{
"co": {
"X": 1,
"Y": 1
},
"handle_type": 0,
"interpolation": 0
}
]
},
"font_size": {
"Points": [
{
"co": {
"X": 1,
"Y": 48
},
"handle_type": 0,
"interpolation": 0
}
]
},
"left": {
"Points": [
{
"co": {
"X": 1,
"Y": 0.25
},
"handle_type": 0,
"interpolation": 0
}
]
},
"name": "Caption",
"right": {
"Points": [
{
"co": {
"X": 1,
"Y": 0.10000000149011612
},
"handle_type": 0,
"interpolation": 0
}
]
},
"stroke": {
"alpha": {
"Points": [
{
"co": {
"X": 1,
"Y": 255
},
"handle_type": 0,
"interpolation": 0
}
]
},
"blue": {
"Points": [
{
"co": {
"X": 1,
"Y": 169
},
"handle_type": 0,
"interpolation": 0
}
]
},
"green": {
"Points": [
{
"co": {
"X": 1,
"Y": 169
},
"handle_type": 0,
"interpolation": 0
}
]
},
"red": {
"Points": [
{
"co": {
"X": 1,
"Y": 169
},
"handle_type": 0,
"interpolation": 0
}
]
}
},
"stroke_width": {
"Points": [
{
"co": {
"X": 1,
"Y": 0.5
},
"handle_type": 0,
"interpolation": 0
}
]
},
"top": {
"Points": [
{
"co": {
"X": 1,
"Y": 0.75
},
"handle_type": 0,
"interpolation": 0
}
]
},
"type": "Caption"
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ export default createStore({
errors: [],
uploads: [],
exports: [],
effects: [],
project: null,
current_export: { progress: 0.0 },
user: null,
......@@ -61,6 +62,18 @@ export default createStore({
setProject(state, project) {
state.project = project
},
addEffect(state, effectObj) {
state.effects.push(effectObj)
},
setEffects(state, effects) {
state.effects = effects
},
setEffect(state, effectObj) {
state.effects = [
...state.effects = state.effects.filter(e => e.id != effectObj.id),
effectObj
]
},
updateProjectThumbnail(state, payload) {
state.projects = [
...state.projects = state.projects.filter(p => p.id != payload.obj.id),
......@@ -467,6 +480,31 @@ export default createStore({
commit('addError', err.response.data)
}
},
async loadEffects({commit}, project_id) {
try {
const effects_response = await instance.get(`projects/${project_id}/effects/`)
commit('setEffects', effects_response.data.results)
} catch(err) {
commit('addError', err.response.data)
}
},
async createEffect({commit}, payload) {
try {
const response = await instance.post('/effects/', payload)
commit('addEffect', response.data)
} catch(err) {
commit('addError', err.response.data)
}
},
async updateEffect({commit}, payload) {
try {
await instance.patch(payload.url, payload)
commit('setEffect', payload)
} catch(err) {
console.log(err)
commit('addError', err.response.data)
}
},
},
modules: {
}
......
......@@ -37,7 +37,7 @@ export default {
this.checkExportProgress(this.activeExports[0])
}
},
...mapActions(['getProject', 'loadExports', 'checkExportProgress']),
...mapActions(['getProject', 'loadExports', 'checkExportProgress', 'loadEffects']),
...mapMutations(['addError', 'setProject', 'setExports'])
},
computed: {
......@@ -53,7 +53,8 @@ export default {
async mounted() {
try {
let project_id = this.$route.params.id
await this.loadExports(project_id)
this.loadExports(project_id)
this.loadEffects(project_id)
let results = await this.getProject(project_id)
this.setProject(results.data)
this.hasData = true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment