備忘錄_20160105(定位) 修改 回首頁

程式 2025-10-19 20:15:51 1760876151 100
一張圖片(jpg or png ......),一個音檔(mp3 or m4a ......),組成一個影片(mp4 [h.264+aac])

一張圖片(jpg or png ......),一個音檔(mp3 or m4a ......),組成一個影片(mp4 [h.264+aac])

前往試試


index.php
<!doctype html>
<html lang="zh-Hant-TW">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <title>圖片+聲音→影片</title>
    <style>
*
{
  font-size: 24pt;
}

.classTitle
{
  font-size: 36pt;
}
#divProgress
{
  white-space: pre-wrap; /* 保留空格和換行,長行自動換行 */
  word-wrap: break-word; /* 避免超長單字溢出 */
  font-family: monospace; /* 可選,看起來像 pre */
}
    </style>
  </head>
  <body>
    <div class="classTitle">圖片+聲音→影片</div>
    <br>
    <form class="classFormMaster" name="frm20251020102957" id="frm20251020102957" method="post" action="convert2mp4.php" enctype="multipart/form-data">
      <div>
        <label for="fileImage">圖片</label>
        <input type="file" name="fileImage" id="fileImage" accept="image/*" required>
      </div>
      <div>
        <label for="fileAudio">聲音</label>
        <input type="file" name="fileAudio" id="fileAudio" accept="audio/*" required>
      </div>
      <input type="hidden" name="txtUniq" id="txtUniq" value="">
      <button type="button" id="btnSubmit" onclick="checkAndSubmit_master();">送出</button>
    </form>
    <div id="divProgress"></div>
    <script>
function gebi(strId)
{
  return document.getElementById(strId);
}

function checkAndSubmit_master()
{
  gebi('btnSubmit').setAttribute("disabled",true);
  gebi('txtUniq').value="progress-"+Math.random().toString(36).substr(2, 16)+".txt";
  
  while(true)
  {
    if(gebi('fileImage').files.length<1)
    {
      alert("抱歉,您還沒有選擇圖片喔!請修正後再上傳,謝謝!");
      break;
    }
    
    if(gebi('fileAudio').files.length<1)
    {
      alert("抱歉,您還沒有選擇聲音喔!請修正後再上傳,謝謝!");
      break;
    }
    
    gebi('frm20251020102957').submit();
    gebi("divProgress").innerHTML="上傳中,請耐心等待,謝謝!";
    
    showProgress();
    
    return;
  }

  gebi('btnSubmit').removeAttribute("disabled");
}

async function showProgress()
{
  let strFilename="tmp/"+gebi('txtUniq').value;
  let oRes=await fetch(strFilename+"?rand=" + Math.random());
  let strTxt = await oRes.text();
  
  console.log((new Date()).toLocaleString());
  
  if(strTxt.indexOf("progress=end")!=-1)
  {
    gebi('divProgress').innerHTML="";
    gebi('btnSubmit').removeAttribute("disabled");
  }
  else if(strTxt.indexOf("404 Not Found")!=-1)
  {
    gebi('divProgress').innerHTML="上傳中,請稍候~~~"+(new Date()).toLocaleString();
    window.setTimeout(showProgress, 1000);
  }
  else
  {
    var iPtr=strTxt.lastIndexOf('frame=');
    if(iPtr!=-1)
    {
      strTxt=strTxt.substring(iPtr);
    }
    
    gebi('divProgress').textContent=strTxt;
    window.scrollTo(0, document.body.scrollHeight);
    window.setTimeout(showProgress, 2000);
  }
}
    </script>
  </body>
</html>

convert2mp4.php
<?php

  //$strTmpDir=sys_get_temp_dir();
  $strTmpDir=rtrim(dirname(realpath(__FILE__)), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . "tmp";
  
  $strExtImage=pathinfo($_FILES['fileImage']['name'], PATHINFO_EXTENSION);
  $strExtAudio=pathinfo($_FILES['fileAudio']['name'], PATHINFO_EXTENSION);
  
  $strImage=$strTmpDir . "/" . uniqid() . "." . $strExtImage;
  $strAudio=$strTmpDir . "/" . uniqid() . "." . $strExtAudio;

  $strProgressFilename=$strTmpDir . "/" . $_REQUEST['txtUniq'];
  
  $booImage=move_uploaded_file($_FILES['fileImage']['tmp_name'], $strImage);
  $booAudio=move_uploaded_file($_FILES['fileAudio']['tmp_name'], $strAudio);
  
  $strVideo=$strTmpDir . "/" . uniqid() . ".mp4";
  
  // 檢查檔案
  if (!file_exists($strImage) || !file_exists($strAudio))
  {
    die("缺少檔案,終止執行!(上傳檔案上限為100MB)");
  }
  
  // ffmpeg 指令
  $strCmd = sprintf(
    'ffmpeg -loop 1 -i %s -i %s -vf "scale=\'trunc(min(iw\\,1280)/2)*2:trunc(min(ih\\,720)/2)*2\',format=yuv420p" -c:v libx264 -tune stillimage -c:a aac -b:a 192k -pix_fmt yuv420p -shortest -progress %s %s 2>&1',
    escapeshellarg($strImage),
    escapeshellarg($strAudio),
    escapeshellarg($strProgressFilename),
    escapeshellarg($strVideo)
  );
  
  file_put_contents($strTmpDir . '/log.log', $strCmd . "\r\n" , FILE_APPEND);
  
  // 執行 ffmpeg
  exec($strCmd, $strLog, $strRet);

  // 清除原始檔
  @unlink($strImage);
  @unlink($strAudio);

  // 檢查結果
  if(file_exists($strVideo)==false)
  {
    header("Content-Type: text/plain; charset=utf-8");
    echo "影片產生失敗。\r\n";
    echo htmlspecialchars(implode("\r\n", $strLog));
    exit;
  }
  
  // 回傳下載
  header('Content-Description: File Transfer');
  header('Content-Type: video/mp4');
  header('Content-Disposition: attachment; filename="result.mp4"');
  header('Content-Length: ' . filesize($strVideo));
  readfile($strVideo);
  
  // 刪除暫存影片,與進度
  @unlink($strVideo);
  @unlink($strProgressFilename);
  
?>