Recherche avancée

Médias (91)

Autres articles (84)

  • L’espace de configuration de MediaSPIP

    29 novembre 2010, par

    L’espace de configuration de MediaSPIP est réservé aux administrateurs. Un lien de menu "administrer" est généralement affiché en haut de la page [1].
    Il permet de configurer finement votre site.
    La navigation de cet espace de configuration est divisé en trois parties : la configuration générale du site qui permet notamment de modifier : les informations principales concernant le site (...)

  • HTML5 audio and video support

    13 avril 2011, par

    MediaSPIP uses HTML5 video and audio tags to play multimedia files, taking advantage of the latest W3C innovations supported by modern browsers.
    The MediaSPIP player used has been created specifically for MediaSPIP and can be easily adapted to fit in with a specific theme.
    For older browsers the Flowplayer flash fallback is used.
    MediaSPIP allows for media playback on major mobile platforms with the above (...)

  • Support audio et vidéo HTML5

    10 avril 2011

    MediaSPIP utilise les balises HTML5 video et audio pour la lecture de documents multimedia en profitant des dernières innovations du W3C supportées par les navigateurs modernes.
    Pour les navigateurs plus anciens, le lecteur flash Flowplayer est utilisé.
    Le lecteur HTML5 utilisé a été spécifiquement créé pour MediaSPIP : il est complètement modifiable graphiquement pour correspondre à un thème choisi.
    Ces technologies permettent de distribuer vidéo et son à la fois sur des ordinateurs conventionnels (...)

Sur d’autres sites (10260)

  • Recording audio with MediaRecorder on iPhone with Safari and Chrome only 1 second long ? Mimetype and FFMPEG problem ?

    9 mai 2023, par Avatar

    I am using MediaRecorder to record the Microphone audio on a website.

    


    Javascript :

    


    var blob;
var blob_url;
var stream;
var recorder;
var chunks;

var media = {
    tag: 'audio',
    type: 'audio/ogg',
    ext: '.ogg',
    gUM: {audio: true}
};

navigator.mediaDevices.getUserMedia(media.gUM).then(_stream => 
{
    stream = _stream;

    recorder = new MediaRecorder(stream);

    recorder.ondataavailable = e => 
    {
        // push data to chunks
        chunks.push(e.data);

        // recording has been stopped
        if(recorder.state == 'inactive') 
        {
            // audio data available
            blob = new Blob(chunks, {type: media.type });
            blob_url = URL.createObjectURL(blob);
            
            // send data to server
            uploadfile_audio();
        }
    };

    if(typeof(recorder)=='undefined')
    {
        alert('No microphone access');
        return;
    }

    chunks = [];
    recorder.start();
}


// when stop button is clicked
recorder.stop();
stream.getTracks().forEach( track => track.stop() );


    


    The audio stream (ogg format) is sent to the server.

    


    Since iPad/iPhone do not play ogg files, the recording file is converted to "mp3" using FFMPEG.

    


    This file is stored on the server.

    

    


    This works on Windows and MAC (Chrome and Safari), also on iPad (Safari) but not properly on iPhone (Chrome/Safari). Version : iPhone iOS 15.1.

    


    On iPhone the recording file is only 0:01 min in length. Size is always 17277 Bytes.

    


    What could be the issue ? (I cannot debug because I don't have a Mac.)

    


    Does the stream get interrupted ? Is the recording stopped after 1 second ?

    


    Update 1 :

    


    I have checked the incoming filesize of the browser-recorded file serverside. It seems to be coming in properly, because there are different sizes such as 184 kB.

    


    My guess is now that FFMPEG cannot handle the incoming file correctly. Which might have the wrong mimetype set in Javascript with type: 'audio/ogg',. Is another format needed ?

    


    The conversion code serverside :

    


    PHP

    


    $mp3file = shell_exec("ffmpeg -i ".$file_locationtmp." -vn -ar 44100 -ac 2 -b:a 128k ".$file_locationtmp.".mp3");


    


    I would need to find out the audio recording format used by iPhone but I couldn't.

    


    I tried to find the supporting mimetypes using https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported - however, it shows that NO mimetypes are supported on iPhone (neither in Chrome nor Safari).

    


    Update 2 :

    


    I used ffprobe to get the recording file information. It says Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 2234 kb/s (default)

    


    Update 3 :

    


    It seems to be a problem with FFMPEG. See my new question How to convert AAC/MP4A to MP3 using FFMPEG in full length ? Audio file gets cut off after 1 second

    


  • Converting a voice recording into an mp3

    21 juillet 2023, par Raphael M

    For a vue.js messaging project, I'm using the wavesurfer.js library to record voice messages. However Google chrome gives me an audio/webm blob and Safari gives me an audio/mp4 blob.

    


    I'm trying to find a solution to transcode the blob into audio/mp3. I've tried several methods, including ffmpeg. However, ffmpeg gives me an error when compiling "npm run dev" : "Can't resolve '/node_modules/@ffmpeg/core/dist/ffmpeg-core.js'".

    


    "@ffmpeg/core": "^0.11.0",
"@ffmpeg/ffmpeg": "^0.11.6"


    


    I tried to downgrade ffmpeg

    


    "@ffmpeg/core": "^0.9.0",
"@ffmpeg/ffmpeg": "^0.9.8"


    


    I no longer get the error message when compiling, but when I want to convert my audio stream, the console displays a problem with SharedBuffer : "Uncaught (in promise) ReferenceError : SharedArrayBuffer is not defined".

    


    Here's my complete code below.
Is there a reliable way of transcoding the audio stream into mp3 ?

    


    Can you give me an example ?

    


    Thanks

    


    <template>&#xA;  <div class="left-panel">&#xA;    <header class="radial-blue">&#xA;      <div class="container">&#xA;        <h1 class="mb-30">Posez votre premi&#xE8;re question &#xE0; nos th&#xE9;rapeutes</h1>&#xA;        <p><b>Attention</b>, vous disposez seulement de 2 messages. Veillez &#xE0; les utiliser de mani&#xE8;re judicieuse !</p>&#xA;        <div class="available-messages">&#xA;          <div class="item disabled">&#xA;            <span>Message 1</span>&#xA;          </div>&#xA;          <div class="item">&#xA;            <span>Message 2</span>&#xA;          </div>&#xA;        </div>&#xA;      </div>&#xA;    </header>&#xA;  </div>&#xA;  <div class="right-panel">&#xA;    <div class="messagerie bg-light">&#xA;      <messaging ref="messagingComponent"></messaging>&#xA;      <footer>&#xA;        <button type="button"><img src="http://stackoverflow.com/assets/backoffice/images/record-start.svg" style='max-width: 300px; max-height: 300px' /></button>&#xA;        <div class="loading-animation">&#xA;          <img src="http://stackoverflow.com/assets/backoffice/images/record-loading.svg" style='max-width: 300px; max-height: 300px' />&#xA;        </div>&#xA;        <button type="button"><img src="http://stackoverflow.com/assets/backoffice/images/record-stop.svg" style='max-width: 300px; max-height: 300px' /></button>&#xA;        <div class="textarea gradient text-dark">&#xA;          <textarea placeholder="Posez votre question"></textarea>&#xA;        </div>&#xA;        <div class="loading-text">Chargement de votre microphone en cours...</div>&#xA;        <div class="loading-text">Envoi de votre message en cours...</div>&#xA;        <div ref="visualizer"></div>&#xA;        <button type="button"><img src="http://stackoverflow.com/assets/backoffice/images/send.svg" style='max-width: 300px; max-height: 300px' /></button>&#xA;        <div>&#xA;          {{ formatTimer() }}&#xA;        </div>&#xA;      </footer>&#xA;    </div>&#xA;  </div>&#xA;</template>&#xA;&#xA;<code class="echappe-js">&lt;script&gt;&amp;#xA;import Messaging from &quot;./Messaging.vue&quot;;&amp;#xA;import { createFFmpeg, fetchFile } from &amp;#x27;@ffmpeg/ffmpeg&amp;#x27;;&amp;#xA;&amp;#xA;export default {&amp;#xA;  data() {&amp;#xA;    return {&amp;#xA;      isMicrophoneLoading: false,&amp;#xA;      isSubmitLoading: false,&amp;#xA;      isMobile: false,&amp;#xA;      isMessagerie: false,&amp;#xA;      isRecording: false,&amp;#xA;      audioUrl: &amp;#x27;&amp;#x27;,&amp;#xA;      messageText: &amp;#x27;&amp;#x27;,&amp;#xA;      message:null,&amp;#xA;      wavesurfer: null,&amp;#xA;      access:(this.isMobile?&amp;#x27;denied&amp;#x27;:&amp;#x27;granted&amp;#x27;),&amp;#xA;      maxMinutes: 5,&amp;#xA;      orangeTimer: 3,&amp;#xA;      redTimer: 4,&amp;#xA;      timer: 0,&amp;#xA;      timerInterval: null,&amp;#xA;      ffmpeg: null,&amp;#xA;    };&amp;#xA;  },&amp;#xA;  components: {&amp;#xA;    Messaging,&amp;#xA;  },&amp;#xA;  mounted() {&amp;#xA;    this.checkScreenSize();&amp;#xA;    window.addEventListener(&amp;#x27;resize&amp;#x27;, this.checkScreenSize);&amp;#xA;&amp;#xA;    if(!this.isMobile)&amp;#xA;    {&amp;#xA;      this.$moment.locale(&amp;#x27;fr&amp;#x27;);&amp;#xA;      window.addEventListener(&amp;#x27;beforeunload&amp;#x27;, (event) =&gt; {&amp;#xA;        if (this.isMessagerie) {&amp;#xA;          event.preventDefault();&amp;#xA;          event.returnValue = &amp;#x27;&amp;#x27;;&amp;#xA;        }&amp;#xA;      });&amp;#xA;&amp;#xA;      this.initializeWaveSurfer();&amp;#xA;    }&amp;#xA;  },&amp;#xA;  beforeUnmount() {&amp;#xA;    window.removeEventListener(&amp;#x27;resize&amp;#x27;, this.checkScreenSize);&amp;#xA;  },&amp;#xA;  methods: {&amp;#xA;    checkScreenSize() {&amp;#xA;      this.isMobile = window.innerWidth &lt; 1200;&amp;#xA;&amp;#xA;      const windowHeight = window.innerHeight;&amp;#xA;      const navbarHeight = this.$navbarHeight;&amp;#xA;      let padding = parseInt(navbarHeight &amp;#x2B;181);&amp;#xA;&amp;#xA;      const messageListHeight = windowHeight - padding;&amp;#xA;      this.$refs.messagingComponent.$refs.messageList.style.height = messageListHeight &amp;#x2B; &amp;#x27;px&amp;#x27;;&amp;#xA;    },&amp;#xA;    showMessagerie() {&amp;#xA;      this.isMessagerie = true;&amp;#xA;      this.$refs.messagingComponent.scrollToBottom();&amp;#xA;    },&amp;#xA;    checkMicrophoneAccess() {&amp;#xA;      if (navigator.mediaDevices &amp;amp;&amp;amp; navigator.mediaDevices.getUserMedia) {&amp;#xA;&amp;#xA;        return navigator.mediaDevices.getUserMedia({audio: true})&amp;#xA;            .then(function (stream) {&amp;#xA;              stream.getTracks().forEach(function (track) {&amp;#xA;                track.stop();&amp;#xA;              });&amp;#xA;              return true;&amp;#xA;            })&amp;#xA;            .catch(function (error) {&amp;#xA;              console.error(&amp;#x27;Erreur lors de la demande d\&amp;#x27;acc&amp;#xE8;s au microphone:&amp;#x27;, error);&amp;#xA;              return false;&amp;#xA;            });&amp;#xA;      } else {&amp;#xA;        console.error(&amp;#x27;getUserMedia n\&amp;#x27;est pas support&amp;#xE9; par votre navigateur.&amp;#x27;);&amp;#xA;        return false;&amp;#xA;      }&amp;#xA;    },&amp;#xA;    initializeWaveSurfer() {&amp;#xA;      this.wavesurfer = this.$wavesurfer.create({&amp;#xA;        container: &amp;#x27;#visualizer&amp;#x27;,&amp;#xA;        barWidth: 3,&amp;#xA;        barHeight: 1.5,&amp;#xA;        height: 46,&amp;#xA;        responsive: true,&amp;#xA;        waveColor: &amp;#x27;rgba(108,115,202,0.3)&amp;#x27;,&amp;#xA;        progressColor: &amp;#x27;rgba(108,115,202,1)&amp;#x27;,&amp;#xA;        cursorColor: &amp;#x27;transparent&amp;#x27;&amp;#xA;      });&amp;#xA;&amp;#xA;      this.record = this.wavesurfer.registerPlugin(this.$recordPlugin.create());&amp;#xA;    },&amp;#xA;    startRecording() {&amp;#xA;      const _this = this;&amp;#xA;      this.isMicrophoneLoading = true;&amp;#xA;&amp;#xA;      setTimeout(() =&gt;&amp;#xA;      {&amp;#xA;        _this.checkMicrophoneAccess().then(function (accessible)&amp;#xA;        {&amp;#xA;          if (accessible) {&amp;#xA;            _this.record.startRecording();&amp;#xA;&amp;#xA;            _this.record.once(&amp;#x27;startRecording&amp;#x27;, () =&gt; {&amp;#xA;              _this.isMicrophoneLoading = false;&amp;#xA;              _this.isRecording = true;&amp;#xA;              _this.updateChildMessage( &amp;#x27;server&amp;#x27;, &amp;#x27;Allez-y ! Vous pouvez enregistrer votre message audio maintenant. La dur&amp;#xE9;e maximale autoris&amp;#xE9;e pour votre enregistrement est de 5 minutes.&amp;#x27;, &amp;#x27;text&amp;#x27;, &amp;#x27;&amp;#x27;, &amp;#x27;Message automatique&amp;#x27;);&amp;#xA;              _this.startTimer();&amp;#xA;            });&amp;#xA;          } else {&amp;#xA;            _this.isRecording = false;&amp;#xA;            _this.isMicrophoneLoading = false;&amp;#xA;            _this.$swal.fire({&amp;#xA;              title: &amp;#x27;Microphone non d&amp;#xE9;tect&amp;#xE9;&amp;#x27;,&amp;#xA;              html: &amp;#x27;&lt;p&gt;Le microphone de votre appareil est inaccessible ou l\&amp;#x27;acc&amp;#xE8;s a &amp;#xE9;t&amp;#xE9; refus&amp;#xE9;.&lt;/p&gt;&lt;p&gt;Merci de v&amp;#xE9;rifier les param&amp;#xE8;tres de votre navigateur afin de v&amp;#xE9;rifier les autorisations de votre microphone.&lt;/p&gt;&amp;#x27;,&amp;#xA;              footer: &amp;#x27;&lt;a href='http://stackoverflow.com/contact'&gt;Vous avez besoin d\&amp;#x27;aide ?&lt;/a&gt;&amp;#x27;,&amp;#xA;            });&amp;#xA;          }&amp;#xA;        });&amp;#xA;      }, 100);&amp;#xA;    },&amp;#xA;    stopRecording() {&amp;#xA;      this.stopTimer();&amp;#xA;      this.isRecording = false;&amp;#xA;      this.isSubmitLoading = true;&amp;#xA;      this.record.stopRecording();&amp;#xA;&amp;#xA;      this.record.once(&amp;#x27;stopRecording&amp;#x27;, () =&gt; {&amp;#xA;        const blobUrl = this.record.getRecordedUrl();&amp;#xA;        fetch(blobUrl).then(response =&gt; response.blob()).then(blob =&gt; {&amp;#xA;          this.uploadAudio(blob);&amp;#xA;        });&amp;#xA;      });&amp;#xA;    },&amp;#xA;    startTimer() {&amp;#xA;      this.timerInterval = setInterval(() =&gt; {&amp;#xA;        this.timer&amp;#x2B;&amp;#x2B;;&amp;#xA;        if (this.timer === this.maxMinutes * 60) {&amp;#xA;          this.stopRecording();&amp;#xA;        }&amp;#xA;      }, 1000);&amp;#xA;    },&amp;#xA;    stopTimer() {&amp;#xA;      clearInterval(this.timerInterval);&amp;#xA;      this.timer = 0;&amp;#xA;    },&amp;#xA;    formatTimer() {&amp;#xA;      const minutes = Math.floor(this.timer / 60);&amp;#xA;      const seconds = this.timer % 60;&amp;#xA;      const formattedMinutes = minutes &lt; 10 ? `0${minutes}` : minutes;&amp;#xA;      const formattedSeconds = seconds &lt; 10 ? `0${seconds}` : seconds;&amp;#xA;      return `${formattedMinutes}:${formattedSeconds}`;&amp;#xA;    },&amp;#xA;    async uploadAudio(blob)&amp;#xA;    {&amp;#xA;      const format = blob.type === &amp;#x27;audio/webm&amp;#x27; ? &amp;#x27;webm&amp;#x27; : &amp;#x27;mp4&amp;#x27;;&amp;#xA;&amp;#xA;      // Convert the blob to MP3&amp;#xA;      const mp3Blob = await this.convertToMp3(blob, format);&amp;#xA;&amp;#xA;      const s3 = new this.$AWS.S3({&amp;#xA;        accessKeyId: &amp;#x27;xxx&amp;#x27;,&amp;#xA;        secretAccessKey: &amp;#x27;xxx&amp;#x27;,&amp;#xA;        region: &amp;#x27;eu-west-1&amp;#x27;&amp;#xA;      });&amp;#xA;&amp;#xA;      var currentDate = new Date();&amp;#xA;      var filename = currentDate.getDate().toString() &amp;#x2B; &amp;#x27;-&amp;#x27; &amp;#x2B; currentDate.getMonth().toString() &amp;#x2B; &amp;#x27;-&amp;#x27; &amp;#x2B; currentDate.getFullYear().toString() &amp;#x2B; &amp;#x27;--&amp;#x27; &amp;#x2B; currentDate.getHours().toString() &amp;#x2B; &amp;#x27;-&amp;#x27; &amp;#x2B; currentDate.getMinutes().toString() &amp;#x2B; &amp;#x27;.mp4&amp;#x27;;&amp;#xA;&amp;#xA;      const params = {&amp;#xA;        Bucket: &amp;#x27;xxx/audio&amp;#x27;,&amp;#xA;        Key: filename,&amp;#xA;        Body: mp3Blob,&amp;#xA;        ACL: &amp;#x27;public-read&amp;#x27;,&amp;#xA;        ContentType: &amp;#x27;audio/mp3&amp;#x27;&amp;#xA;      }&amp;#xA;&amp;#xA;      s3.upload(params, (err, data) =&gt; {&amp;#xA;        if (err) {&amp;#xA;          console.error(&amp;#x27;Error uploading audio:&amp;#x27;, err)&amp;#xA;        } else {&amp;#xA;          const currentDate = this.$moment();&amp;#xA;          const timestamp = currentDate.format(&amp;#x27;dddd DD MMMM YYYY HH:mm&amp;#x27;);&amp;#xA;&amp;#xA;          this.updateChildMessage( &amp;#x27;client&amp;#x27;, &amp;#x27;&amp;#x27;, &amp;#x27;audio&amp;#x27;, mp3Blob, timestamp);&amp;#xA;          this.isSubmitLoading = false;&amp;#xA;        }&amp;#xA;      });&amp;#xA;    },&amp;#xA;    async convertToMp3(blob, format) {&amp;#xA;      const ffmpeg = createFFmpeg({ log: true });&amp;#xA;      await ffmpeg.load();&amp;#xA;&amp;#xA;      const inputPath = &amp;#x27;input.&amp;#x27; &amp;#x2B; format;&amp;#xA;      const outputPath = &amp;#x27;output.mp3&amp;#x27;;&amp;#xA;&amp;#xA;      ffmpeg.FS(&amp;#x27;writeFile&amp;#x27;, inputPath, await fetchFile(blob));&amp;#xA;&amp;#xA;      await ffmpeg.run(&amp;#x27;-i&amp;#x27;, inputPath, &amp;#x27;-acodec&amp;#x27;, &amp;#x27;libmp3lame&amp;#x27;, outputPath);&amp;#xA;&amp;#xA;      const mp3Data = ffmpeg.FS(&amp;#x27;readFile&amp;#x27;, outputPath);&amp;#xA;      const mp3Blob = new Blob([mp3Data.buffer], { type: &amp;#x27;audio/mp3&amp;#x27; });&amp;#xA;&amp;#xA;      ffmpeg.FS(&amp;#x27;unlink&amp;#x27;, inputPath);&amp;#xA;      ffmpeg.FS(&amp;#x27;unlink&amp;#x27;, outputPath);&amp;#xA;&amp;#xA;      return mp3Blob;&amp;#xA;    },&amp;#xA;    sendMessage() {&amp;#xA;      this.isSubmitLoading = true;&amp;#xA;      if (this.messageText.trim() !== &amp;#x27;&amp;#x27;) {&amp;#xA;        const emmet = &amp;#x27;client&amp;#x27;;&amp;#xA;        const text = this.escapeHTML(this.messageText)&amp;#xA;            .replace(/\n/g, &amp;#x27;&lt;br&gt;&amp;#x27;);&amp;#xA;&amp;#xA;        const currentDate = this.$moment();&amp;#xA;        const timestamp = currentDate.format(&amp;#x27;dddd DD MMMM YYYY HH:mm&amp;#x27;);&amp;#xA;&amp;#xA;        this.$nextTick(() =&gt; {&amp;#xA;          this.messageText = &amp;#x27;&amp;#x27;;&amp;#xA;&amp;#xA;          const textarea = document.getElementById(&amp;#x27;messageTextarea&amp;#x27;);&amp;#xA;          if (textarea) {&amp;#xA;            textarea.scrollTop = 0;&amp;#xA;            textarea.scrollLeft = 0;&amp;#xA;          }&amp;#xA;        });&amp;#xA;&amp;#xA;        this.updateChildMessage(emmet, text, &amp;#x27;text&amp;#x27;, &amp;#x27;&amp;#x27;, timestamp);&amp;#xA;        this.isSubmitLoading = false;&amp;#xA;      }&amp;#xA;    },&amp;#xA;    escapeHTML(text) {&amp;#xA;      const map = {&amp;#xA;        &amp;#x27;&amp;amp;&amp;#x27;: &amp;#x27;&amp;amp;amp;&amp;#x27;,&amp;#xA;        &amp;#x27;&lt;&amp;#x27;: &amp;#x27;&amp;amp;lt;&amp;#x27;,&amp;#xA;        &amp;#x27;&gt;&amp;#x27;: &amp;#x27;&amp;amp;gt;&amp;#x27;,&amp;#xA;        &amp;#x27;&quot;&amp;#x27;: &amp;#x27;&amp;amp;quot;&amp;#x27;,&amp;#xA;        &quot;&amp;#x27;&quot;: &amp;#x27;&amp;amp;#039;&amp;#x27;,&amp;#xA;        &quot;`&quot;: &amp;#x27;&amp;amp;#x60;&amp;#x27;,&amp;#xA;        &quot;/&quot;: &amp;#x27;&amp;amp;#x2F;&amp;#x27;&amp;#xA;      };&amp;#xA;      return text.replace(/[&amp;amp;&lt;&gt;&quot;&amp;#x27;`/]/g, (match) =&gt; map[match]);&amp;#xA;    },&amp;#xA;    updateChildMessage(emmet, text, type, blob, timestamp) {&amp;#xA;      const newMessage = {&amp;#xA;        id: this.$refs.messagingComponent.lastMessageId &amp;#x2B; 1,&amp;#xA;        emmet: emmet,&amp;#xA;        text: text,&amp;#xA;        type: type,&amp;#xA;        blob: blob,&amp;#xA;        timestamp: timestamp&amp;#xA;      };&amp;#xA;&amp;#xA;      this.$refs.messagingComponent.updateMessages(newMessage);&amp;#xA;    }&amp;#xA;  },&amp;#xA;};&amp;#xA;&lt;/script&gt;&#xA;

    &#xA;

  • Permissions issue with Python and ffmpeg on a Mac

    13 avril 2020, par EventHorizon

    I am fairly new to Python ( 4 weeks), and I have been struggling with this all day.

    &#xA;&#xA;

    I am using MacOS 10.13, Python 3.7 via Anaconda Navigator 1.9.12 and Spyder 4.0.1.

    &#xA;&#xA;

    Somehow (only a noob, remember) I had 2 Anaconda environments. I don't do production code, just research, so I figured I would make life simple and just use the base environment. I deleted the other environment.

    &#xA;&#xA;

    I had previously got FFmpeg working and was able to do frame grabs, build mpeg animations, and convert them to gifs for blog posts and such. I had FFmpeg installed in the directories associated with the deleted environment, so it went away.

    &#xA;&#xA;

    No worries, I got the git URL, used Terminal to install it in /opt/anaconda3/bin. It's all there and I can run FFmpeg from the Terminal.

    &#xA;&#xA;

    My problem : When I attempt to run a module that previously worked fine, I get the following message :

    &#xA;&#xA;

    [Errno 13] Permission denied : '/opt/anaconda3/bin/ffmpeg'

    &#xA;&#xA;

    In my module I set the default location of FFmpeg : plt.rcParams['animation.ffmpeg_path'] = '/opt/anaconda3/bin/ffmpeg'

    &#xA;&#xA;

    In my module I have the following lines :

    &#xA;&#xA;

    writer = animation.FFMpegWriter(fps=frameRate, metadata=metadata)&#xA;writer.setup(fig, "animation.mp4", 100)&#xA;

    &#xA;&#xA;

    This calls matplotlib's 'animation.py', which runs the following :

    &#xA;&#xA;

    def setup(self, fig, outfile, dpi=None):&#xA;    &#x27;&#x27;&#x27;&#xA;    Perform setup for writing the movie file.&#xA;&#xA;    Parameters&#xA;    ----------&#xA;    fig : `~matplotlib.figure.Figure`&#xA;        The figure object that contains the information for frames&#xA;    outfile : str&#xA;        The filename of the resulting movie file&#xA;    dpi : int, optional&#xA;        The DPI (or resolution) for the file.  This controls the size&#xA;        in pixels of the resulting movie file. Default is fig.dpi.&#xA;    &#x27;&#x27;&#x27;&#xA;    self.outfile = outfile&#xA;    self.fig = fig&#xA;    if dpi is None:&#xA;        dpi = self.fig.dpi&#xA;    self.dpi = dpi&#xA;    self._w, self._h = self._adjust_frame_size()&#xA;&#xA;    # Run here so that grab_frame() can write the data to a pipe. This&#xA;    # eliminates the need for temp files.&#xA;    self._run()&#xA;&#xA;def _run(self):&#xA;    # Uses subprocess to call the program for assembling frames into a&#xA;    # movie file.  *args* returns the sequence of command line arguments&#xA;    # from a few configuration options.&#xA;    command = self._args()&#xA;    _log.info(&#x27;MovieWriter.run: running command: %s&#x27;, command)&#xA;    PIPE = subprocess.PIPE&#xA;    self._proc = subprocess.Popen(&#xA;        command, stdin=PIPE, stdout=PIPE, stderr=PIPE,&#xA;        creationflags=subprocess_creation_flags)&#xA;

    &#xA;&#xA;

    Everything works fine up to the last line (i.e. 'command' looks like a well-formatted FFmpeg command line, PIPE returns -1) but subprocess.Popen() bombs out with the error message above.

    &#xA;&#xA;

    I have tried changing file permissions - taking a sledgehammer approach and setting everything in /opt/anaconda3/bin/ffmpeg to 777, read, write, and execute. But that doesn't seem to make any difference. I really am clueless when it comes to Apple's OS, file permissions, etc. Any suggestions ?

    &#xA;