diff --git a/ffmpeg-patch/0001-libavformat-ffmetadec-use-dynamic-allocation-for-lin.patch b/ffmpeg-patch/0001-libavformat-ffmetadec-use-dynamic-allocation-for-lin.patch new file mode 100644 index 0000000..27153c7 --- /dev/null +++ b/ffmpeg-patch/0001-libavformat-ffmetadec-use-dynamic-allocation-for-lin.patch @@ -0,0 +1,69 @@ +From 1b016179fc18059382f8e047552e7031855764a0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fran=C3=A7ois=20Revol?= +Date: Wed, 7 Nov 2018 14:31:04 +0100 +Subject: [PATCH] libavformat/ffmetadec: use dynamic allocation for line buffer + +When adding thumbnails to OGG files, the line can easily go up to 100kB. + +We thus try to allocate the file size or SIZE_MAX to avoid truncation. +--- + libavformat/ffmetadec.c | 21 +++++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/libavformat/ffmetadec.c b/libavformat/ffmetadec.c +index 3290b3b7bc..ccbff51c03 100644 +--- a/libavformat/ffmetadec.c ++++ b/libavformat/ffmetadec.c +@@ -128,16 +128,26 @@ static int read_tag(const uint8_t *line, AVDictionary **m) + static int read_header(AVFormatContext *s) + { + AVDictionary **m = &s->metadata; +- uint8_t line[1024]; ++ int64_t line_size = avio_size(s->pb); ++ uint8_t *line; ++ ++ if (line_size < 1 || line_size > SIZE_MAX) ++ line_size = SIZE_MAX; ++ ++ line = av_malloc(line_size); ++ if (!line) ++ return AVERROR(ENOMEM); + + while(!avio_feof(s->pb)) { +- get_line(s->pb, line, sizeof(line)); ++ get_line(s->pb, line, line_size); + + if (!memcmp(line, ID_STREAM, strlen(ID_STREAM))) { + AVStream *st = avformat_new_stream(s, NULL); + +- if (!st) ++ if (!st) { ++ av_free(line); + return AVERROR(ENOMEM); ++ } + + st->codecpar->codec_type = AVMEDIA_TYPE_DATA; + st->codecpar->codec_id = AV_CODEC_ID_FFMETADATA; +@@ -146,8 +156,10 @@ static int read_header(AVFormatContext *s) + } else if (!memcmp(line, ID_CHAPTER, strlen(ID_CHAPTER))) { + AVChapter *ch = read_chapter(s); + +- if (!ch) ++ if (!ch) { ++ av_free(line); + return AVERROR(ENOMEM); ++ } + + m = &ch->metadata; + } else +@@ -160,6 +172,7 @@ static int read_header(AVFormatContext *s) + s->chapters[s->nb_chapters - 1]->time_base, + AV_TIME_BASE_Q); + ++ av_free(line); + return 0; + } + +-- +2.19.1 + diff --git a/ffmpeg-patch/README b/ffmpeg-patch/README new file mode 100644 index 0000000..10da1dd --- /dev/null +++ b/ffmpeg-patch/README @@ -0,0 +1,25 @@ +Pour le traitement des podcasts, il est utile d'avoir un ffmpeg patché (du moins tant que le patch n'a pas été intégré dans la version officielle). C'est mmu_man (François Revol qui a fait le patch). Il permet d'ajouter par un script l'image de cover à nos podcasts sans que l'image soit floue. Sinon, il faut utiliser un logiciel avec un interface graphique, par exemple Easytag. + +J'ai mis en ligne le tarball du FFmpeg que j'avais patché et compilé en novembre 2017 : + +media.april.org/audio/radio-cause-commune/libre-a-vous/FFmpeg.tgz + +Car visiblement le patch ne s'applique plus : + + sudo apt install yasm + + git clone https://github.com/FFmpeg/FFmpeg.git + + cd FFmpeg + + cd libavformat + + git checkout d96ae9d5ea1f47a437fc0663b0cc26ff5d4d5d31 + #pour être dans la version patchable + + appliquer le patch: + patch < path/to/0001-libavformat-ffmetadec-use-dynamic-allocation-for-lin.patch + + ./configure + + make diff --git a/podcasts/README b/podcasts/README new file mode 100644 index 0000000..e218fdc --- /dev/null +++ b/podcasts/README @@ -0,0 +1,49 @@ +Comment découper l'enregistrement en podcasts : + +* Se positionner dans le dossier contenant l'enregistrement, les images, etc : +root@raspberrypi:~/lav-outils# ls -l ~/libreavous/ +total 357004 +drwxr-xr-x 19 root root 4096 janv. 7 11:47 FFmpeg +drwxr-xr-x 5 root root 4096 janv. 16 11:53 lav-outils +-rw-r--r-- 1 root root 99183 janv. 7 15:55 image-pour-etiqueter-podcast.jpg +-rw-r--r-- 1 root root 90081558 janv. 15 17:54 libre-a-vous-20190108.ogg + +* Regarder le programme de l'émission et chercher les timestamp de début et de fin des différents podcast + +* Décider d'un nom court, qui sera utilisé pour le fichier de podcast + +* Écrire la conf json en s'inspirant de example.json (mettre à jour la date), par exemple : + +{ + "short_date" : "20190108", + "long_date" : "8 janvier 2019", + "ffmpeg_bin" : "./FFmpeg/ffmpeg", + "chapters" : [ + { + "start_timestamp" : "0:03:09", + "end_timestamp" : "0:18:45", + "short_chapter_name" : "chronique-transcriptions" + }, + { + "start_timestamp" : "00:18:45", + "end_timestamp" : "01:11:01", + "short_chapter_name" : "dinsic-etalab" + }, + { + "start_timestamp" : "01:11:01", + "end_timestamp" : "01:23:38", + "short_chapter_name" : "logiciel-caisse" + }, + { + "start_timestamp" : "01:23:38", + "end_timestamp" : "01:29:58", + "short_chapter_name" : "annonces" + } + ] +} + +* Exécuter le script : +root@raspberrypi:~/libreavous# lav-outils/scripts/make-all-podcasts.pl --config lav-outils/config/lav-20190115.json + +* Les podcasts se retrouvent dans le dossier courant + diff --git a/podcasts/config/example.json b/podcasts/config/example.json new file mode 100644 index 0000000..a9257c1 --- /dev/null +++ b/podcasts/config/example.json @@ -0,0 +1,12 @@ +{ + "short_date" : "AAAAMMDD", + "long_date" : "DD MMMM AAAA", + "ffmpeg_bin" : "./FFmpeg/ffmpeg", + "chapters" : [ + { + "start_timestamp" : "HH:MM:SS", + "end_timestamp" : "HH:MM:SS", + "short_chapter_name" : "" + } + ] +} diff --git a/podcasts/config/lav-20190108.json b/podcasts/config/lav-20190108.json new file mode 100644 index 0000000..b421a3b --- /dev/null +++ b/podcasts/config/lav-20190108.json @@ -0,0 +1,27 @@ +{ + "short_date" : "20190108", + "long_date" : "8 janvier 2019", + "ffmpeg_bin" : "./FFmpeg/ffmpeg", + "chapters" : [ + { + "start_timestamp" : "0:03:09", + "end_timestamp" : "0:18:45", + "short_chapter_name" : "chronique-transcriptions" + }, + { + "start_timestamp" : "00:18:45", + "end_timestamp" : "01:11:01", + "short_chapter_name" : "dinsic-etalab" + }, + { + "start_timestamp" : "01:11:01", + "end_timestamp" : "01:23:38", + "short_chapter_name" : "logiciel-caisse" + }, + { + "start_timestamp" : "01:23:38", + "end_timestamp" : "01:29:58", + "short_chapter_name" : "annonces" + } + ] +} diff --git a/podcasts/config/lav-20190115.json b/podcasts/config/lav-20190115.json new file mode 100644 index 0000000..1f8d79b --- /dev/null +++ b/podcasts/config/lav-20190115.json @@ -0,0 +1,12 @@ +{ + "short_date" : "20190115", + "long_date" : "15 janvier 2019", + "ffmpeg_bin" : "./FFmpeg/ffmpeg", + "chapters" : [ + { + "start_timestamp" : "00:16:27", + "end_timestamp" : "01:29:40", + "short_chapter_name" : "CADA" + } + ] +} diff --git a/podcasts/images/image-pour-etiqueter-podcast.jpg b/podcasts/images/image-pour-etiqueter-podcast.jpg new file mode 100644 index 0000000..c2cbc8c Binary files /dev/null and b/podcasts/images/image-pour-etiqueter-podcast.jpg differ diff --git a/podcasts/scripts/make-all-podcasts.pl b/podcasts/scripts/make-all-podcasts.pl new file mode 100755 index 0000000..b525770 --- /dev/null +++ b/podcasts/scripts/make-all-podcasts.pl @@ -0,0 +1,148 @@ +#!/usr/bin/perl + +use strict; +use Getopt::Long; +use JSON; +use Data::Dumper; + +my ($help,$config,$verbose,$dryrun); +my $meta_data_script = "lav-outils/podcasts/scripts/make-metadata-image-podcast.sh"; + +my $verbose; +GetOptions ("help" => \$help, + "config=s" => \$config, + "verbose" => \$verbose, + "dryrun" => \$dryrun); + +if($help) { + usage(); +} elsif( not $config ) { + print " /!\\ Missing config arg\n\n"; + usage(); +} + +sub usage { + print < + }; + + my $json = JSON->new; + my $data = $json->decode($json_text); + + return $data; +} + +sub process { + my ($config,$verbose,$dryrun)=@_; + my $data = read_config($config); + + my $short_date = $data->{short_date}; + my $long_date = $data->{long_date}; + my $source_name = "libre-a-vous-$short_date"; + my $title = "Libre à vous ! du $long_date sur Cause Commune"; + my $ffmpeg_bin = $data->{ffmpeg_bin}; + for my $chapter (values @{$data->{chapters}}) { + my $start = $chapter->{start_timestamp}; + my $end = $chapter->{end_timestamp}; + my $short_chapter_name = $chapter->{short_chapter_name}; + + # cutting chapter + my $command = "$ffmpeg_bin -y -i $source_name.ogg -vn -acodec copy -ss \"$start\" -to \"$end\" $source_name-$short_chapter_name.ogg"; + if($dryrun) { + print "$command\n"; + } else { + my @ret = `$command`; + if($?) { + print "Error while cutting $short_chapter_name\n"; + if($verbose) { + print Dumper @ret; + print Dumper $data; + return 0; + } + } + } + + # putting metadata + my $url = "https://media.april.org/audio/radio-cause-commune/libre-a-vous/emissions/$short_date/$source_name-$short_chapter_name.ogg"; + my $command = "$meta_data_script -s \"$source_name-$short_chapter_name.ogg\" -d \"output.ogg\" -u \"$url\" -t \"$title\" -p \"$ffmpeg_bin\""; + if($dryrun) { + print "$command\n"; + } else { + my @ret = `$command`; + if($?) { + print "Error while setting metadata in $short_chapter_name\n"; + if($verbose) { + print Dumper @ret; + print Dumper $data; + return 0; + } + } + } + + my $command = "mv output.ogg $source_name-$short_chapter_name.ogg"; + if($dryrun) { + print "$command\n"; + } else { + my @ret = `$command`; + if($?) { + print "Error while renaming $short_chapter_name\n"; + if($verbose) { + print Dumper @ret; + print Dumper $data; + return 0; + } + } + } + } + + # putting metadata in main podcast + my $url = "https://media.april.org/audio/radio-cause-commune/libre-a-vous/emissions/$short_date/$source_name.ogg"; + my $command = "$meta_data_script -s \"$source_name.ogg\" -d \"output.ogg\" -u \"$url\" -t \"$title\" -p \"$ffmpeg_bin\""; + if($dryrun) { + print "$command\n"; + } else { + my @ret = `$command`; + if($?) { + print "Error while setting metadata in $source_name\n"; + if($verbose) { + print Dumper @ret; + print Dumper $data; + return 0; + } + } + } + my $command = "mv output.ogg $source_name.ogg"; + if($dryrun) { + print "$command\n"; + } else { + my @ret = `$command`; + if($?) { + print "Error while renaming $source_name\n"; + if($verbose) { + print Dumper @ret; + print Dumper $data; + return 0; + } + } + } +} + +process($config,$verbose,$dryrun); diff --git a/podcasts/scripts/make-metadata-image-podcast.sh b/podcasts/scripts/make-metadata-image-podcast.sh new file mode 100755 index 0000000..59bd840 --- /dev/null +++ b/podcasts/scripts/make-metadata-image-podcast.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +usage() { echo "$0 -s source_file -d destination_file -u http://... -t \"title\" -p path/to/ffmpeg/binary"; exit 0; } +[ $# -eq 0 ] && usage +while getopts "s:d:u:t:p:h" arg; do + case $arg in + s) + source=${OPTARG} + ;; + d) + destination=${OPTARG} + ;; + u) + url=${OPTARG} + ;; + t) + title=${OPTARG} + ;; + p) + FFmpegBin=${OPTARG} + ;; + h | *) # Display help. + usage + exit 0 + ;; + esac +done + +if [ ! -f $source ]; then + echo "File $source does not exist" + usage + exit 1 +fi +if [ ! -f "$FFmpegBin" ]; then + echo "$FFmpegBin is not executable" + usage + exit 1 +fi + +image="lav-outils/images/image-pour-etiqueter-podcast.jpg" +fichiertemp="$(command mktemp -t "tmp.XXXXXXXXXX.ogg")" + +(echo -en ";FFMETADATA1\nMETADATA_BLOCK_PICTURE=";(i=${image};t=3;m="image/jpeg";eval "w=`identify-im6 "$i"|awk '{g=$3;sub("x"," h=",g);print g;d=$5;gsub(/-.*/,"",d);print " d=" d}'`"; echo -en "\x00\x00\x00\x$(printf '%02x' $t)\x00\x00\x00\x$(printf '%02x' `echo -n "$m"|wc -c`)$m\x00\x00\x00\x00$(printf '%08x' $w|sed 's/../\\x&/g')$(printf '%08x' $h|sed 's/../\\x&/g')$(printf '%08x' $d|sed 's/../\\x&/g')\x00\x00\x00\xff$(printf '%08x' `stat -c '%s' "$i"`|sed 's/../\\x&/g')";cat "$i")|base64 --wrap=0) > i.meta + +${FFmpegBin} -y -i ${source} -acodec copy -map 0:0 -map_metadata -1 -metadata title="${title}" -metadata copyright="Diffusée selon les termes d’au moins une des licences suivantes : licence Art libre version 1.3 ou ultérieure http://artlibre.org/licence/lal/, licence Creative Commons By Sa version 2.0 ou ultérieure http://creativecommons.org/licenses/by-sa/2.0/fr/ et licence GNU FDL version 1.3 ou ultérieure http://www.gnu.org/licenses/fdl-1.3.html" -metadata artist="April - Cause Commune" -metadata contact="${url}" ${fichiertemp} + +${FFmpegBin} -y -i ${fichiertemp} -i i.meta -acodec copy -map 0:0 -map_metadata 1 ${destination} + +rm ${fichiertemp} +rm i.meta