2020年3月16日 星期一

ffmpeg 常用命令

ffmpeg 基本參數:
-y : 如果輸出檔案已存在,不詢問直接覆蓋
-n : 如果輸出檔案已存在,不詢問直接忽略不覆蓋

注意:
如果輸出檔案跟輸入檔案相同,
在為 video 檔案時,-y 會失效,
會印出
FFmpeg cannot edit existing files in-place.
錯誤

ffmpeg 常用命令:
=============================================================
將圖或影片 Resize 成特定尺寸:
Ex : 將圖或影片 resize 成 320x240
D:\\ffmpeg\\bin\\ffmpeg -i "input.jpg" -vf scale=320:240 "output_320x240.png"
** 注意:
因為 h264 encoder 規定輸出影片長寬須為偶數,
如指定奇數會有 "ffmpeg height not divisible by 2" 的錯誤訊息,可將指令改成:
D:\\ffmpeg\\bin\\ffmpeg -i "input.jpg" -vf scale=floor(321/2)*2:floor(241/2)*2 "output_321x241.png"

=============================================================
將圖或影片在保持長寬比 ration 的情況下 Resize 到能塞到特別尺寸的矩形中,
如原圖檢來就可塞得下,就不 resize
Ex: 將圖或影片保持長寬比並 resize 至塞得下 300x500 的矩形中
D:\ffmpeg\bin\ffmpeg -i "input.jpg" -vf "scale='min(iw, 300)':'min(ih, 500)':force_original_aspect_ratio=decrease" "output_boundedBy_300x500.png"
** 注意:
因為 h264 encoder 規定輸出影片長寬須為偶數,
如指定奇數會有 "ffmpeg height not divisible by 2" 的錯誤訊息,可將指令改成:
D:\ffmpeg\bin\ffmpeg -i "input.jpg" -vf "scale='floor(iw*min(1,min(301/iw,501/ih))/2)*2':'floor(ih*min(1,min(301/iw,501/ih))/2)*2'" "output_boundedBy_301x501.png"

參考:

  1. FFMPEG (libx264) “height not divisible by 2”
  2. ffmpeg转码遇见错误height not divisible by 2的解决方案
  3. [FFmpeg] error about width or height not divisible by 2
=============================================================
將影片壓縮
D:\\ffmpeg\\bin\\ffmpeg -i "inputVideo.mp4" -r 10 -b:a 32k "outputVideo.mp4"
=============================================================
得到影片中,特定秒數幀(frame)的圖片
Ex: 取得 22:11:00 的 frame
D:\\ffmpeg\\bin\\ffmpeg -i "inputVideo.mp4" -y -f  image2  -ss 22:11:00 -vframes 1 "outputFrame.jpg"
=============================================================
將影片中的 "影片資訊 (moov box 或稱 movie box)" 放到檔案二進位較前的位置,讓例如瀏覽器能夠先讀到馬上開始邊播放邊下載影片 (通常瀏覽器需得到如影片長度等資訊後才會開始播放影片),
而不用等到讀到 "影片資訊 (moov box)" 後才開始播放
參考 : MP4 moov box解析
D:\\ffmpeg\\bin\\ffmpeg -i "inputVideo.mp4" -movflags faststart -acodec copy -vcodec copy "outputVideo.mp4"
=============================================================
取得資源 (影片檔或圖片檔,也可為外部網址 url),的資訊,並以 json 格式輸出,
例如長、寬、影片長度等
D:\\ffmpeg\\bin\\ffprobe -show_streams -print_format json "imageOrVideoUrl"
=============================================================
對圖片或影片進行截剪
Ex: 將圖片在 x=135, y=75 的位置(以左上為原點,向右向下為正),以width=270, height = 150 進行截剪
D:\\ffmpeg\\bin\\ffmpeg -i "input.jpg" -vf crop=270:150:135:75 "out.jpg"
=============================================================
將GIF動圖的某個幀(frame)取出,
或者是將所有幀全數取出 (輸出檔名用 %d ,之後會得到多個圖,檔名中的 %d 會被幀數代替 )
Ex:
D:\\ffmpeg\\bin\\ffmpeg -i "input.gif" -vsync 0 "output.png"
D:\\ffmpeg\\bin\\ffmpeg -i "input.gif" -vsync 0 "output_%d.png"
=============================================================
旋轉或翻轉圖片和影片
D:\\ffmpeg\\bin\\ffmpeg -i -vf "transpose=1" output.mp4
Note:
transpose = 0 : 順時鐘團 90 度後,水平左右翻轉
transpose = 1 : 順時鐘團 90 度
transpose = 2 : 逆時鐘團 90 度
transpose = 3 : 順時鐘團 90 度,水平左右翻轉

可以連續旋轉翻轉,用transpose 指令用雙引號(")包住並用逗號(,)隔開,例如
D:\\ffmpeg\\bin\\ffmpeg -i -vf "transpose=1,transpose=1" output.mp4
參考:
How To Rotate Videos Using FFMpeg From Commandline


2020年3月7日 星期六

React - Redux 練習 - ToDo List (待辦事項) 簡易實作

React 如果使用的元件嵌套過於多層,
需要用到的 state 傳值就會必須由上層元件一路傳到下層子元件,
當傳值層數過多的時候,會造成每一層的子元件都要撰寫傳值的程式碼,
而最下層如果要改變某個 state 值,要呼叫 root 元件的某 function 時,
又需要把 function 從 root 元件一路傳到子元件。

例如:
<Element1>
          <Element2 attr={this.state.attr} func1={this.func1}></Element2>
</Element1>

<Element2>
          <Element3 attr={this.props.attr} func1={this.props.func1}></Element3>
</Element2>

<Element3>
          <Element4 attr={this.props.attr} func1={this.props.func1}></Element4>
</Element3>

<Element4>...............</Element4>

而且一路傳值的中間,有的元件可能根本不需要用到某些值。

為了處理上述的問題,
有了 Redux 這樣的一個工具可以幫助我們,
Redux 並不完全是為 React 而開發的,
不過它的概念可以幫助我們。

我們希望可以把 React 的各層元件中,
所用到的 state, function 等都集中在最上層的某個地方來管理,
並且把 state, function 一路傳到 Root 元件上的各子元件中以供利用,
但又不想把傳值的程式碼寫在各子元件上 。

Redux 的想法跟我們很像,
它用 store 來管理 state,state 只會被叫做 action 的 function 來改變,
並且我們可以把 callback function 註冊  (subscribe) 給 store,
只要呼叫 action 來改變 state 時,被註冊的 callback function就會被執行。

對應 React 的概念是,
用 Redux 來管理 state,
用 action 來改變 state,
將 React Render 繪製元件的 function (相當於 render() ) 註冊到 store 上,
將 store 的 state, action 一路傳給所有子元件,
子元件可以從 store 中取得 state 來顯示,
也可取得 action 來改變 state,
而action 被執行時就會執行被註冊的 callback function 來繪製 React 元件。

不過通常我們不會直接使用 redux 在 react 中,
而是使用 react-redux,
react-redux  幫我們處理並優化了 react 與 redux 合作時的細節部份,
並且已經註冊了 react render 繪制 function,
我們只要專注使用 action 來改變 state,
react-redux 就會幫我們自動重繪元件

在這篇文中,我修改了
這篇
React 練習 - ToDo List (待辦事項) 簡易實作
,將它用 Redux + React 的方式改寫

首先安裝 redux 和 react-redux
npm install redux
npm install react-redux



使用 Redux 管理一個統一的 state,其可在各 component 內之間使用,
state 中包括一般 state 及可跟 reducer 配合的 actionCreator

reducer :
Redux 用來決來決定其管理 state 的依據,
為一個 function,輸入為"前一個 state" 和 "要做的行為 action (通常為一個物件,內含純文字的 type 和 action 需要的參數)",返回值為更新後的 state:
Ex :
newState reducer(oldState, action){
     var newState = defaultState;
     switch (action.type){
          case "action1" :
              newState  = .........;
          break;
     }
     return newState;
}

actionCreator :
為多個 function 的集合,共通點是返回 reducer function 所需的 action 物件,
actionCreator 中的各 function 會傳遞給各 React 子元件中以供使用
Ex:
returnAction addToDo(id, text){
     return {id : id, text : text};
}

store :
Redux 所管理的 state

跟之前沒有使用 redux 的範例比較,
我們增加了 action.js 及 reducer.js


/src/components/main/reducer.js :
import { combineReducers } from 'redux'

function toDoListReducer(state = {toDoList: []}, action) {
  /*
  state : {
 toDoList : [{id : new Date().getTime(),toDoText : ""}]]
  }
  */
  switch (action.type) {
    case 'ADD_TODO':
      {
  //action.payload : [{id : new Date().getTime(),toDoText : ""}]  
  if (action.payload.id && action.payload.toDoText.trim()){
   return {
    toDoList : state.toDoList.concat([action.payload])
   };
  }else{
   return state;
  }
      }

    case 'DEL_TODO':
      {
  //action.payload : {id : toDoId}
  return {
   toDoList : state.toDoList.filter(function(toDo){
    return toDo.id != action.payload.id;
   })
  }
      }

    default:
      return state
  }
}

const reducers = combineReducers({
  toDoListReducer
})

export default reducers

說明:
在 reducer.js 中,宣告了一個 toDoListReducer() 來處理 state 及 action 的變化,
根據輸入的 actions 和上一次的 state ,回傳不同的新 state,
state 及 action 的物件格式可以由自己決定,
在這裡, state 裡管理一個 toDoList 陣例,其中存放各個待辦事項的 id 及文字內容。
而 action 裡有 type 及 payload 兩個屬性,type 用來表示是哪種 action,
而 payload 存放此 action 所需的其他資訊。

可以注意到的是, reducer function 可以不只一個 (雖然本例只有一個),
當有多個 reducer function 時,redux 提供了 combinReducers 來將所有的 reducer function
包起來一起 export 出去。

/src/components/main/action.js :
export const addToDo = function(id, toDoText){
 //action.payload : [{id : new Date().getTime(),toDoText : ""}]  
 return { 
  type: 'ADD_TODO',
  payload : {
   id : id,
   toDoText : toDoText
  }
 };
}

export const deleteToDo = function(id){
 //action.payload : {id : toDoId}
 return { 
  type: 'DEL_TODO',
  payload : {
   id : id
  }
 };
}

說明:
在 action.js  中,宣告了想要改變 toDoList 值的各種行為 function,
這些 function 之後會用 redux 傳進各元件中以供使用,
當各元件呼叫這些 action function 時, action function 會將其轉成 reducer function 需要的格式,例如此例中的
{
      type: "someAction",
      payload : { /* some infomation */}
}

/src/index.jsx :
import React from "react"
import ReactDOM from "react-dom"
import Main from "./components/main/Main.jsx"

import {Provider} from 'react-redux'
import {createStore} from 'redux'
import reducers from './components/main/reducer'

//below code if used for Chrome plugin, REDUX_DEVTOOLS
//const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
const store = createStore(reducers);

ReactDOM.render(
 <Provider store={store}>
  <Main/>
 </Provider>, 
 document.getElementById('root')
);

說明:
接下來,在 index.jsx 中,即在最頂層的 root element 中,
我們先利用 react 提供的 createStore() 結合之前建立的 reducer 來得到交結 redux 管理的 store,
再利用了 react-redux 提供的 <Provider> 元件,
來將 store 傳給所有子元件。
需要注意的是,我們只是將 store 傳給子元件,只包含了 state 及 action 的關係。
之後再各子元件中,可依各自的需要將 store 的 state 及 action 取出來使用。
也就是不用再像沒有 redux 時,一定要用 this.props 從父元件獲取,
父元件也不用特別寫傳值的程式碼。

題外:
REDUX_DEVTOOLS 是一個 Chrome 的 Redux 外掛,安裝後,在使用 conntect() 時用以下的方式:
const store = createStore(reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
即可以在 Chrome 中對 Redux 進行 debug,例如觀察 state 的變化。
REDUX_DEVTOOLS 應用程式商店
REDUX_DEVTOOLS 官網

我們這次只有利用 redux 來管理待辦事項,
沒有委託 redux 來管理 User 在輸入框中即時輸入的字串,
所以 TextDisplayer.jsx 完全不需要修改,還是使用 react 本來管理的方式 。

/src/components/main/Main.jsx:
import React from "react"
import {TextDisplayer} from "./TextDisplayer.jsx"
import TextInput from "./TextInput.jsx"
import ToDoList from "./ToDoList.jsx"

class Main extends React.Component{
	constructor(props){
		super(props);
		
		this.state = {
			text : ""
		};
		
		this.handleTextChanged = this.handleTextChanged.bind(this);
	}
	
	handleTextChanged(text){
		this.setState({text : text});
	}
	
    render(){
        return (
        	
        		
        		
        		
				
        	
    	);
    }
}

export default Main


/src/components/main/TextInput.jsx :
import React from "react"

import {connect} from 'react-redux'
import * as actionCreators from './action'

class TextInput extends React.Component{
 constructor(props){
  super(props);
  
  this.onHandleTextChanged = this.onHandleTextChanged.bind(this);
  this.onHandleAddToDo = this.onHandleAddToDo.bind(this);
 }
 
 onHandleTextChanged(e){
  this.props.handleTextChanged(e.target.value);
 }

 onHandleAddToDo(e){
  // addToDo() is set to this.props by connect(mapStateToProps, actionCreators)(TextInput)
  this.props.addToDo(new Date().getTime(), this.props.text);
  this.props.handleTextChanged("");
  e.preventDefault();
 }

 render(){
  return (
   <div>
    <form onSubmit={this.onHandleAddToDo}>
     <input type="text" value={this.props.text} onChange={this.onHandleTextChanged} />
     <input type="submit" value="addToDo"/>
    </form>
   </div>
  );
 }
}

const mapStateToProps = function(store){
 return { 
  storeState: store.toDoListReducer
 };
}
export default connect(mapStateToProps, actionCreators)(TextInput)

在 TextInput.jsx 中,我們需要取得 addToDo() 來添加待辦事項到 toDoList 裡。
我們使用了 react-redux 的 connect(mapStateToProps, actionCreators)(someElement)
來將 redux 管理的 state 、 action 並結合  store 中的 reducer 一起取出來放到 TextInput 元件的 props上。
在 TextInput 元件中,就可以使用 this.props.addToDo() 來增加待辦事項。
(為了方便,我們在 TextInput 中取出了所有的 state 及 action,但其實可以只取出部份的 state 及 action,例如 deleteToDo() 其實可以不用取出,因為沒有用到 )

接下來其他的元件: ToDoList.jsx、ToDoRow.jsx
也是用一樣的方式取出所需的 state, action,
用 this.props.someStateOrAction 來使用。
以下為程式嗎:

/src/components/main/ToDoList.jsx :
import React from "react"
import ToDoRow from "./ToDoRow.jsx"

import {connect} from 'react-redux'
import * as actionCreators from './action'

class ToDoList extends React.Component{ 
 render(){
  return (
   <ul>
    {  
     // toDoList is set to this.props.storeState by connect(mapStateToProps, actionCreators)(ToDoList)
     this.props.storeState.toDoList.map(function(toDo){
      return (
       <ToDoRow toDo={toDo} key={toDo.id}/>
      );
     })
    }
   </ul>
  );
 }
}

const mapStateToProps = function(store){
 return { 
  storeState: store.toDoListReducer
 };
}
export default connect(mapStateToProps, actionCreators)(ToDoList)

/src/components/main/ToDoRow.jsx :
import React from "react"

import {connect} from 'react-redux'
import * as actionCreators from './action'

class ToDoRow extends React.Component{
 constructor(props){
  super(props);

  this.handleDeleteToDo = this.handleDeleteToDo.bind(this);
 }

 handleDeleteToDo(){
  //deleteToDo() is set to this.props by connect(mapStateToProps, actionCreators)(ToDoRow)
  this.props.deleteToDo(this.props.toDo.id);
 }

 render(){
  return (
   <li>
    {this.props.toDo.toDoText}
    <button onClick={this.handleDeleteToDo}>Delete</button>
   </li>
  );
 }
}

const mapStateToProps = function(store){
 return { 
  storeState: store.toDoListReducer
 };
}
export default connect(mapStateToProps, actionCreators)(ToDoRow)

源碼下載