Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Elevista committed Jan 28, 2019
0 parents commit aceb923
Show file tree
Hide file tree
Showing 32 changed files with 12,536 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
root: true,
env: {
browser: true,
node: true
},
rules:{
'vue/max-attributes-per-line':'off',
'vue/require-prop-types':'off',
'vue/require-v-for-key':'off',
'vue/require-default-prop':'off',
'comma-dangle':'off',
'prefer-template':'error'
},
extends: [
'plugin:vue/recommended',
'standard'
],
plugins: ['vue'],
globals: {
_: true,
Vue: true,
moment: true
}
}
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# dependencies
node_modules

# logs
npm-debug.log

# Nuxt build
.nuxt

# Nuxt generate
dist

# Mac OS
.DS_Store

.vscode
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ffmpeg-gli

> Nuxt + Electron
## Build Setup

``` bash
# install dependencies
$ npm install # Or yarn install

# development with vue devtools
$ npm run dev

# build for production
$ npm run build

For detailed explanation on how things work, checkout [Nuxt.js](https://github.com/nuxt/nuxt.js), [Electron.js](https://electronjs.org/), and [electron-builder](https://www.electron.build/).
Binary file added assets/MaterialIcons-Regular.woff2
Binary file not shown.
31 changes: 31 additions & 0 deletions assets/material-icons.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url('./MaterialIcons-Regular.woff2') format('woff2');
}

.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;

/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;

/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;

/* Support for IE. */
font-feature-settings: 'liga';
}
75 changes: 75 additions & 0 deletions components/Inputs/Input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<mu-card class="input">
<mu-card-header>
<mu-icon :value="icon" class="icon" color="rgba(0,0,0,0.54)"/>
<span class="name">{{ input.name }}</span>
<mu-button small icon color="rgba(0,0,0,0.7)" @click="$emit('remove')">
<mu-icon value="clear"/>
</mu-button>
</mu-card-header>
<mu-card-text>
<mu-list>
<draggable :value="input.streams" :options="draggableOptions">
<stream v-for="stream of input.streams" :stream="stream" :key="stream.key"/>
</draggable>
</mu-list>
</mu-card-text>
</mu-card>
</template>

<script>
import Stream from './Stream.vue'
export default {
name: 'Input',
components: { Stream },
props: ['input'],
data () {
return {
draggableOptions: {
group: {
name: 'shared',
pull: 'clone',
put: false
},
animation: 150,
sort: false
}
}
},
computed: {
isVideo () {
const { type = '', ext = '' } = this.input
const exts = ['.mp4', '.avi', '.mkv', '.ts', '.mpeg', '.webm']
return type.startsWith('video') || exts.includes(ext)
},
isAudio () {
const { type = '', ext = '' } = this.input
const exts = ['.m4a', '.mp3', '.aac', '.ac3']
return type.startsWith('audio') || exts.includes(ext)
},
isSubtitle () {
const exts = ['.smi', '.aas', '.srt', '.sub']
return exts.includes(this.input.ext)
},
icon () {
const { isVideo, isAudio, isSubtitle } = this
if (isVideo) return 'videocam'
if (isAudio) return 'audiotrack'
if (isSubtitle) return 'subtitles'
return 'input'
},
info () {
const { lastModified, type } = this.input
return [moment(lastModified).format('YYYY.MM.DD'), type].filter(x => x).join(', ')
}
},
}
</script>
<style scoped>
.icon{width: 48px;height: 48px; padding: 12px;}
.input:first-child{ margin-bottom:8px;}
.input.sortable-ghost{pointer-events: none;} /* nested drop problem */
.input:not(:first-child){ margin-top: 8px;margin-bottom:8px;}
.mu-card-header{display: flex;flex-direction: row;align-items: center;padding-bottom: 0px;height: 57px;justify-content: space-between;}
.name{margin:0;padding:0 6px;min-height: 0;overflow:hidden;text-overflow: ellipsis;font-size: 15px;flex:1}
</style>
118 changes: 118 additions & 0 deletions components/Inputs/Inputs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<template>
<mu-flex class="inputs" fill style="max-width:100%" align-items="stretch" direction="column" @drop.prevent="drop">
<mu-flex class="title" align-items="center" justify-content="between">
Inputs
<mu-button small icon color="rgba(0,0,0,0.7)" @click="openFiles">
<mu-icon value="add"/>
</mu-button>
</mu-flex>
<draggable :value="inputs" :options="draggableOptions" class="cards" style="list-style-type: none;">
<!-- eslint-disable-next-line vue/html-self-closing -->
<Input v-for="(input,idx) of inputs" :input="input" :key="input.key" @remove="onRemove(idx)"/>
</draggable>
</mu-flex>
</template>
<script>
import Input from './Input.vue'
import ffmpeg from '~/utils/ffmpeg'
import { dirname, extname, basename } from 'path'
import { remote } from 'electron'
import fs from 'fs'
import { promisify } from 'util'
import { lookup as mime } from 'mime-types'
const stat = promisify(fs.stat)
export default {
name: 'Inputs',
components: { Input },
data () {
return {
draggableOptions: {
group: {
name: 'shared',
pull: 'clone',
put: false
},
animation: 150,
sort: false
}
}
},
computed: {
inputs () { return this.$store.state.inputs }
},
methods: {
async openFiles () {
const selectedFiles = remote.dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'] })
if (!selectedFiles) return
const files = []
for (const path of selectedFiles) {
const { mtimeMs: lastModified, mtime: lastModifiedDate, size } = await stat(path)
files.push({ lastModified, lastModifiedDate, name: basename(path), path, size, type: mime(extname(path)) })
}
this.addFiles(files)
},
onRemove (idx) {
this.$store.commit('removeInput', idx)
},
parseStreamText (text) {
const match = text.match(/Stream #(\d+:\d+)(\(\w+\))?: (Video|Audio|Subtitle): (.+)/)
if (!match) return
const [ , id, , type, etc ] = match
const [codec, ...info] = etc.split(', ')
const [input, stream] = id.split(':')
const inputKey = this.$store.state.inputs[input].key
return { class: 'Stream', input, stream, key: Math.random().toString().slice(2), type, codec, info, inputKey }
},
async setStreams () {
const res = await ffmpeg(this.inputs.map(x => ['-i', x.path]))
const durations = (res.match(/Duration: [\w:/.]+/g) || [])
.map(x => x.slice(10))
.map(x => /[\d:.]+/.test(x) && moment.duration(x))
const streams = (res.match(/Stream #\d+:\d+(\(\w+\))?:.+/g) || [])
.map(this.parseStreamText).filter(x => x)
streams.filter(stream => !this.inputs[stream.input].streams).forEach(stream => {
const input = this.inputs[stream.input]
const streams = (this.inputs[stream.input].streams || []).slice()
streams.push(stream)
stream.duration = durations[stream.input]
this.$store.commit('assignInput', [input, { streams }])
})
},
async addFiles (files) {
const q = []
let dir = ''
for (const f of files) {
const { lastModified, lastModifiedDate, name, path, size, type } = f
if (/Stream #\d+:\d+/.test(await ffmpeg('-i', path))) {
q.push(() => this.$store.commit('addInput', {
class: 'Input',
lastModified,
lastModifiedDate,
name,
path,
size,
type,
dir: dirname(path),
ext: extname(name),
key: Math.random().toString().slice(2),
streams: null
}))
dir = dirname(path)
}
}
if (dir) this.$store.commit('setInputDir', dir)
if (q.length) {
q.forEach(x => x())
this.setStreams()
}
},
drop (evt) {
this.addFiles(evt.dataTransfer.files)
}
}
}
</script>
<style scoped>
.cards{ list-style-type: none; flex:1; padding: 8px; }
</style>
35 changes: 35 additions & 0 deletions components/Inputs/Stream.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<mu-list-item class="stream" button>
<mu-list-item-action>
<mu-icon :value="icon"/>
</mu-list-item-action>
<mu-list-item-content>
<mu-list-item-title>{{ stream.codec }} [{{ stream.input }}:{{ stream.stream }}]</mu-list-item-title>
<mu-list-item-sub-title>{{ stream.info.join(', ') }}</mu-list-item-sub-title>
</mu-list-item-content>
</mu-list-item>
</template>
<script>
export default {
name: 'Stream',
props: ['stream'],
computed: {
icon () {
switch (this.stream.type) {
case 'Video': return 'videocam'
case 'Audio': return 'audiotrack'
case 'Subtitle': return 'subtitles'
default: return 'input'
}
}
},
}
</script>
<style scoped>
.stream {display: flex;}
.stream >>> .mu-item-wrapper{flex:1}
.stream >>> .mu-item{height: auto;min-height: 40px;padding-top: 2px;padding-bottom:2px;}
.mu-item-title,.mu-item-sub-title{white-space: normal;height: auto;}
.mu-item-title{font-size:14px;}
.mu-item-sub-title{font-size:13px;}
</style>
Loading

0 comments on commit aceb923

Please sign in to comment.