Commit 55bab8bb authored by Stefan Westerfeld's avatar Stefan Westerfeld

Add hls-prepare command to audiowmark main binary.

Signed-off-by: Stefan Westerfeld's avatarStefan Westerfeld <stefan@space.twc.de>
parent eeab8b5a
......@@ -446,10 +446,6 @@ parse_shared_options (ArgParser& ap)
{
Params::mix = false;
}
if (ap.parse_opt ("--quiet") || ap.parse_opt ("-q"))
{
set_log_level (Log::WARNING);
}
if (Params::have_key > 1)
{
error ("audiowmark: watermark key can at most be set once (--key / --test-key option)\n");
......@@ -574,6 +570,10 @@ main (int argc, char **argv)
printf ("audiowmark %s\n", VERSION);
return 0;
}
if (ap.parse_opt ("--quiet") || ap.parse_opt ("-q"))
{
set_log_level (Log::WARNING);
}
if (ap.parse_cmd ("hls-add"))
{
parse_shared_options (ap);
......@@ -583,6 +583,13 @@ main (int argc, char **argv)
if (ap.parse_args (3, args))
return hls_add (args[0], args[1], args[2]);
}
else if (ap.parse_cmd ("hls-prepare"))
{
ap.parse_opt ("--bit-rate", Params::hls_bit_rate);
if (ap.parse_args (4, args))
return hls_prepare (args[0], args[1], args[2], args[3]);
}
else if (ap.parse_cmd ("add"))
{
parse_shared_options (ap);
......
......@@ -16,17 +16,64 @@
*/
#include <string>
#include <regex>
#include "utils.hh"
#include "mpegts.hh"
#include "sfinputstream.hh"
#include "hlsoutputstream.hh"
#include "sfoutputstream.hh"
#include "wmcommon.hh"
#include "wavdata.hh"
using std::string;
using std::vector;
using std::regex;
using std::map;
using std::min;
Error
xsystem (const string& cmd)
{
info ("+++ %s\n", cmd.c_str());
int rc = system (cmd.c_str());
int exit_status = WEXITSTATUS (rc);
if (exit_status != 0)
{
error ("audiowmark: failed to execute command:\n%s\n", cmd.c_str());
return Error (string_printf ("system failed / exit status %d", exit_status));
}
return Error::Code::NONE;
}
Error
ff_decode (const string& filename, WavData& out_wav_data)
{
FILE *tmp_file = tmpfile();
ScopedFile tmp_file_s (tmp_file);
string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file));
FILE *input_tmp_file = tmpfile();
ScopedFile input_tmp_file_s (input_tmp_file);
string input_tmp_file_name = string_printf ("/dev/fd/%d", fileno (input_tmp_file));
// write current ts
FILE *main = fopen (filename.c_str(), "r");
ScopedFile main_s (main);
int c;
while ((c = fgetc (main)) >= 0)
fputc (c, input_tmp_file);
fflush (input_tmp_file);
string cmd = string_printf ("ffmpeg -v error -y -f mpegts -i %s -f wav %s", input_tmp_file_name.c_str(), tmp_file_name.c_str());
Error err = xsystem (cmd.c_str());
if (err)
return err;
err = out_wav_data.load (tmp_file_name);
return err;
}
int
hls_add (const string& infile, const string& outfile, const string& bits)
{
......@@ -111,4 +158,151 @@ hls_add (const string& infile, const string& outfile, const string& bits)
return 0;
}
int
hls_prepare (const string& in_dir, const string& out_dir, const string& filename, const string& audio_master)
{
string in_name = in_dir + "/" + filename;
FILE *in_file = fopen (in_name.c_str(), "r");
ScopedFile in_file_s (in_file);
if (!in_file)
{
error ("audiowmark: error opening input playlist %s\n", in_name.c_str());
return 1;
}
string out_name = out_dir + "/" + filename;
FILE *out_file = fopen (out_name.c_str(), "w");
ScopedFile out_file_s (out_file);
if (!out_file)
{
error ("audiowmark: error opening output playlist %s\n", out_name.c_str());
return 1;
}
WavData audio_master_data;
Error err = audio_master_data.load (audio_master);
if (err)
{
error ("audiowmark: failed to load audio master: %s\n", audio_master.c_str());
return 1;
}
info ("AAC Bitrate: %d\n", Params::hls_bit_rate);
struct Segment
{
string name;
size_t size;
map<string, string> vars;
};
vector<Segment> segments;
char buffer[1024];
int line = 1;
const regex blank_re (R"(\s*(#.*)?)");
while (fgets (buffer, 1024, in_file))
{
/* kill newline chars at end */
int last = strlen (buffer) - 1;
while (last > 0 && (buffer[last] == '\n' || buffer[last] == '\r'))
buffer[last--] = 0;
string s = buffer;
std::smatch match;
if (regex_match (s, blank_re))
{
/* blank line or comment */
fprintf (out_file, "%s\n", s.c_str());
}
else
{
fprintf (out_file, "%s\n", s.c_str());
Segment segment;
segment.name = s;
segments.push_back (segment);
}
line++;
}
size_t start_pos = 0;
for (auto& segment : segments)
{
WavData out;
Error err = ff_decode (in_dir + "/" + segment.name, out);
if (err)
{
error ("audiowmark: hls: ff_decode failed: %s\n", err.message());
return 1;
}
printf ("%d %zd\n", out.sample_rate(), out.n_values() / out.n_channels());
segment.size = out.n_values() / out.n_channels();
/* obtain pts for first frame */
string cmd = string_printf ("ffprobe -v 0 -show_entries packet=pts_time %s/%s -of compact=p=0:nk=1 | grep '^[0-9]'", in_dir.c_str(), segment.name.c_str());
FILE *pts = popen (cmd.c_str(), "r");
char buffer[1024];
if (fgets (buffer, 1024, pts))
{
if (strlen (buffer) && buffer[strlen (buffer) - 1] == '\n')
buffer[strlen (buffer) - 1] = 0;
segment.vars["pts_start"] = buffer;
}
fclose (pts);
/* store 3 seconds of the context before this segment and after this segment (if available) */
const size_t ctx_3sec = 3 * out.sample_rate();
const size_t prev_size = min<size_t> (start_pos, ctx_3sec);
const size_t next_size = min<size_t> (audio_master_data.n_frames() - (segment.size + start_pos), ctx_3sec);
segment.vars["start_pos"] = string_printf ("%zd", start_pos);
segment.vars["size"] = string_printf ("%zd", segment.size);
segment.vars["prev_size"] = string_printf ("%zd", prev_size);
segment.vars["next_size"] = string_printf ("%zd", next_size);
/* write audio segment with context */
const size_t start_point = start_pos - prev_size;
const size_t end_point = start_point + prev_size + segment.size + next_size;
vector<float> out_signal (audio_master_data.samples().begin() + start_point * audio_master_data.n_channels(),
audio_master_data.samples().begin() + end_point * audio_master_data.n_channels());
vector<unsigned char> full_flac_mem;
SFOutputStream out_stream;
err = out_stream.open (&full_flac_mem,
audio_master_data.n_channels(), audio_master_data.sample_rate(), audio_master_data.bit_depth(),
SFOutputStream::OutFormat::FLAC);
if (err)
{
error ("audiowmark: hls: open context flac failed: %s\n", err.message());
return 1;
}
err = out_stream.write_frames (out_signal);
if (err)
{
error ("audiowmark: hls: write context flac failed: %s\n", err.message());
return 1;
}
err = out_stream.close();
if (err)
{
error ("audiowmark: hls: close context flac failed: %s\n", err.message());
return 1;
}
/* store everything we need in a mpegts file */
TSWriter writer;
writer.append_data ("full.flac", full_flac_mem);
writer.append_vars ("vars", segment.vars);
writer.process (in_dir + "/" + segment.name, out_dir + "/" + segment.name);
/* start position for the next segment */
start_pos += segment.size;
}
return 0;
}
......@@ -15,11 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AUDIOWMARK_MPEGTS_HH
#define AUDIOWMARK_MPEGTS_HH
#ifndef AUDIOWMARK_HLS_HH
#define AUDIOWMARK_HLS_HH
#include <string>
int hls_add (const std::string& infile, const std::string& outfile, const std::string& bits);
int hls_prepare (const std::string& in_dir, const std::string& out_dir, const std::string& filename, const std::string& audio_master);
Error ff_decode (const std::string& filename, WavData& out_wav_data);
#endif /* AUDIOWMARK_MPEGTS_HH */
......@@ -24,9 +24,9 @@
#include "mpegts.hh"
#include "wavdata.hh"
#include "wmcommon.hh"
#include "hls.hh"
#include "sfinputstream.hh"
#include "hlsoutputstream.hh"
#include "sfoutputstream.hh"
using std::string;
using std::regex;
......@@ -34,193 +34,6 @@ using std::vector;
using std::map;
using std::min;
Error
xsystem (const string& cmd)
{
info ("+++ %s\n", cmd.c_str());
int rc = system (cmd.c_str());
int exit_status = WEXITSTATUS (rc);
if (exit_status != 0)
{
error ("audiowmark: failed to execute command:\n%s\n", cmd.c_str());
return Error (string_printf ("system failed / exit status %d", exit_status));
}
return Error::Code::NONE;
}
Error
ff_decode (const string& filename, WavData& out_wav_data)
{
FILE *tmp_file = tmpfile();
ScopedFile tmp_file_s (tmp_file);
string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file));
FILE *input_tmp_file = tmpfile();
ScopedFile input_tmp_file_s (input_tmp_file);
string input_tmp_file_name = string_printf ("/dev/fd/%d", fileno (input_tmp_file));
// write current ts
FILE *main = fopen (filename.c_str(), "r");
ScopedFile main_s (main);
int c;
while ((c = fgetc (main)) >= 0)
fputc (c, input_tmp_file);
fflush (input_tmp_file);
string cmd = string_printf ("ffmpeg -v error -y -f mpegts -i %s -f wav %s", input_tmp_file_name.c_str(), tmp_file_name.c_str());
Error err = xsystem (cmd.c_str());
if (err)
return err;
err = out_wav_data.load (tmp_file_name);
return err;
}
int
hls_embed_context (const string& in_dir, const string& out_dir, const string& filename, const string& audio_master)
{
string in_name = in_dir + "/" + filename;
FILE *in_file = fopen (in_name.c_str(), "r");
ScopedFile in_file_s (in_file);
if (!in_file)
{
error ("audiowmark: error opening input playlist %s\n", in_name.c_str());
return 1;
}
string out_name = out_dir + "/" + filename;
FILE *out_file = fopen (out_name.c_str(), "w");
ScopedFile out_file_s (out_file);
if (!out_file)
{
error ("audiowmark: error opening output playlist %s\n", out_name.c_str());
return 1;
}
WavData audio_master_data;
Error err = audio_master_data.load (audio_master);
if (err)
{
error ("audiowmark: failed to load audio master: %s\n", audio_master.c_str());
return 1;
}
struct Segment
{
string name;
size_t size;
map<string, string> vars;
};
vector<Segment> segments;
char buffer[1024];
int line = 1;
const regex blank_re (R"(\s*(#.*)?)");
while (fgets (buffer, 1024, in_file))
{
/* kill newline chars at end */
int last = strlen (buffer) - 1;
while (last > 0 && (buffer[last] == '\n' || buffer[last] == '\r'))
buffer[last--] = 0;
string s = buffer;
std::smatch match;
if (regex_match (s, blank_re))
{
/* blank line or comment */
fprintf (out_file, "%s\n", s.c_str());
}
else
{
fprintf (out_file, "%s\n", s.c_str());
Segment segment;
segment.name = s;
segments.push_back (segment);
}
line++;
}
size_t start_pos = 0;
for (auto& segment : segments)
{
WavData out;
Error err = ff_decode (in_dir + "/" + segment.name, out);
if (err)
{
error ("audiowmark: hls: ff_decode failed: %s\n", err.message());
return 1;
}
printf ("%d %zd\n", out.sample_rate(), out.n_values() / out.n_channels());
segment.size = out.n_values() / out.n_channels();
/* obtain pts for first frame */
string cmd = string_printf ("ffprobe -v 0 -show_entries packet=pts_time %s/%s -of compact=p=0:nk=1 | grep '^[0-9]'", in_dir.c_str(), segment.name.c_str());
FILE *pts = popen (cmd.c_str(), "r");
char buffer[1024];
if (fgets (buffer, 1024, pts))
{
if (strlen (buffer) && buffer[strlen (buffer) - 1] == '\n')
buffer[strlen (buffer) - 1] = 0;
segment.vars["pts_start"] = buffer;
}
fclose (pts);
/* store 3 seconds of the context before this segment and after this segment (if available) */
const size_t ctx_3sec = 3 * out.sample_rate();
const size_t prev_size = min<size_t> (start_pos, ctx_3sec);
const size_t next_size = min<size_t> (audio_master_data.n_frames() - (segment.size + start_pos), ctx_3sec);
segment.vars["start_pos"] = string_printf ("%zd", start_pos);
segment.vars["size"] = string_printf ("%zd", segment.size);
segment.vars["prev_size"] = string_printf ("%zd", prev_size);
segment.vars["next_size"] = string_printf ("%zd", next_size);
/* write audio segment with context */
const size_t start_point = start_pos - prev_size;
const size_t end_point = start_point + prev_size + segment.size + next_size;
vector<float> out_signal (audio_master_data.samples().begin() + start_point * audio_master_data.n_channels(),
audio_master_data.samples().begin() + end_point * audio_master_data.n_channels());
vector<unsigned char> full_flac_mem;
SFOutputStream out_stream;
err = out_stream.open (&full_flac_mem,
audio_master_data.n_channels(), audio_master_data.sample_rate(), audio_master_data.bit_depth(),
SFOutputStream::OutFormat::FLAC);
if (err)
{
error ("audiowmark: hls: open context flac failed: %s\n", err.message());
return 1;
}
err = out_stream.write_frames (out_signal);
if (err)
{
error ("audiowmark: hls: write context flac failed: %s\n", err.message());
return 1;
}
err = out_stream.close();
if (err)
{
error ("audiowmark: hls: close context flac failed: %s\n", err.message());
return 1;
}
/* store everything we need in a mpegts file */
TSWriter writer;
writer.append_data ("full.flac", full_flac_mem);
writer.append_vars ("vars", segment.vars);
writer.process (in_dir + "/" + segment.name, out_dir + "/" + segment.name);
/* start position for the next segment */
start_pos += segment.size;
}
return 0;
}
class WDInputStream : public AudioInputStream
{
WavData *wav_data;
......@@ -378,12 +191,7 @@ seek_perf (int sample_rate, double seconds)
int
main (int argc, char **argv)
{
if (argc == 6 && strcmp (argv[1], "hls-embed-context") == 0)
{
info ("hls-embed-context: in_dir=%s out_dir=%s m3u8=%s audio_master=%s\n", argv[2], argv[3], argv[4], argv[5]);
return hls_embed_context (argv[2], argv[3], argv[4], argv[5]);
}
else if (argc == 6 && strcmp (argv[1], "test-seek") == 0)
if (argc == 6 && strcmp (argv[1], "test-seek") == 0)
{
return test_seek (argv[2], argv[3], atoi (argv[4]), argv[5]);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment