ffmpegfs-2.50/0000775000175000017500000000000015215723161007003 5ffmpegfs-2.50/src/0000775000175000017500000000000015215723160007571 5ffmpegfs-2.50/src/ffmpeg_subtitle.cc0000664000175000017500000000450215177713600013205 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_subtitle.cc * @brief FFmpeg_Subtitle class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_subtitle.h" FFmpeg_Subtitle::FFmpeg_Subtitle(int stream_index) : std::shared_ptr(alloc_subtitle(), &FFmpeg_Subtitle::delete_subtitle), m_res(0), m_stream_idx(stream_index) { m_res = (get() != nullptr) ? 0 : AVERROR(ENOMEM); } void FFmpeg_Subtitle::unref() noexcept { reset(); } int FFmpeg_Subtitle::res() const { return m_res; } FFmpeg_Subtitle::operator AVSubtitle*() { return get(); } FFmpeg_Subtitle::operator const AVSubtitle*() const { return get(); } AVSubtitle* FFmpeg_Subtitle::operator->() { return get(); } AVSubtitle* FFmpeg_Subtitle::alloc_subtitle() { return reinterpret_cast(av_mallocz(sizeof(AVSubtitle))); } void FFmpeg_Subtitle::delete_subtitle(AVSubtitle *subtitle) { if (subtitle != nullptr) { avsubtitle_free(subtitle); av_free(subtitle); } } ffmpegfs-2.50/src/diskio.cc0000664000175000017500000000515315177713600011313 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file diskio.cc * @brief DiskIO class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "diskio.h" #include "logging.h" #include DiskIO::DiskIO() : m_fpi(nullptr) { } DiskIO::~DiskIO() { pvt_close(); } VIRTUALTYPE DiskIO::type() const { return VIRTUALTYPE::DISK; } size_t DiskIO::bufsize() const { return (100 /* KB */ * 1024); } int DiskIO::openio(LPVIRTUALFILE virtualfile) { set_virtualfile(virtualfile); Logging::debug(virtualfile->m_origfile, "Opening the input file."); m_fpi = fopen(virtualfile->m_origfile.c_str(), "rb"); if (m_fpi != nullptr) { return 0; } else { return errno; } } size_t DiskIO::readio(void * data, size_t size) { return fread(data, 1, size, m_fpi); } int DiskIO::error() const { return ferror(m_fpi); } int64_t DiskIO::duration() const { return AV_NOPTS_VALUE; // not applicable } size_t DiskIO::size() const { if (m_fpi == nullptr) { errno = EINVAL; return 0; } struct stat stbuf; fstat(fileno(m_fpi), &stbuf); return static_cast(stbuf.st_size); } size_t DiskIO::tell() const { return static_cast(ftell(m_fpi)); } int DiskIO::seek(int64_t offset, int whence) { return fseek(m_fpi, static_cast(offset), whence); } bool DiskIO::eof() const { return feof(m_fpi) ? true : false; } void DiskIO::closeio() { pvt_close(); } void DiskIO::pvt_close() { FILE *fpi = m_fpi; if (fpi != nullptr) { m_fpi = nullptr; fclose(fpi); } } ffmpegfs-2.50/src/scripts/0000775000175000017500000000000015215723160011260 5ffmpegfs-2.50/src/scripts/videotag.txt0000644000175000017500000000054415052412650013542 videotag.php is a small script that allows testing the video tag easily. Just put it into the base path directory, mount it to a folder that is accessible to your (PHP enabled) web server. When the page is opened, all transcoded files are listed on the right and playback can be started by clicking on the link. Very handy to test browser capabilities...ffmpegfs-2.50/src/scripts/hls.html0000644000175000017500000000237715052412650012661 HLS Demo

Hls.js demo - basic usage

ffmpegfs-2.50/src/scripts/videotag.php0000644000175000017500000001050015052412650013503 Transcoder Test
\n\n"; echo "\n\n"; } foreach ($dirs as &$value) { echo "\n\n"; } echo ""; foreach ($files as &$value) { echo "\n\n"; } ?>
"; echo "Go back"; echo ""; echo "
"; echo ""; echo "
"; echo "" . utf8_encode($value) . " "; echo "\n"; echo "
"; echo ""; echo "
"; echo "" . utf8_encode($value) . " "; echo ""; echo "Preload"; echo "

Supported file types
ffmpegfs-2.50/src/dvdparser.h0000664000175000017500000000331715177713600011665 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file dvdparser.h * @brief DVD parser * * This is only available if built with -DUSE_LIBDVD parameter. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef DVDPARSER_H #define DVDPARSER_H #pragma once #ifdef USE_LIBDVD #include /** * @brief Get number of titles on DVD * @param[in] path - Path to check * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see0 * @return -errno or number or titles on DVD */ int check_dvd(const std::string & path, void *buf = nullptr, fuse_fill_dir_t filler = nullptr); #endif // USE_LIBDVD #endif // DVDPARSER_H ffmpegfs-2.50/src/vcd/0000775000175000017500000000000015215723160010345 5ffmpegfs-2.50/src/vcd/vcdchapter.cc0000664000175000017500000001540115175426212012723 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdchapter.cc * @brief S/VCD VcdChapter class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #include "ffmpegfs.h" #include "vcdchapter.h" #include "vcdutils.h" #include "ffmpeg_utils.h" #include VcdChapter::VcdChapter(bool is_svcd) : m_is_svcd(is_svcd), m_track_no(0), m_min(0), m_sec(0), m_frame(0), m_duration(0), m_start_pos(0), m_end_pos(0) { } VcdChapter::VcdChapter(const VCDCHAPTER & VcdChapter, bool is_svcd) : m_is_svcd(is_svcd), m_track_no(VcdChapter.m_track_no), m_min(BCD2DEC(VcdChapter.m_msf.m_min)), m_sec(BCD2DEC(VcdChapter.m_msf.m_sec)), m_frame(BCD2DEC(VcdChapter.m_msf.m_frame)), m_duration(0), m_start_pos(0), m_end_pos(0) { } VcdChapter::VcdChapter(int track_no, int min, int sec, int frame, bool is_svcd, int64_t duration) : m_is_svcd(is_svcd), m_track_no(track_no), m_min(min), m_sec(sec), m_frame(frame), m_duration(duration), m_start_pos(0), m_end_pos(0) { } int VcdChapter::readio(FILE *fpi, int track_no) { VCDMSF msf; std::array buffer; // Read first sync if (fread(&buffer, 1, buffer.size(), fpi) != buffer.size()) { return ferror(fpi); } // Validate sync if (buffer != SYNC) { return EIO; } // Read position block std::memset(&msf, 0, sizeof(msf)); if (fread(reinterpret_cast(&msf), 1, sizeof(msf), fpi) != sizeof(msf)) { return ferror(fpi); } m_track_no = track_no; m_min = BCD2DEC(msf.m_min); m_sec = BCD2DEC(msf.m_sec); m_frame = BCD2DEC(msf.m_frame); return 0; } bool VcdChapter::get_is_svcd() const { return m_is_svcd; } int VcdChapter::get_track_no() const { return m_track_no; } int VcdChapter::get_min() const { return m_min; } int VcdChapter::get_sec() const { return m_sec; } int VcdChapter::get_frame() const { return m_frame; } int64_t VcdChapter::get_duration() const { return m_duration; } std::string VcdChapter::get_filename() const { std::string buffer; if (m_is_svcd) { strsprintf(&buffer, "MPEG2/AVSEQ%02i.MPG", m_track_no - 1); } else { strsprintf(&buffer, "MPEGAV/AVSEQ%02i.DAT", m_track_no - 1); } return buffer; } uint64_t VcdChapter::get_start_pos() const { return m_start_pos; } uint64_t VcdChapter::get_end_pos() const { return m_end_pos; } uint64_t VcdChapter::get_size() const { return (m_end_pos - m_start_pos); } int64_t VcdChapter::get_start_time() const { // MSF format: minutes, seconds, and fractional seconds called frames. Each timecode frame is one seventy-fifth of a second. return static_cast(m_min * 60 + m_sec) * AV_TIME_BASE + (static_cast(m_frame) * AV_TIME_BASE / 75); } //Conversion from MSF to LBA //-------------------------- //As from Red book because there are 75 frames in 1 second, so, //LBA = Minute * 60 * 75 + Second * 75 + Frame - 150 //The minus 150 is the 2 second pregap that is recorded on every CD. //Conversion from LBA to MSF //-------------------------- //Minute = Int((LBA + 150) / (60 * 75)) //Second = Int(LBA + 150 - Minute * 60 * 75) / 75) //Frame = LBA + 150 - Minute * 60 * 75 - Second * 75 //Where Int() is a function that truncates the fractional part giving only the whole number part. int VcdChapter::get_lba() const { return m_frame + (m_sec + m_min * 60) * 75; } VcdChapter & VcdChapter::operator= (VcdChapter const & other) { if (this != & other) //oder if (*this != rhs) { m_is_svcd = other.m_is_svcd; m_track_no = other.m_track_no; m_min = other.m_min; m_sec = other.m_sec; m_frame = other.m_frame; m_start_pos = other.m_start_pos; m_end_pos = other.m_end_pos; m_duration = other.m_duration; } return *this; //Referenz auf das Objekt selbst zurückgeben } int VcdChapter::operator==(const VcdChapter & other) const { return (m_track_no == other.m_track_no && m_min == other.m_min && m_sec == other.m_sec && m_frame == other.m_frame); } int VcdChapter::operator<(const VcdChapter & other) const { int res; res = (m_track_no - other.m_track_no); if (res < 0) { return 1; } if (res > 0) { return 0; } res = (m_min - other.m_min); if (res < 0) { return 1; } if (res > 0) { return 0; } res = (m_sec - other.m_sec); if (res < 0) { return 1; } if (res > 0) { return 0; } res = (m_frame - other.m_frame); if (res < 0) { return 1; } return 0; } int VcdChapter::operator<=(const VcdChapter & other) const { if (*this == other) { return 1; } return (*this < other); } int VcdChapter::operator>(const VcdChapter & other) const { int res; res = (m_track_no - other.m_track_no); if (res > 0) { return 1; } if (res < 0) { return 0; } res = (m_min - other.m_min); if (res > 0) { return 1; } if (res < 0) { return 0; } res = (m_sec - other.m_sec); if (res > 0) { return 1; } if (res < 0) { return 0; } res = (m_frame - other.m_frame); if (res > 0) { return 1; } //if (res <= 0) return 0; } int VcdChapter::operator>=(const VcdChapter & other) const { if (*this == other) { return 1; } return (*this > other); } int VcdChapter::operator!=(const VcdChapter & other) const { return (m_track_no != other.m_track_no && m_min != other.m_min && m_sec != other.m_sec && m_frame != other.m_frame); } ffmpegfs-2.50/src/vcd/vcdutils.h0000664000175000017500000001347215175426212012305 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdutils.h * @brief S/VCD utility functions * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #ifndef VCDUTILS_H #define VCDUTILS_H #pragma once #include "vcd/vcdchapter.h" #include #include /** Convert BCD value to hex */ #define BCD2DEC(hex) (((hex & 0xF0) >> 4) * 10 + (hex & 0x0F)) /** Max. number of chapters on video CD */ #define VCD_MAX_CHAPTERS 500 #pragma pack(push, 1) /** @brief Video CD MSF time format * * MSF in minutes, seconds, and fractional seconds (frames). @n * * @note The fields in this structure are BCD encoded */ typedef struct VCDMSF { uint8_t m_min; /**< @brief Minute in BCD code */ uint8_t m_sec; /**< @brief Second in BCD code */ uint8_t m_frame; /**< @brief Number of frames, for a 25 frames per second movie 0...24. */ } VCDMSF, *LPVCDMSF; /**< @brief Pointer version of VCDMSF */ typedef const VCDMSF * LPCVCDMSF; /**< @brief Pointer to const version of VCDMSF */ /** @brief Video CD chapter */ typedef struct VCDCHAPTER { uint8_t m_track_no; /**< @brief Track number */ VCDMSF m_msf; /**< @brief MSF position of chapter in file */ } VCDCHAPTER, *LPVCDCHAPTER; /**< @brief Pointer version of VCDCHAPTER */ typedef const VCDCHAPTER * LPCVCDCHAPTER; /**< @brief Pointer to const version of VCDCHAPTER */ /** @brief Video CD entry */ typedef struct VCDENTRIES { std::array m_ID; /**< @brief 8 Bytes: ID "ENTRYVCD" or "ENTRYSVD" */ /** @brief 1 Byte: CD type * * 1 for VCD 1.0, VCD 1.1, SVCD 1.0 and HQVCD @n * 2 for VCD 2.0 @n * Identical with value in INFO.VCD/SVD @n */ uint8_t m_type; /** @brief 1 Byte: System Profile Tag. * * 0 for VCD 1.0, VCD 2.0, SVCD und HQVCD * 1 for VCD 1.1 * Identical with value in INFO.VCD/SVD */ uint8_t m_profile_tag; /** @brief 2 Bytes: 1 <= tracks <= 500 * * There must be at least 1 chapter */ uint16_t m_num_entries; /** @brief Chapters * * Number of chapters as in m_profile_tag @n * @n * 1 Byte: Tracknummer @n * 3 Byte: Adresse MSF */ std::array m_chapter; std::array reserved; /**< @brief RESERVED, must be 0x00 */ } VCDENTRY, *LPVCDENTRIES; /**< @brief Pointer version of VCDENTRY */ typedef const VCDENTRY * LPCVCDENTRIES; /**< @brief Pointer to const version of VCDENTRY */ #pragma pack(pop) extern const std::array SYNC; /**< @brief Chapter synchronisation in S/VCD mpeg/dat files (12 byte: 0x00FFFFFFFFFFFFFFFFFFFF00) */ /** @namespace VCDUTILS * @brief Video CD utility functions */ namespace VCDUTILS { /** * @brief Non-zero terminated text is converted to std::string. * @param[in] txt - Text for conversion (not zero-terminated). * @param[in] size - Length of string. * @param[in] trimmed - If true, trim trailing white spaces. * @return Converted text */ std::string convert_txt2string(const char * txt, int size, bool trimmed = true); /** * @brief Check if path is a S/VCD * @param[in] path - Path to check * @param[in] filename - File name to check, can be done per ENTRIES or INFO (extension .SVD or .VCD will be added automatically). * @param[out] fullname - Path and filename of ENTRIES.SVC/VCD or INFO.SVC/VCD, if found. * @param[out] is_vcd - True if the directory contains a Super Video CD, false if it's a Video CD. * @return true if path contains a S/VCD, false if not. */ bool locate_file(const std::string & path, const std::string & filename, std::string & fullname, bool & is_vcd); /** * @brief Locate AVSEQ*DAT/MPEG video file for track_no. * @param[in] path - path to search in. * @param[in] track_no - track number (1...n). * @param[out] fullname - name and path of file if found. * @return If successful, it returns 0 or errno if not. */ int locate_video(const std::string & path, int track_no, std::string & fullname); /** * @brief Return disk type as a human readable string. * @param[in] type - 1: VCD 1.0, VCD 1.1, SVCD 1.0, HQVCD, 2: VCD 2.0 * @return Disk type as a human readable string. */ std::string get_type_str(VCDTYPE type); /** * @brief Profile as a human readable string. * @param[in] tag - 1: VCD 1.0, VCD 2.0, SVCD, HQVCD, 2: VCD 1.1 * @return Returns profile as a human readable string. */ std::string get_profile_tag_str(VCDPROFILETAG tag); /** * @brief Check if fullname is a directory. Remove the filename if necessary. * @note Really checks if fullname is a path even if the trailing slash is missing. * @param[in] fullname - Path and optional filename. * @param[out] directory - Directory without a file name. */ void get_directory(const std::string & fullname, std::string * directory); } #endif // VCDUTILS_H ffmpegfs-2.50/src/vcd/vcdchapter.h0000664000175000017500000001621215175426212012566 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdchapter.h * @brief S/VCD VcdChapter class * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #ifndef VCDCHAPTER_H #define VCDCHAPTER_H #pragma once #include #include /** * S/VCD type */ enum class VCDTYPE { UNKNOWN = -1, /**< @brief unknown type */ VCD_10_11_SVCD_10_HQVCD = 1, /**< @brief VCD 1.0, VCD 1.1, SVCD 1.0 und HQVCD */ VCD_20 = 2 /**< @brief VCD 2.0 */ }; /** * S/VCD profile tag */ enum class VCDPROFILETAG { UNKNOWN = -1, /**< @brief unknown file tag */ VCD_10_20_SVCD_HQVCD = 0, /**< @brief VCD 1.0, VCD 2.0, SVCD und HQVCD */ VCD_11 = 1 /**< @brief VCD 1.1 */ }; struct VCDCHAPTER; /**< @brief Video CD chapter forward declaration */ typedef struct VCDCHAPTER VCDCHAPTER; /** @brief Video CD chapter */ class VcdChapter { friend class VcdEntries; public: /** * @brief Construct VcdChapter object * @param[in] is_svcd - true for SVCD CD, false for VCD */ explicit VcdChapter(bool is_svcd); /** * @brief Construct VcdChapter object * @param[in] VcdChapter - source object to copy from * @param[in] is_svcd - true for SVCD CD, false for VCD */ explicit VcdChapter(const VCDCHAPTER & VcdChapter, bool is_svcd); /** * @brief Construct VcdChapter object * @param[in] track_no - track number 1.. * @param[in] min - Start minute * @param[in] sec - Start second * @param[in] frame - Start frame * @param[in] is_svcd - true for SVCD CD, false for VCD * @param[in] duration - Chapter duration, in AV_TIME_BASE fractional seconds */ explicit VcdChapter(int track_no, int min, int sec, int frame, bool is_svcd, int64_t duration); /** * @brief Destroy VcdChapter object */ virtual ~VcdChapter() = default; /** * @brief Check if this is a Super Video CD. * @return Returns true for SVCD, false for VCD. */ bool get_is_svcd() const; /** * @brief Get the track number of this chapter. * @return Returns track number. */ int get_track_no() const; /** * @brief Get MSF (minutes, seconds, and fractional seconds/frames) minute. * @return Returns MSF minute. */ int get_min() const; /** * @brief Get MSF (minutes, seconds, and fractional seconds/frames) second. * @return Returns MSF second. */ int get_sec() const; /** * @brief Get MSF (minutes, seconds, and fractional seconds/frames) frame. * * Each timecode frame is one seventy-fifth of a second. * * @return Returns MSF frame. */ int get_frame() const; /** * @brief Get chapter duration, in AV_TIME_BASE fractional seconds. * @return Returns the chapter duration, in AV_TIME_BASE fractional seconds. */ int64_t get_duration() const; /** * @brief Get file name and path of source file (e.g. MPEG/AVSEQ##.MPG). * @return Returns file name and path of source file. */ std::string get_filename() const; /** * @brief Get file position of chapter in bytes. * @return Returns file position of chapter in bytes. */ uint64_t get_start_pos() const; /** * @brief Get end position of chapter in bytes. * @return Returns end position of chapter in bytes. */ uint64_t get_end_pos() const; /** * @brief Get start position of chapter in AV_TIME_BASE units. * @return Returns start position of chapter in AV_TIME_BASE units. */ int64_t get_start_time() const; /** * @brief Get the size of this chapter in bytes. * @return Returns chapter size in bytes. */ uint64_t get_size() const; /** * @brief Get LBA (large block address) of chapter. * @return Returns LBA of chapter. */ int get_lba() const; /** * @brief Assignment operator = * @param[in] other * @return this */ VcdChapter & operator= (VcdChapter const & other); /** * @brief Comparison operator == * @param[in] other * @return Nonzero if equal, 0 if not */ int operator==(const VcdChapter & other) const; /** * @brief Comparison operator < * @param[in] other * @return Nonzero if this object is smaller, 0 if not */ int operator<(const VcdChapter & other) const; /** * @brief Comparison operator <= * @param[in] other * @return Nonzero if this object is smaller or equal, 0 if not */ int operator<=(const VcdChapter & other) const; /** * @brief Comparison operator > * @param[in] other * @return Nonzero if this object is greater, 0 if not */ int operator>(const VcdChapter & other) const; /** * @brief Comparison operator >= * @param[in] other * @return Nonzero if this object is greater or equal, 0 if not */ int operator>=(const VcdChapter & other) const; /** * @brief Comparison operator != * @param[in] other * @return Nonzero if this object is not equal, 0 if equal */ int operator!=(const VcdChapter & other) const; protected: /** * @brief Read file from disk * @param[in] fpi - Open file object to read from * @param[in] track_no - Track number 1... * @return */ int readio(FILE *fpi, int track_no); protected: bool m_is_svcd; /**< @brief true for SVCD, false for VCD */ int m_track_no; /**< @brief Track no */ int m_min; /**< @brief MSF minute */ int m_sec; /**< @brief MSF second */ int m_frame; /**< @brief MSF frame */ int64_t m_duration; /**< @brief Chapter duration, in AV_TIME_BASE fractional seconds */ uint64_t m_start_pos; /**< @brief Start offset in bytes */ uint64_t m_end_pos; /**< @brief End offset in bytes (not including this byte) */ }; #endif // VCDCHAPTER_H ffmpegfs-2.50/src/vcd/vcdutils.cc0000664000175000017500000000724215175426212012441 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdutils.cc * @brief S/VCD utility functions implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #include "ffmpegfs.h" #include "vcd/vcdchapter.h" #include "vcdutils.h" #include "ffmpeg_utils.h" #include #include #include namespace VCDUTILS { std::string convert_txt2string(const char * txt, int size, bool trimmed) { std::unique_ptr buffer = std::make_unique(static_cast(size + 1)); std::string ret_value; if (buffer != nullptr) { std::memcpy(buffer.get(), txt, static_cast(size)); *(buffer.get() + size) = '\0'; ret_value = buffer.get(); } if (trimmed) { return trim(ret_value); } else { return ret_value; } } bool locate_file(const std::string & path, const std::string & filename, std::string & fullname, bool & is_vcd) { is_vcd = false; // Try VCD fullname = path + "VCD/" + filename + ".VCD"; if (!access(fullname.c_str(), F_OK)) { return true; } // Try SVCD fullname = path + "SVCD/" + filename + ".SVD"; if (!access(fullname.c_str(), F_OK)) { is_vcd = true; return true; } return false; } int locate_video(const std::string & path, int track_no, std::string & fullname) { std::string buffer; // Try VCD strsprintf(&buffer, "MPEGAV/AVSEQ%02i.DAT", track_no - 1); fullname = path + buffer; if (!access(fullname.c_str(), F_OK)) { return 0; } // Try SVCD strsprintf(&buffer, "MPEG2/AVSEQ%02i.MPG", track_no - 1); fullname = path + buffer; if (!access(fullname.c_str(), F_OK)) { return 0; } return ENOENT; } std::string get_type_str(VCDTYPE type) { switch (type) { case VCDTYPE::VCD_10_11_SVCD_10_HQVCD: { return "VCD 1.0, VCD 1.1, SVCD 1.0, HQVCD"; } case VCDTYPE::VCD_20: { return "VCD 2.0"; } default: { return ""; } } } std::string get_profile_tag_str(VCDPROFILETAG tag) { switch (tag) { case VCDPROFILETAG::VCD_10_20_SVCD_HQVCD: { return "VCD 1.0, VCD 2.0, SVCD, HQVCD"; } case VCDPROFILETAG::VCD_11: { return "VCD 1.1"; } default: { return ""; } } } void get_directory(const std::string & fullname, std::string *directory) { struct stat stbuf; stat(fullname.c_str(), &stbuf); if (S_ISDIR(stbuf.st_mode)) { // Already a directory *directory = fullname; append_sep(directory); } else { // Make it a directory *directory = fullname; remove_filename(directory); } } } ffmpegfs-2.50/src/vcd/vcdinfo.h0000664000175000017500000000637215175426212012101 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdinfo.h * @brief S/VCD VcdInfo class * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #ifndef VCDINFO_H #define VCDINFO_H #pragma once #include "vcdchapter.h" /** * @brief The #VcdInfo class */ class VcdInfo { public: /** * @brief Construct VcdInfo object */ explicit VcdInfo(); /** * @brief Destruct VcdInfo object */ virtual ~VcdInfo() = default; /** * @brief Reset this object */ void clear(); /** * @brief Load VCD from path * @param[in] path - path to locate VCD in * @return If successful, returns 0; otherwise, returns errno. */ int load_file(const std::string & path); const time_t & get_file_date() const; /**< @brief Date of disk (of INFO.VCD or SVD) */ const std::string & get_id() const; /**< @brief Get disk ID */ VCDTYPE get_type() const; /**< @brief Get disk type */ std::string get_type_str() const; /**< @brief Get disk type as string */ VCDPROFILETAG get_profile_tag() const; /**< @brief Get disk profile tag */ std::string get_profile_tag_str() const; /**< @brief Get disk profile tag as string */ const std::string & get_album_id() const; /**< @brief Get album ID */ int get_number_of_cds() const; /**< @brief Get number of CDs in set */ int get_cd_number() const; /**< @brief Get CD number in set */ protected: // Common data std::string m_disk_path; /**< @brief Path to disk */ time_t m_file_date; /**< @brief File date */ std::string m_id; /**< @brief ID of this CD. */ VCDTYPE m_type; /**< @brief Type of CD. */ VCDPROFILETAG m_profile_tag; /**< @brief System profile tag. */ // INFO.XXX data std::string m_album_id; /**< @brief Album ID */ int m_number_of_cds; /**< @brief Number of CDs in set */ int m_cd_number; /**< @brief Number of this CD in set */ }; #endif // VCDINFO_H ffmpegfs-2.50/src/vcd/vcdinfo.cc0000664000175000017500000001240615175426212012232 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdinfo.cc * @brief S/VCD VcdInfo class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #include "ffmpegfs.h" #include "vcdinfo.h" #include "vcdutils.h" #include #include #include #include /** * @brief VCDINFO structure of INFO.VCD/SVD file */ typedef struct VCDINFO { #pragma pack(1) /** * @brief 8 Byte: ID for this CD * * This ID is CD type dependant: VIDEO_CD, SUPERVCD or HQ-VCD. It is always 8 bytes long. */ std::array m_ID; /** * @brief 1 Byte: CD type * * 1 for VCD 1.0, VCD 1.1, SVCD 1.0 and HQVCD * 2 for VCD 2.0 */ char m_type; /** * @brief 1 Byte: System profile tag * * 0 fuer VCD 1.0, VCD 2.0, SVCD und HQVCD * 1 fuer VCD 1.1 */ char m_profile_tag; /** * @brief 16 Byte: Album ID * * The album ID is the name of this album. */ std::array m_albumid; /** * @brief 2 Byte: Number of CDs in set * * Total number of CDs in this set. */ short m_numberof_cds; /** * @brief 2 Byte: Number of this CD * * Defines which number this CD is in this set. */ short m_cd_number; /** * @brief 98 Byte: PAL Flags * * The meaning of these flags is unknown to me. */ std::array m_palflags; // reserved1: // restriction: // special info: // user data cc: // start lid #2: // start track #2: // reserved2: // psd size: // first segment addr: // offset multiplier: // maximum lid: // maximum segment number: // SEGMENT[1]: audio: video: // volume start time[0]: // ... } VCDINFO, *LPVCDINFO; /**< @brief Pointer version of VCDINFO */ typedef const VCDINFO * LPCVCDINFO; /**< @brief Pointer to const version of VCDINFO */ static_assert(sizeof(VCDINFO) == 128); VcdInfo::VcdInfo() { clear(); } void VcdInfo::clear() { m_disk_path.clear(); m_file_date = -1; m_id.clear(); m_type = VCDTYPE::UNKNOWN; m_profile_tag = VCDPROFILETAG::UNKNOWN; m_album_id.clear(); m_number_of_cds = 0; m_cd_number = 0; } int VcdInfo::load_file(const std::string & path) { std::string fullname; bool is_svcd = false; clear(); if (!VCDUTILS::locate_file(path, "INFO", fullname, is_svcd)) { return errno; } VCDUTILS::get_directory(path, &m_disk_path); FILE *fpi; VCDINFO vi; bool success = true; fpi = fopen(fullname.c_str(), "rb"); if (fpi == nullptr) { return errno; } struct stat stbuf; if (fstat(fileno(fpi), &stbuf) != 0) { return ferror(fpi); } m_file_date = stbuf.st_mtime; std::memset(&vi, 0, sizeof(vi)); if (fread(reinterpret_cast(&vi), 1, sizeof(vi), fpi) == sizeof(vi)) { m_id = VCDUTILS::convert_txt2string(vi.m_ID.data(), vi.m_ID.size()); m_type = static_cast(vi.m_type); m_profile_tag = static_cast(vi.m_profile_tag); m_album_id = VCDUTILS::convert_txt2string(vi.m_albumid.data(), vi.m_albumid.size()); m_number_of_cds = htons(static_cast(vi.m_numberof_cds)); m_cd_number = htons(static_cast(vi.m_cd_number)); } else { success = false; } int orgerrno = 0; if (success) { orgerrno = ferror(fpi); } fclose(fpi); return orgerrno; } const time_t &VcdInfo::get_file_date() const { return m_file_date; } const std::string & VcdInfo::get_id() const { return m_id; } VCDTYPE VcdInfo::get_type() const { return m_type; } std::string VcdInfo::get_type_str() const { return VCDUTILS::get_type_str(m_type); } VCDPROFILETAG VcdInfo::get_profile_tag() const { return m_profile_tag; } std::string VcdInfo::get_profile_tag_str() const { return VCDUTILS::get_profile_tag_str(m_profile_tag); } const std::string & VcdInfo::get_album_id() const { return m_album_id; } int VcdInfo::get_number_of_cds() const { return m_number_of_cds; } int VcdInfo::get_cd_number() const { return m_cd_number; } ffmpegfs-2.50/src/vcd/vcdentries.cc0000664000175000017500000002653015175426212012753 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdentries.cc * @brief S/VCD VcdEntries class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #include "vcdentries.h" #include "vcdutils.h" #include #include #include #define VCD_SECTOR_SIZE 2352 /**< @brief Video CD sector size */ #define VCD_SECTOR_OFFS 24 /**< @brief Video CD sector offset */ #define VCD_SECTOR_DATA 2324 /**< @brief Video CD data sector size */ /** * Sync bytes for a Video CD sector. */ const std::array SYNC = { '\x00', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\x00' }; /** * Sync bytes for a Video CD picture start. */ //static const char PICTURE_START_CODE[4] = { '\x00', '\x00', '\x01', '\x00' }; /** * Sync bytes for a Video CD video stream. */ //static const char VIDEO_STREAM_1[4] = { '\x00', '\x00', '\x01', '\xE0' }; VcdEntries::VcdEntries() { clear(); } void VcdEntries::clear() { m_file_date = -1; m_id.clear(); m_type = VCDTYPE::UNKNOWN; m_profile_tag = VCDPROFILETAG::UNKNOWN; m_chapters.clear(); m_disk_path.clear(); m_duration = 0; } int VcdEntries::load_file(const std::string & path) { FILE * fpi = nullptr; std::string fullname; bool is_vcd = false; clear(); if (!VCDUTILS::locate_file(path, "ENTRIES", fullname, is_vcd)) { return ENOENT; } VCDUTILS::get_directory(path, &m_disk_path); try { VCDENTRY vcdentry; struct stat stbuf; uint32_t num_entries = 0; fpi = fopen(fullname.c_str(), "rb"); if (fpi == nullptr) { throw static_cast(errno); } if (fstat(fileno(fpi), &stbuf) != 0) { throw static_cast(ferror(fpi)); } m_file_date = stbuf.st_mtime; std::memset(&vcdentry, 0, sizeof(vcdentry)); if (fread(reinterpret_cast(&vcdentry), 1, sizeof(vcdentry), fpi) != sizeof(vcdentry)) { throw static_cast(ferror(fpi)); } m_id = VCDUTILS::convert_txt2string(reinterpret_cast(vcdentry.m_ID.data()), vcdentry.m_ID.size()); m_type = static_cast(vcdentry.m_type); m_profile_tag = static_cast(vcdentry.m_profile_tag); num_entries = htons(vcdentry.m_num_entries); m_duration = 0; int sec = BCD2DEC(vcdentry.m_chapter[0].m_msf.m_min) * 60 + BCD2DEC(vcdentry.m_chapter[0].m_msf.m_sec); for (uint32_t chapter_no = 0, total = num_entries; chapter_no < total; chapter_no++) { if (chapter_no && BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_min) * 60 + BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_sec) - sec < 1) { // Skip chapters shorter than 1 second sec = BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_min) * 60 + BCD2DEC(vcdentry.m_chapter[chapter_no].m_msf.m_sec); --num_entries; continue; } VcdChapter chapter(vcdentry.m_chapter[chapter_no], is_vcd); m_chapters.push_back(chapter); } // Calculate durations of all chapters until last. This will be done later as we do not yet know the duration of the stream for (size_t chapter_no = 0; chapter_no < m_chapters.size() - 1; chapter_no++) { VcdChapter & chapter1 = m_chapters[chapter_no]; const VcdChapter & chapter2 = m_chapters[chapter_no + 1]; int64_t chapter_duration = chapter2.get_start_time() - chapter1.get_start_time(); // Chapter duration chapter1.m_duration = chapter_duration; // Total duration m_duration += chapter_duration; } } catch (int orgerrno) { if (fpi != nullptr) { fclose(fpi); } return orgerrno; } fclose(fpi); return scan_chapters(); } int VcdEntries::scan_chapters() { FILE * fpi = nullptr; struct stat stbuf; std::memset(&stbuf, 0, sizeof(stbuf)); if (!m_chapters.size()) { return EIO; // Fail safe only: Should not happen, at least 1 chapter is required. } try { int last_track_no = -1; int64_t first_sync = -1; // Build list of chapters for (size_t chapter_no = 0; chapter_no < m_chapters.size(); chapter_no++) { if (last_track_no != m_chapters[chapter_no].get_track_no()) { std::string fullname; last_track_no = m_chapters[chapter_no].get_track_no(); int orgerrno = VCDUTILS::locate_video(m_disk_path, last_track_no, fullname); if (orgerrno != 0) { throw static_cast(orgerrno); } if (chapter_no) { m_chapters[chapter_no - 1].m_end_pos = static_cast(stbuf.st_size); } if (fpi != nullptr) { fclose(fpi); } fpi = fopen(fullname.c_str(), "rb"); if (fpi == nullptr) { throw static_cast(errno); } if (fstat(fileno(fpi), &stbuf) != 0) { throw static_cast(ferror(fpi)); } // Locate the first sync bytes SEEKRES res = seek_sync(fpi, SYNC); if (res != SEEKRES::FOUND) { throw static_cast(EIO); } first_sync = ftell(fpi) - static_cast(SYNC.size()); } int64_t total_chunks = (stbuf.st_size - first_sync) / VCD_SECTOR_SIZE; int64_t first = 0; int64_t last = total_chunks - 1; int64_t middle = (first + last) / 2; // Locate sector with correct start time while (first <= last) { VcdChapter buffer(m_chapters[chapter_no].get_is_svcd()); long int file_pos = static_cast(first_sync + middle * VCD_SECTOR_SIZE); if (fseek(fpi, file_pos, SEEK_SET)) { throw static_cast(ferror(fpi)); } int orgerrno = buffer.readio(fpi, last_track_no); if (orgerrno) { throw static_cast(orgerrno); } if (buffer < m_chapters[chapter_no]) { first = middle + 1; } else if (buffer == m_chapters[chapter_no]) { m_chapters[chapter_no].m_start_pos = static_cast(file_pos); if (chapter_no) { m_chapters[chapter_no - 1].m_end_pos = static_cast(file_pos); } break; } else { last = middle - 1; } middle = (first + last) / 2; } } { VcdChapter buffer(m_chapters[m_chapters.size() - 1].get_is_svcd()); int64_t total_chunks = (stbuf.st_size - first_sync) / VCD_SECTOR_SIZE; // Read time stamp of last sector if (fseek(fpi, static_cast(first_sync + (total_chunks - 1) * VCD_SECTOR_SIZE), SEEK_SET)) { throw static_cast(ferror(fpi)); } int orgerrno = buffer.readio(fpi, last_track_no); if (orgerrno) { throw static_cast(orgerrno); } VcdChapter & chapter1 = m_chapters[m_chapters.size() - 1]; int64_t chapter_duration = buffer.get_start_time() - chapter1.get_start_time(); // Chapter duration chapter1.m_duration = chapter_duration; // Total duration m_duration += chapter_duration; } } catch (int orgerrno) { if (fpi != nullptr) { fclose(fpi); } return orgerrno; } // End of last chapter m_chapters[m_chapters.size() - 1].m_end_pos = static_cast(stbuf.st_size); if (fpi != nullptr) { fclose(fpi); } return 0; } VcdEntries::SEEKRES VcdEntries::seek_sync(FILE *fpi, const std::array & sync) const { char ch; // Read first char if (fread(&ch, 1, 1, fpi) != 1) { return SEEKRES::NOTFOUND; } for (size_t n = 1; n <= sync.size(); n++) { if (ch != sync[n - 1]) { if (n > 1) { // Restart check n = 0; continue; } n = 0; } if (n == sync.size()) { // Found! break; } if (fread(&ch, 1, 1, fpi) != 1) { return SEEKRES::NOTFOUND; } } return SEEKRES::FOUND; } time_t VcdEntries::get_file_date() const { return m_file_date; } const std::string & VcdEntries::get_id() const { return m_id; } VCDTYPE VcdEntries::get_type() const { return m_type; } std::string VcdEntries::get_type_str() const { return VCDUTILS::get_type_str(m_type); } VCDPROFILETAG VcdEntries::get_profile_tag() const { return m_profile_tag; } std::string VcdEntries::get_profile_tag_str() const { return VCDUTILS::get_profile_tag_str(m_profile_tag); } int VcdEntries::get_number_of_chapters() const { return static_cast(m_chapters.size()); } const VcdChapter *VcdEntries::get_chapter(int chapter_idx) const { if (chapter_idx < 0 || chapter_idx >= get_number_of_chapters()) { return nullptr; } return &m_chapters[static_cast(chapter_idx)]; } int64_t VcdEntries::get_duration() const { return m_duration; } uint64_t VcdEntries::get_size() const { size_t chapters = static_cast(get_number_of_chapters()); if (!chapters) { return 0; } return (m_chapters[chapters - 1].get_end_pos() - m_chapters[0].get_start_pos()); } const std::string & VcdEntries::get_disk_path() const { return m_disk_path; } ffmpegfs-2.50/src/vcd/vcdentries.h0000664000175000017500000001250515175426212012612 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file vcdentries.h * @brief S/VCD VcdEntries class * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2013-2026 Norbert Schlia (nschlia@oblivion-software.de) @n * From BullysPLayer Copyright (C) 1984-2026 by Oblivion Software/Norbert Schlia */ #ifndef VCDENTRIES_H #define VCDENTRIES_H #pragma once #include "vcdchapter.h" #include #include /** @brief Video CD entries */ class VcdEntries { public: /** * Seek results */ enum class SEEKRES { NOTFOUND, /**< @brief Sync not found */ FOUND, /**< @brief Sync found */ ERROR /**< @brief Seek error */ }; typedef SEEKRES *LPSEEKRES; /**< @brief Pointer to SEEKRES */ typedef const SEEKRES *LPCSEEKRES; /**< @brief Const pointer to SEEKRES */ public: /** * @brief Construct #VcdEntries object */ explicit VcdEntries(); /** * @brief Destroy VcdEntries object */ virtual ~VcdEntries() = default; /** * @brief Reset this object */ void clear(); /** * @brief Load VCD from path * @param[in] path - path to locate VCD in * @return On success, returns 0; in case of error returns errno */ int load_file(const std::string & path); /** * @brief Get date of disk (taken from INFO.VCD or SVD). * @return Returns date of disk. */ time_t get_file_date() const; /** * @brief Get disk ID. * @return Returns disk ID. */ const std::string & get_id() const; /** * @brief Get disk type. * @return Returns disk type. */ VCDTYPE get_type() const; /** * @brief Get disk type as string. * @return Returns disk type as string. */ std::string get_type_str() const; /** * @brief Get disk profile tag. * @return Returns disk profile tag. */ VCDPROFILETAG get_profile_tag() const; /** * @brief Get disk profile tag as string. * @return Returns disk profile tag as string. */ std::string get_profile_tag_str() const; /** * @brief Get number of chapters on this disk. * @return Returns number of chapters on this disk. */ int get_number_of_chapters() const; /** * @brief Get chapter object. * @note If a disk is successfully read, at least one chapter is guaranteed to exist. * @param[in] chapter_idx - 0..number of chapters - 1 * @return VcdChapter object with this chapter, nullptr if chapter_idx is invalid. */ const VcdChapter * get_chapter(int chapter_idx) const; /** * @brief Get the total disk duration in AV_TIME_BASE fractional seconds. * @return Returns total disk duration. */ int64_t get_duration() const; /** * @brief Get disk size (DAT/MPEG only). * @return Returns disk size. */ uint64_t get_size() const; /** @brief Get disk directory. * @return Returns disk directory. */ const std::string & get_disk_path() const; protected: /** * @brief Scan the disk for chapters. * @return On success, returns 0; on error, returns errno. */ int scan_chapters(); /** * @brief Seek for sync bytes. * @param[in] fpi - file pointer of open file * @param[in] sync - sync bytes * @return Returns SEEKRES result code. */ SEEKRES seek_sync(FILE *fpi, const std::array & sync) const; protected: // Common data time_t m_file_date; /**< @brief File date */ std::string m_id; /**< @brief ID of CD. */ VCDTYPE m_type; /**< @brief Type of CD. */ VCDPROFILETAG m_profile_tag; /**< @brief System profile tag. */ // ENTRIES.XXX data std::vector m_chapters; /**< @brief VCD chapters */ int64_t m_duration; /**< @brief Total disk duration, in AV_TIME_BASE fractional seconds. */ // misc. std::string m_disk_path; /**< @brief Path to this disk */ }; #endif // VCDENTRIES_H ffmpegfs-2.50/src/fileio.h0000664000175000017500000004110515177713600011137 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file fileio.h * @brief FileIO class * * This class allows transparent access to files from DVD, Blu-ray, Video CD or * to regular disk files. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FILEIO_H #define FILEIO_H #pragma once #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include // Disable annoying warnings outside our code #ifdef __cplusplus extern "C" { #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #pragma pack(push, 1) #define IMAGE_FRAME_TAG "IMGFRAME" /**< @brief Tag of an image frame header for the frame images buffer. */ /** * @brief Image frame header * * This image frame header will always start at an 8K boundary of the cache. * * It can be used to find the next image by seeking an 8K block starting with the tag. */ typedef struct IMAGE_FRAME { std::array m_tag; /**< @brief Start tag, always ascii "IMGFRAME". */ uint32_t m_frame_no; /**< @brief Number of the frame image. 0 if not yet decoded. */ uint64_t m_offset; /**< @brief Offset in index file. */ uint32_t m_size; /**< @brief Image size in bytes. */ std::array m_reserved; /**< @brief Reserved. Pad structure to 32 bytes. */ // ...data } IMAGE_FRAME; #pragma pack(pop) typedef IMAGE_FRAME const *LPCIMAGE_FRAME; /**< @brief Pointer version of IMAGE_FRAME */ typedef IMAGE_FRAME *LPIMAGE_FRAME; /**< @brief Pointer to const version of IMAGE_FRAME */ /** @brief Virtual file types enum */ enum class VIRTUALTYPE { PASSTHROUGH, /**< @brief passthrough file, not used */ DISK, /**< @brief Regular disk file to transcode */ SCRIPT, /**< @brief Virtual script */ #ifdef USE_LIBVCD VCD, /**< @brief Video CD file */ #endif // USE_LIBVCD #ifdef USE_LIBDVD DVD, /**< @brief DVD file */ #endif // USE_LIBDVD #ifdef USE_LIBBLURAY BLURAY, /**< @brief Blu-ray disk file */ #endif // USE_LIBBLURAY BUFFER, /**< @brief Buffer file */ }; typedef VIRTUALTYPE const *LPCVIRTUALTYPE; /**< @brief Pointer version of VIRTUALTYPE */ typedef VIRTUALTYPE LPVIRTUALTYPE; /**< @brief Pointer to const version of VIRTUALTYPE */ #define VIRTUALFLAG_NONE 0x00000000 /**< @brief No flags */ #define VIRTUALFLAG_PASSTHROUGH 0x00000001 /**< @brief passthrough file, not used */ #define VIRTUALFLAG_DIRECTORY 0x00000002 /**< @brief File is a virtual directory */ #define VIRTUALFLAG_FILESET 0x00000004 /**< @brief File is file set (images, HLS) */ #define VIRTUALFLAG_FRAME 0x00000008 /**< @brief File is part of a set of frames */ #define VIRTUALFLAG_HLS 0x00000010 /**< @brief File is part of a set of HLS transport stream (ts) files */ #define VIRTUALFLAG_CUESHEET 0x00000020 /**< @brief File is part of a set of cue sheet tracks or the directory */ #define VIRTUALFLAG_HIDDEN 0x00000040 /**< @brief File is not transcodable or should otherwise show in listings */ /** @brief Virtual file definition */ typedef struct VIRTUALFILE { VIRTUALFILE() : m_type(VIRTUALTYPE::DISK) , m_flags(VIRTUALFLAG_NONE) , m_format_idx(0) , m_full_title(false) , m_duration(0) , m_predicted_size(0) , m_video_frame_count(0) , m_has_audio(false) , m_has_video(false) , m_has_subtitle(false) , m_channels(0) , m_sample_rate(0) , m_width(0) , m_height(0) , m_framerate{ 0, 0 } { std::memset(&m_st, 0, sizeof(m_st)); } uint32_t get_segment_count() const; /**< @brief Number of HLS segments in set */ VIRTUALTYPE m_type; /**< @brief Type of this virtual file */ int m_flags; /**< @brief One of the VIRTUALFLAG_* flags */ size_t m_format_idx; /**< @brief Index into params.format[] array */ std::string m_destfile; /**< @brief Name and path of destination file */ std::string m_virtfile; /**< @brief Name and path of virtual file */ std::string m_origfile; /**< @brief Sanitised name and path of original file */ struct stat m_st; /**< @brief stat structure with size etc. */ bool m_full_title; /**< @brief If true, ignore m_chapter_no and provide full track */ int64_t m_duration; /**< @brief Track/chapter duration, in AV_TIME_BASE fractional seconds. */ size_t m_predicted_size; /**< @brief Use this as the size instead of computing it over and over. */ uint32_t m_video_frame_count; /**< @brief Number of frames in video or 0 if not a video */ bool m_has_audio; /**< @brief True if file has an audio track */ bool m_has_video; /**< @brief True if file has a video track */ bool m_has_subtitle; /**< @brief True if file has a subtitle track */ std::vector m_file_contents; /**< @brief Buffer for virtual files */ #ifdef USE_LIBVCD /** @brief Extra value structure for Video CDs. * @note Only available if compiled with -DUSE_LIBVCD. */ struct VCD_CHAPTER { VCD_CHAPTER() : m_track_no(0) , m_chapter_no(0) , m_start_pos(0) , m_end_pos(0) {} int m_track_no; /**< @brief Track number (1..) */ int m_chapter_no; /**< @brief Chapter number (1..) */ uint64_t m_start_pos; /**< @brief Start offset in bytes */ uint64_t m_end_pos; /**< @brief End offset in bytes (not including this byte) */ } m_vcd; /**< @brief S/VCD track/chapter info */ #endif //USE_LIBVCD #ifdef USE_LIBDVD /** @brief Extra value structure for DVDs. * @note Only available if compiled with -DUSE_LIBDVD. */ struct DVD_CHAPTER { DVD_CHAPTER() : m_title_no(0) , m_chapter_no(0) , m_angle_no(0) {} int m_title_no; /**< @brief Track number (1...n) */ int m_chapter_no; /**< @brief Chapter number (1...n) */ int m_angle_no; /**< @brief Selected angle number (1...n) */ } m_dvd; /**< @brief DVD title/chapter info */ #endif // USE_LIBDVD #ifdef USE_LIBBLURAY /** @brief Extra value structure for Blu-ray disks. * @note Only available if compiled with -DUSE_LIBBLURAY. */ struct BLURAY_CHAPTER { BLURAY_CHAPTER() : m_title_no(0) , m_playlist_no(0) , m_chapter_no(0) , m_angle_no(0) {} uint32_t m_title_no; /**< @brief Track number (1...n) */ uint32_t m_playlist_no; /**< @brief Playlist number (1...n) */ unsigned m_chapter_no; /**< @brief Chapter number (1...n) */ unsigned m_angle_no; /**< @brief Selected angle number (1...n) */ } m_bluray; /**< @brief Blu-ray title/chapter info */ #endif // USE_LIBBLURAY /** @brief Extra value structure for cue sheets. */ struct CUESHEET_TRACK { CUESHEET_TRACK() : m_tracktotal(0) , m_trackno(0) , m_start(0) , m_duration(0) , m_nextfile(nullptr) {} int m_tracktotal; /**< @brief Total number of tracks in cue sheet. */ int m_trackno; /**< @brief Track number */ std::string m_artist; /**< @brief Track artist */ std::string m_title; /**< @brief Track title */ std::string m_album; /**< @brief Album title */ std::string m_genre; /**< @brief Album genre */ std::string m_date; /**< @brief Publishing date */ int64_t m_start; /**< @brief Track start time, in AV_TIME_BASE fractional seconds. */ int64_t m_duration; /**< @brief Track/chapter duration, in AV_TIME_BASE fractional seconds. */ VIRTUALFILE* m_nextfile; /**< @brief Next (probable) file to be played. Used for cuesheet lists. */ } m_cuesheet_track; /**< @brief Cue sheet data for track. */ std::string m_cuesheet; /**< @brief Cue sheet file contents for physical file. */ // These may be filled in for DVD/Blu-ray int m_channels; /**< @brief Audio channels - Filled in for the DVD/Blu-ray directory. */ int m_sample_rate; /**< @brief Audio sample rate - Filled in for the DVD/Blu-ray directory. */ int m_width; /**< @brief Video width - Filled in for the DVD/Blu-ray directory. */ int m_height; /**< @brief Video height - Filled in for the DVD/Blu-ray directory. */ AVRational m_framerate; /**< @brief Video frame rate - Filled in for the DVD/Blu-ray directory. */ } VIRTUALFILE; typedef VIRTUALFILE const *LPCVIRTUALFILE; /**< @brief Pointer to const version of VIRTUALFILE. */ typedef VIRTUALFILE *LPVIRTUALFILE; /**< @brief Pointer version of VIRTUALFILE. */ /** @brief Base class for I/O */ class FileIO { public: /** * @brief Create #FileIO object */ explicit FileIO(); /** * @brief Free #FileIO object */ virtual ~FileIO() = default; /** @brief Allocate the correct object for type(). * * Free with delete if no longer required. * * @param[in] type - VIRTUALTYPE of new object. * @return Upon successful completion, #FileIO of the requested type. * On error, (out of memory), it returns a nullptr. */ static std::shared_ptr alloc(VIRTUALTYPE type); /** * @brief Get type of the virtual file. * @return Returns the type of the virtual file. */ virtual VIRTUALTYPE type() const = 0; /** * @brief Get the ideal buffer size. * @return Return the ideal buffer size. */ virtual size_t bufsize() const = 0; /** @brief Open a virtual file. * @param[in] virtualfile - LPCVIRTUALFILE of file to open. * @return Upon successful completion, #openio() returns 0. @n * On error, a nonzero value is returned and errno is set to indicate the error. */ virtual int openio(LPVIRTUALFILE virtualfile) = 0; /** @brief Read data from a file. * @param[out] data - A buffer to store read bytes in. It must be large enough to hold up to size bytes. * @param[in] size - The number of bytes to read. * @return Upon successful completion, #readio() returns the number of bytes read. @n * This may be less than size. @n * On error, the value 0 is returned and errno is set to indicate the error. @n * If at the end of the file, 0 may be returned but errno is not set. error() will return 0 if at EOF. */ virtual size_t readio(void *data, size_t size) = 0; /** * @brief Get last error. * @return errno value of last error. */ virtual int error() const = 0; /** @brief Get the duration of the file, in AV_TIME_BASE fractional seconds. * * This is only possible for file formats that are aware of the play time. * May be AV_NOPTS_VALUE if the time is not known. */ virtual int64_t duration() const = 0; /** * @brief Get the file size. * @return Returns the file size. */ virtual size_t size() const = 0; /** * @brief Get current read position. * @return Gets the current read position. */ virtual size_t tell() const = 0; /** @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. @n * On error, the value -1 is returned and errno is set to indicate the error. */ virtual int seek(int64_t offset, int whence) = 0; /** * @brief Check if at end of file. * @return Returns true if at end of file. */ virtual bool eof() const = 0; /** * @brief Close virtual file. */ virtual void closeio() = 0; /** * @brief Get virtual file object * @return Current virtual file object or nullptr if unset. */ LPVIRTUALFILE virtualfile(); /** * @brief Get source filename. * @return Returns source filename. */ const std::string & filename() const; /** * @brief Path to source file (without file name) * @return Returns path to source file. */ const std::string & path() const; protected: /** @brief Set the virtual file object. * @param[in] virtualfile - LPCVIRTUALFILE of file to set. */ void set_virtualfile(LPVIRTUALFILE virtualfile); private: std::string m_path; /**< @brief Source path (directory without file name) */ LPVIRTUALFILE m_virtualfile; /**< @brief Virtual file object of current file */ }; #endif // FILEIO_H ffmpegfs-2.50/src/ffmpeg_formatcontext.h0000664000175000017500000001267515200152616014112 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_formatcontext.h * @brief FFmpeg AVFormatContext RAII wrapper. * * @ingroup ffmpegfs */ #ifndef FFMPEG_FORMATCONTEXT_H #define FFMPEG_FORMATCONTEXT_H #pragma once #include "ffmpeg_utils.h" /** * @brief RAII wrapper for AVFormatContext. * * Owns the AVFormatContext and releases it with the correct FFmpeg function. * Input contexts are closed with avformat_close_input(), output contexts are * released with avformat_free_context(). Custom AVIO contexts allocated with * avio_alloc_context() are freed before the format context is released. */ class FFmpeg_FormatContext { public: /** * @brief Managed AVFormatContext direction. */ enum class TYPE { INPUT, /**< @brief Input context closed with avformat_close_input(). */ OUTPUT /**< @brief Output context freed with avformat_free_context(). */ }; /** * @brief Construct an empty format-context wrapper. * @param[in] type Initial context direction used when resetting/freeing. */ explicit FFmpeg_FormatContext(TYPE type = TYPE::INPUT); /** * @brief Release the owned format context, if any. */ ~FFmpeg_FormatContext(); FFmpeg_FormatContext(const FFmpeg_FormatContext&) = delete; FFmpeg_FormatContext& operator=(const FFmpeg_FormatContext&) = delete; /** * @brief Move-construct a format-context wrapper. * @param[in,out] context Source wrapper whose context ownership is transferred. */ FFmpeg_FormatContext(FFmpeg_FormatContext&& context) noexcept; /** * @brief Move-assign a format-context wrapper. * @param[in,out] context Source wrapper whose context ownership is transferred. * @return Reference to this wrapper. */ FFmpeg_FormatContext& operator=(FFmpeg_FormatContext&& context) noexcept; /** * @brief Allocate a new input format context. * @return 0 on success, or a negative FFmpeg error code. */ int alloc_input_context(); /** * @brief Allocate a new output format context. * @param[in] format_name Output container format name, or nullptr for automatic selection. * @param[in] filename Optional output file name used by FFmpeg for format probing. * @return 0 on success, or a negative FFmpeg error code. */ int alloc_output_context(const char *format_name, const char *filename = nullptr); /** * @brief Set the context direction used for later cleanup. * @param[in] type Input or output context direction. */ void set_type(TYPE type); /** * @brief Mark whether the context owns a custom AVIOContext. * @param[in] custom_io true if pb was allocated with avio_alloc_context(). */ void set_custom_io(bool custom_io = true); /** * @brief Free the owned format context and reset the wrapper to empty. * @return true if a context or custom IO object was released, false otherwise. */ bool reset(); /** * @brief Check whether the wrapper currently owns a format context. * @return true if no format context is owned, false otherwise. */ bool empty() const; /** * @brief Get the owned FFmpeg format context pointer. * @return Mutable AVFormatContext pointer, or nullptr if empty. */ AVFormatContext* get(); /** * @brief Get the owned FFmpeg format context pointer. * @return Const AVFormatContext pointer, or nullptr if empty. */ const AVFormatContext* get() const; /** * @brief Get a writable pointer-to-pointer for FFmpeg APIs. * * Any currently owned context is released first so that FFmpeg can write a * fresh context pointer without leaking the previous one. * * @return Address of the managed context pointer. */ AVFormatContext** address(); /** * @brief Release ownership without freeing the format context. * @return Previously owned AVFormatContext pointer, or nullptr if empty. */ AVFormatContext* release(); /** * @brief Convert to the underlying mutable format context pointer. */ operator AVFormatContext*(); /** * @brief Convert to the underlying const format context pointer. */ operator const AVFormatContext*() const; /** * @brief Access members of the underlying mutable format context. * @return Mutable AVFormatContext pointer. */ AVFormatContext* operator->(); /** * @brief Access members of the underlying const format context. * @return Const AVFormatContext pointer. */ const AVFormatContext* operator->() const; private: /** * @brief Free the custom AVIOContext attached to the format context. */ void free_custom_io(); private: AVFormatContext * m_context; /**< @brief Pointer to underlying AVFormatContext. */ TYPE m_type; /**< @brief Input or output context. */ bool m_custom_io; /**< @brief true if pb was allocated with avio_alloc_context(). */ }; #endif // FFMPEG_FORMATCONTEXT_H ffmpegfs-2.50/src/Makefile.am0000664000175000017500000000523315202053317011544 EXTRA_DIST = $(wildcard scripts/*) # Add fuse3 AM_CPPFLAGS = $(fuse3_CFLAGS) -D_FILE_OFFSET_BITS=64 AM_CFLAGS = $(PERFTOOLS_CFLAGS) AM_CXXFLAGS = $(PERFTOOLS_CXXFLAGS) bin_PROGRAMS = ffmpegfs ffmpegfs_SOURCES = ffmpegfs.cc ffmpegfs.h fuseops.cc transcode.cc transcode.h cache.cc cache.h buffer.cc buffer.h logging.cc logging.h cache_entry.cc cache_entry.h cache_maintenance.cc cache_maintenance.h id3v1tag.h aiff.h wave.h cuesheetparser.cc cuesheetparser.h diskio.cc diskio.h fileio.cc fileio.h ffmpeg_compat.h ffmpeg_profiles.h ffmpeg_audiofifo.h ffmpeg_dictionary.h ffmpeg_packet.h ffmpeg_swrcontext.h ffmpeg_swscontext.h thread_pool.cc thread_pool.h ffmpeg_formatcontext.cc ffmpegfs_LDADD = $(libcue_LIBS) $(fuse3_LIBS) -lrt -lstdc++fs ffmpegfs_LDADD += $(PERFTOOLS_LIBS) ffmpegfs_SOURCES += ffmpeg_base.cc ffmpeg_base.h ffmpeg_transcoder.cc ffmpeg_transcoder.h ffmpeg_utils.cc ffmpeg_utils.h ffmpeg_profiles.cc ffmpeg_audiofifo.cc ffmpeg_dictionary.cc ffmpeg_packet.cc ffmpeg_swrcontext.cc ffmpeg_swscontext.cc ffmpeg_frame.h ffmpeg_frame.cc ffmpeg_subtitle.h ffmpeg_subtitle.cc ffmpeg_formatcontext.h ffmpegfs_LDADD += $(libavcodec_LIBS) $(libavutil_LIBS) $(libavformat_LIBS) $(libswscale_LIBS) $(libavfilter_LIBS) $(libswresample_LIBS) AM_CPPFLAGS += $(libavcodec_CFLAGS) $(libavutil_CFLAGS) $(libavformat_CFLAGS) $(libswscale_CFLAGS) $(libavfilter_CFLAGS) $(libswresample_CFLAGS) # Add sqlite3 ffmpegfs_LDADD += $(sqlite3_LIBS) # Add libchardet AM_CPPFLAGS += $(chardet_CFLAGS) ffmpegfs_LDADD += $(chardet_LIBS) # DVD support: requires both libdbdnav and libdvdread if USE_LIBDVD AM_CPPFLAGS += -DUSE_LIBDVD AM_CPPFLAGS += $(libdvdnav_CFLAGS) $(libdvdread_CFLAGS) ffmpegfs_SOURCES += dvdio.cc dvdio.h dvdparser.cc dvdparser.h ffmpegfs_LDADD += $(libdvdnav_LIBS) $(libdvdread_LIBS) endif # Blu-ray support: requires libbluray if USE_LIBBLURAY AM_CPPFLAGS += -DUSE_LIBBLURAY AM_CPPFLAGS += $(libbluray_CFLAGS) ffmpegfs_SOURCES += blurayio.cc blurayio.h blurayparser.cc blurayparser.h ffmpegfs_LDADD += $(libbluray_LIBS) endif # VCD support: uses internal code if USE_LIBVCD AM_CPPFLAGS += -DUSE_LIBVCD AM_CPPFLAGS += $(libvcd_CFLAGS) ffmpegfs_SOURCES += vcdio.cc vcdio.h vcdparser.cc vcdparser.h vcd/vcdchapter.cc vcd/vcdchapter.h vcd/vcdentries.cc vcd/vcdentries.h vcd/vcdinfo.cc vcd/vcdinfo.h vcd/vcdutils.cc vcd/vcdutils.h endif # Add conversion of manpages source. Will be used in binary. BUILT_SOURCES = ../ffmpegfs.1.text ffmpegfshelp.h ../ffmpegfs.1.text: ../ffmpegfs.1.txt $(AM_V_GEN)a2x -a revnumber="$(VERSION)" \ -a revdate="$(shell date +'%B %Y')" -D ".." -f text $< # sed in makefiles is a menace so we use a helper script ffmpegfshelp.h: ../ffmpegfs.1.text $(srcdir)/makehelp.sh $@ ffmpegfs-2.50/src/blurayio.cc0000664000175000017500000001571315177713600011662 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifdef USE_LIBBLURAY /** * @file blurayio.cc * @brief BlurayIO class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "blurayio.h" #include "logging.h" #include BlurayIO::BlurayIO() : m_bd(nullptr) , m_is_eof(false) , m_errno(0) , m_rest_size(0) , m_rest_pos(0) , m_cur_pos(0) , m_start_pos(0) , m_end_pos(AV_NOPTS_VALUE) , m_full_title(false) , m_title_idx(0) , m_chapter_idx(0) , m_angle_idx(0) , m_duration(AV_NOPTS_VALUE) { m_data.fill(0); } BlurayIO::~BlurayIO() { pvt_close(); } VIRTUALTYPE BlurayIO::type() const { return VIRTUALTYPE::BLURAY; } size_t BlurayIO::bufsize() const { return sizeof(m_data); } int BlurayIO::openio(LPVIRTUALFILE virtualfile) { uint32_t title_count; uint32_t chapter_end; BLURAY_TITLE_INFO *ti; set_virtualfile(virtualfile); if (virtualfile != nullptr) { m_full_title = virtualfile->m_full_title; m_title_idx = virtualfile->m_bluray.m_title_no - 1; m_chapter_idx = virtualfile->m_bluray.m_chapter_no - 1; m_angle_idx = virtualfile->m_bluray.m_angle_no - 1; m_duration = virtualfile->m_duration; } else { m_full_title = false; m_title_idx = 0; m_chapter_idx = 0; m_angle_idx = 0; m_duration = AV_NOPTS_VALUE; } chapter_end = m_chapter_idx + 1; Logging::debug(path(), "Opening input Blu-ray."); m_bd = bd_open(path().c_str(), nullptr); if (m_bd == nullptr) { Logging::error(path(), "Failed to open disc."); return 1; } title_count = bd_get_titles(m_bd, TITLES_RELEVANT, 0); if (title_count == 0) { Logging::error(path(), "There were no titles found."); return 1; } if (!bd_select_title(m_bd, m_title_idx)) { Logging::error(path(), "The Blu-ray title no. %1 could not be opened.", m_title_idx); return 1; } ti = bd_get_title_info(m_bd, m_title_idx, m_angle_idx); if (m_angle_idx >= ti->angle_count) { Logging::warning(path(), "The angle %1 is greater than the angle count %2. Using angle 1.", m_angle_idx + 1, ti->angle_count); m_angle_idx = 0; } bd_select_angle(m_bd, m_angle_idx); if (m_chapter_idx >= ti->chapter_count) { Logging::error(path(), "The first chapter %1 is greater than the chapter count %2.", m_chapter_idx + 1, ti->chapter_count); return 1; } if (chapter_end >= ti->chapter_count) { chapter_end = 0; } if (chapter_end > 0 && !m_full_title) { m_end_pos = bd_chapter_pos(m_bd, chapter_end) - 1; } else { m_end_pos = static_cast(bd_get_title_size(m_bd)); } if (m_full_title) { m_duration = static_cast(ti->duration * AV_TIME_BASE / 90000); } else { BLURAY_TITLE_CHAPTER *chapter = &ti->chapters[m_chapter_idx]; m_duration = static_cast(chapter->duration * AV_TIME_BASE / 90000); } bd_free_title_info(ti); m_start_pos = bd_seek_chapter(m_bd, m_chapter_idx); m_rest_size = 0; m_rest_pos = 0; return 0; } size_t BlurayIO::readio(void * data, size_t size) { size_t result_len = 0; if (m_rest_size) { result_len = m_rest_size; if (m_rest_size > size) { errno = EINVAL; return 0; } std::memcpy(data, &m_data[m_rest_pos], m_rest_size); m_rest_size = m_rest_pos = 0; return result_len; } m_cur_pos = static_cast(bd_tell(m_bd)); if (m_end_pos < 0 || m_cur_pos < m_end_pos) { int maxsize = sizeof(m_data); if (maxsize > (m_end_pos - m_cur_pos)) { maxsize = static_cast(m_end_pos - m_cur_pos); } int res = bd_read(m_bd, m_data.data(), maxsize); if (res < 0) { Logging::error(path(), "bd_read has failed."); return 0; } size_t bytes = static_cast(res); m_cur_pos = static_cast(bd_tell(m_bd)); if (bytes > size) { result_len = size; std::memcpy(data, m_data.data(), result_len); m_rest_size = bytes - size; m_rest_pos = size; } else { result_len = bytes; std::memcpy(data, m_data.data(), result_len); } } return result_len; } int BlurayIO::error() const { return m_errno; } int64_t BlurayIO::duration() const { return m_duration; } size_t BlurayIO::size() const { return static_cast(m_end_pos - m_start_pos); } size_t BlurayIO::tell() const { return static_cast(static_cast(bd_tell(m_bd)) - m_start_pos); } int BlurayIO::seek(int64_t offset, int whence) { int64_t seek_pos; switch (whence) { case SEEK_SET: { seek_pos = m_start_pos + offset; break; } case SEEK_CUR: { seek_pos = m_start_pos + offset + static_cast(bd_tell(m_bd)); break; } case SEEK_END: { seek_pos = m_end_pos + offset; break; } default: { errno = EINVAL; return (EOF); } } if (seek_pos > m_end_pos) { m_cur_pos = m_end_pos; // Cannot go beyond EOF. Set position to end, leave errno untouched. return 0; } if (seek_pos < 0) // Cannot go before head, set errno. { errno = EINVAL; return (EOF); } m_cur_pos = bd_seek(m_bd, static_cast(seek_pos)); return (m_cur_pos == seek_pos ? 0 : -1); } bool BlurayIO::eof() const { return (m_cur_pos >= m_end_pos); } void BlurayIO::closeio() { pvt_close(); } void BlurayIO::pvt_close() { BLURAY * bd = m_bd; if (bd != nullptr) { m_bd = nullptr; bd_close(bd); } } #endif // USE_LIBBLURAY ffmpegfs-2.50/src/thread_pool.h0000664000175000017500000001045515177713600012174 /* * Copyright (C) 2019-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file thread_pool.h * @brief Thread pool class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2019-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef THREAD_POOL_H #define THREAD_POOL_H #pragma once #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include /** * @brief The thread_pool class. */ class thread_pool { public: typedef std::function FunctionPointer; /**< @brief Pointer to thread pool function */ public: /** * @brief Construct a thread_pool object. * @param[in] num_threads - Optional: number of threads to create in pool. Defaults to Defaults to 4 x number of CPU cores. */ explicit thread_pool(unsigned int num_threads = std::thread::hardware_concurrency() * 4); /** * @brief Object destructor. Ends all threads and cleans up resources. */ virtual ~thread_pool(); /** * @brief Initialise thread pool. * Initialise the thread pool. Does nothing if called more than once. * @param[in] num_threads - Optional: number of threads to create in pool. Defaults to Defaults to 4x number of CPU cores. * @return Number of threads created on success; on error or if called more than once, returns 0. */ int init(unsigned int num_threads = 0); /** * @brief Shut down the thread pool. * @param[in] silent - If true, no log messages will be issued. */ void tear_down(bool silent = false); /** * @brief Schedule a new thread from pool. * @param[in] func - std::function object to call * @return Returns true if thread was successfully scheduled, false if not. */ bool schedule_thread(FunctionPointer && func); /** * @brief Get number of currently running threads. * @return Returns number of currently running threads. */ unsigned int current_running() const; /** * @brief Get number of currently queued threads. * @return Returns number of currently queued threads. */ unsigned int current_queued(); /** * @brief Get current pool size. * @return Return current pool size. */ unsigned int pool_size() const; private: /** * @brief Start loop function. * @param[in] tp - Thread pool object of caller. */ static void loop_function_starter(thread_pool &tp); /** * @brief Start loop function */ void loop_function(); protected: std::vector m_thread_pool; /**< Thread pool */ std::mutex m_queue_mutex; /**< Mutex for critical section */ std::condition_variable m_queue_cond; /**< Condition for critical section */ std::queue m_thread_queue; /**< Thread queue parameters */ std::atomic_bool m_queue_shutdown; /**< If true all threads have been shut down */ unsigned int m_num_threads; /**< Max. number of threads. Defaults to 4x number of CPU cores. */ unsigned int m_cur_threads; /**< Current number of threads. */ std::atomic_uint32_t m_threads_running; /**< Currently running threads. */ }; #endif // THREAD_POOL_H ffmpegfs-2.50/src/vcdparser.cc0000664000175000017500000001567315177713600012032 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifdef USE_LIBVCD /** * @file vcdparser.cc * @brief Video/Super Video CD parser implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "ffmpegfs.h" #include "vcdparser.h" #include "logging.h" #include "vcd/vcdentries.h" extern "C" { #include } static int parse_vcd(const std::string & path, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler); static bool create_vcd_virtualfile(const VcdEntries &vcd, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler, bool full_title, int chapter_no); /** * @brief Create a virtual file for a video CD. * @param[in] vcd - Video CD handle. * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @param[in] full_title - If true, create virtual file of all title. If false, include single chapter only. * @param[in] chapter_no - Chapter number of virtual file. * @return Returns true if successful. Returns false on error. */ static bool create_vcd_virtualfile(const VcdEntries & vcd, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler, bool full_title, int chapter_no) { const VcdChapter * chapter1 = vcd.get_chapter(chapter_no); std::string title_buf; size_t size; int64_t duration; if (!full_title) { size = static_cast(chapter1->get_size()); duration = chapter1->get_duration(); strsprintf(&title_buf, "%02d. Chapter %03d [%s].%s", chapter1->get_track_no(), chapter_no + 1, replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); // can safely assume this a video } else { size = static_cast(vcd.get_size()); duration = vcd.get_duration(); strsprintf(&title_buf, "%02d. Title [%s].%s", chapter1->get_track_no(), replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); // can safely assume this a video } LPVIRTUALFILE virtualfile = nullptr; if (!ffmpeg_format[FORMAT::VIDEO].is_multiformat()) { virtualfile = insert_file(VIRTUALTYPE::VCD, vcd.get_disk_path() + title_buf, statbuf); } else { virtualfile = insert_dir(VIRTUALTYPE::VCD, vcd.get_disk_path() + title_buf, statbuf); } if (virtualfile == nullptr) { Logging::error(vcd.get_disk_path(), "Failed to create virtual path: %1", (vcd.get_disk_path() + title_buf).c_str()); errno = EIO; return false; } if (add_fuse_entry(buf, filler, title_buf, &virtualfile->m_st, 0)) { // break; } // Video CD is video format anyway virtualfile->m_format_idx = 0; // Mark title/chapter/angle virtualfile->m_full_title = full_title; virtualfile->m_vcd.m_track_no = chapter1->get_track_no(); virtualfile->m_vcd.m_chapter_no = chapter_no; virtualfile->m_vcd.m_start_pos = chapter1->get_start_pos(); if (!full_title) { virtualfile->m_vcd.m_end_pos = chapter1->get_end_pos(); } else { virtualfile->m_vcd.m_end_pos = size; } virtualfile->m_duration = duration; AVRational framerate = av_make_q(25000, 1000); //*** @todo check disk which framerate is correct, can be 25 or 29.996 fps! virtualfile->m_video_frame_count = static_cast(av_rescale_q(duration, av_get_time_base_q(), av_inv_q(framerate))); virtualfile->m_predicted_size = size; return true; } /** * @brief Parse VCD directory and get all VCD chapters as virtual files. * @param[in] path - Path to check. * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @return On success, returns number of chapters found. On error, returns -errno. */ static int parse_vcd(const std::string & path, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler) { VcdEntries vcd; bool success = true; vcd.load_file(path); Logging::debug(path, "Parsing the Video CD."); for (int chapter_no = 0; chapter_no < vcd.get_number_of_chapters() && success; chapter_no++) { success = create_vcd_virtualfile(vcd, statbuf, buf, filler, false, chapter_no); } if (success && vcd.get_number_of_chapters() > 1) { success = create_vcd_virtualfile(vcd, statbuf, buf, filler, true, 0); } if (success) { return vcd.get_number_of_chapters(); } else { return -errno; } } int check_vcd(const std::string & path, void *buf, fuse_fill_dir_t filler) { std::string _path(path); struct stat stbuf; int res = 0; append_sep(&_path); if (stat((_path + "SVCD/INFO.SVD").c_str(), &stbuf) == 0) { if (!check_path(_path)) { Logging::trace(_path, "SVCD detected."); res = parse_vcd(_path, &stbuf, buf, filler); Logging::trace(nullptr, "%1 titles were discovered.", res); } else { res = load_path(_path, &stbuf, buf, filler); } add_dotdot(buf, filler, &stbuf, 0); } else if (stat((_path + "VCD/INFO.VCD").c_str(), &stbuf) == 0) { if (!check_path(_path)) { Logging::trace(_path, "VCD detected."); res = parse_vcd(_path, &stbuf, buf, filler); Logging::trace(nullptr, "%1 titles were discovered.", res); } else { res = load_path(_path, &stbuf, buf, filler); } add_dotdot(buf, filler, &stbuf, 0); } return res; } #endif // USE_LIBVCD ffmpegfs-2.50/src/thread_pool.cc0000664000175000017500000001033215177713600012324 /* * Copyright (C) 2019-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file thread_pool.cc * @brief Thread pool class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2019-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "thread_pool.h" #include "logging.h" #include "config.h" thread_pool::thread_pool(unsigned int num_threads) : m_queue_shutdown(false) , m_num_threads(num_threads) , m_cur_threads(0) , m_threads_running(0) { } thread_pool::~thread_pool() { tear_down(true); } void thread_pool::loop_function_starter(thread_pool & tp) { tp.loop_function(); } void thread_pool::loop_function() { unsigned int thread_no = ++m_cur_threads; while (true) { FunctionPointer info; { std::unique_lock lock_queue_mutex(m_queue_mutex); m_queue_cond.wait(lock_queue_mutex, [this]{ return (!m_thread_queue.empty() || m_queue_shutdown); }); if (m_queue_shutdown) { break; } Logging::trace(nullptr, "Starting job taking pool thread no. %1 with id 0x%<" FFMPEGFS_FORMAT_PTHREAD_T ">2.", thread_no, pthread_self()); info = m_thread_queue.front(); m_thread_queue.pop(); } int ret = info(); Logging::trace(nullptr, "The job using pool thread no. %1 with id 0x%<" FFMPEGFS_FORMAT_PTHREAD_T ">2 has exited with return code %3.", thread_no, pthread_self(), ret); } } bool thread_pool::schedule_thread(FunctionPointer &&func) { if (!m_queue_shutdown) { Logging::trace(nullptr, "Queuing up a new thread. There are %1 threads in the pool.", m_thread_pool.size()); { std::lock_guard lock_queue_mutex(m_queue_mutex); m_thread_queue.push(func); } m_queue_cond.notify_one(); return true; } else { return false; } } unsigned int thread_pool::current_running() const { return m_threads_running; } unsigned int thread_pool::current_queued() { std::lock_guard lock_queue_mutex(m_queue_mutex); return static_cast(m_thread_queue.size()); } unsigned int thread_pool::pool_size() const { return static_cast(m_thread_pool.size()); } int thread_pool::init(unsigned int num_threads /*= 0*/) { if (!m_thread_pool.empty()) { Logging::warning(nullptr, "The thread pool already initialised"); return 0; } if (num_threads) { m_num_threads = num_threads; } Logging::info(nullptr, "The thread pool is being initialised with a maximum of %1 threads.", m_num_threads); for(unsigned int i = 0; i < m_num_threads; i++) { m_thread_pool.emplace_back(std::thread(&thread_pool::loop_function_starter, std::ref(*this))); } return static_cast(m_thread_pool.size()); } void thread_pool::tear_down(bool silent) { if (!silent) { Logging::debug(nullptr, "Tearing down the thread pool. There are %1 threads still in the pool.", m_thread_queue.size()); } { std::lock_guard lk(m_queue_mutex); m_queue_shutdown = true; } m_queue_cond.notify_all(); while (!m_thread_pool.empty()) { m_thread_pool.back().join(); m_thread_pool.pop_back(); } } ffmpegfs-2.50/src/ffmpeg_swrcontext.cc0000664000175000017500000000310115177713600013564 /* * This file is part of FFmpegfs. * * FFmpegfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ #include "ffmpeg_swrcontext.h" extern "C" { #include } FFmpeg_SwrContext::FFmpeg_SwrContext() : m_ctx(nullptr) { } FFmpeg_SwrContext::FFmpeg_SwrContext(SwrContext *ctx) : m_ctx(ctx) { } FFmpeg_SwrContext::~FFmpeg_SwrContext() { reset(); } FFmpeg_SwrContext::FFmpeg_SwrContext(FFmpeg_SwrContext&& ctx) noexcept : m_ctx(ctx.release()) { } FFmpeg_SwrContext& FFmpeg_SwrContext::operator=(FFmpeg_SwrContext&& ctx) noexcept { if (this != &ctx) { reset(ctx.release()); } return *this; } SwrContext *FFmpeg_SwrContext::get() const { return m_ctx; } SwrContext **FFmpeg_SwrContext::address() { reset(); return &m_ctx; } SwrContext *FFmpeg_SwrContext::release() { SwrContext *ctx = m_ctx; m_ctx = nullptr; return ctx; } bool FFmpeg_SwrContext::reset(SwrContext *ctx) { bool closed = false; if (m_ctx != nullptr) { swr_free(&m_ctx); closed = true; } m_ctx = ctx; return closed; } bool FFmpeg_SwrContext::empty() const { return m_ctx == nullptr; } FFmpeg_SwrContext::operator bool() const { return m_ctx != nullptr; } FFmpeg_SwrContext::operator SwrContext *() const { return m_ctx; } SwrContext *FFmpeg_SwrContext::operator->() const { return m_ctx; } ffmpegfs-2.50/src/ffmpeg_profiles.h0000664000175000017500000001007715177713600013043 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_profiles.h * @brief FFmpeg encoder profiles * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_PROFILES_H #define FFMPEG_PROFILES_H #pragma once #include "ffmpeg_utils.h" /** * @brief The #FFmpeg_Profiles class */ class FFmpeg_Profiles { public: #define OPT_ALL 0x00000000 /**< @brief All files */ #define OPT_AUDIO 0x00000001 /**< @brief For audio only files */ #define OPT_VIDEO 0x00000002 /**< @brief For videos (not audio only) */ #define OPT_SW_ONLY 0x00000004 /**< @brief Use this option for software encoding only */ #define OPT_HW_ONLY 0x00000008 /**< @brief Use this option for hardware encoding only */ typedef struct PROFILE_OPTION /**< @brief Profiles options */ { const char * m_key; /**< @brief Key, see av_opt_set() and av_dict_set() FFmpeg API function */ const char * m_value; /**< @brief Value, see av_opt_set() and av_dict_set() FFmpeg API function */ const int m_flags; /**< @brief Flags, see av_opt_set() and av_dict_set() FFmpeg API function */ const int m_options; /**< @brief One of the OPT_* flags */ } PROFILE_OPTION; /**< @brief Profile option */ typedef PROFILE_OPTION * LPPROFILE_OPTION; /**< @brief Pointer version of PROFILE_OPTION */ typedef PROFILE_OPTION const * LPCPROFILE_OPTION; /**< @brief Pointer to const version of PROFILE_OPTION */ typedef std::vector PROFILE_OPTION_VEC; /**< @brief PROFILE_OPTION array */ typedef struct PROFILE_LIST /**< @brief List of profiles */ { FILETYPE m_filetype; /**< @brief File type this option set is for */ PROFILE m_profile; /**< @brief One of PROFILE_ */ const PROFILE_OPTION_VEC m_option_codec; /**< @brief av_opt_set() options */ const PROFILE_OPTION_VEC m_option_format; /**< @brief av_dict_set() options */ } PROFILE_LIST; /**< @brief Profile list */ typedef PROFILE_LIST * LPPROFILE_LIST; /**< @brief Pointer version of PROFILE_LIST */ typedef PROFILE_LIST const * LPCPROFILE_LIST; /**< @brief Pointer to const version of PROFILE_LIST */ typedef std::vector PROFILE_LIST_VEC; /**< @brief PROFILE_LIST array */ protected: /** * @brief Construct a FFmpeg_Profiles object. */ explicit FFmpeg_Profiles() = default; /** * @brief Destruct a FFmpeg_Profiles object. */ virtual ~FFmpeg_Profiles() = default; protected: static const PROFILE_LIST_VEC m_profile; /**< @brief List of profile options */ }; #endif // FFMPEG_PROFILES_H ffmpegfs-2.50/src/transcode.h0000664000175000017500000002047715215723104011654 /* * Copyright (C) 2006-2008 David Collett @n * Copyright (C) 2008-2013 K. Henriksson @n * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file transcode.h * @brief File transcoder interface (for use with by FUSE) * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2006-2008 David Collett @n * Copyright (C) 2008-2013 K. Henriksson @n * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef TRANSCODE_H #define TRANSCODE_H #pragma once #include "ffmpegfs.h" #include "fileio.h" /** * @brief Fill a stat buffer with the cached or predicted transcoded file size. * * Uses the encoded size when the file has already been transcoded. If no * encoded size is known yet, the predicted transcoded size is used instead. * * @param[in] virtualfile Virtual file whose cached size should be queried. * @param[in,out] stbuf Stat buffer whose size field is updated on success. * @return Returns @c true when a cached or predicted size was available; * otherwise @c false and @p stbuf is left unchanged. */ bool transcoder_cached_filesize(LPVIRTUALFILE virtualfile, struct stat *stbuf); /** * @brief Calculate and store the predicted transcoded file size. * * Estimates the output size from stream duration, selected output format, * audio parameters, video parameters, and container overhead. The result is * stored both in the cache entry and in the virtual file. * * @param[in,out] virtualfile Virtual file whose predicted size should be set. * @param[in] duration Media duration in FFmpeg time base units. * @param[in] audio_bit_rate Target audio bit rate in bits per second. * @param[in] channels Number of audio channels. * @param[in] sample_rate Audio sample rate in Hz. * @param[in] sample_format Audio sample format. * @param[in] video_bit_rate Target video bit rate in bits per second. * @param[in] width Video width in pixels. * @param[in] height Video height in pixels. * @param[in] interleaved Set to @c true for interleaved video output. * @param[in] framerate Video frame rate. * @return Returns @c true after updating the prediction; otherwise @c false. */ bool transcoder_set_filesize(LPVIRTUALFILE virtualfile, int64_t duration, BITRATE audio_bit_rate, int channels, int sample_rate, AVSampleFormat sample_format, BITRATE video_bit_rate, int width, int height, bool interleaved, const AVRational & framerate); /** * @brief Probe an input file and update cached media properties. * * Opens the source with FFmpeg, reads the predicted transcoded size, duration, * video frame count, and HLS/frame-set segment count, then stores those values * in the cache entry when one was supplied. * * @param[in,out] virtualfile Virtual file to inspect. * @param[in,out] cache_entry Optional cache entry to update with probed values. * @return Returns @c true when probing succeeded; otherwise @c false. */ bool transcoder_predict_filesize(LPVIRTUALFILE virtualfile, Cache_Entry* cache_entry = nullptr); // Functions for doing transcoding, called by main program body /** * @brief Open or create a cache entry for a virtual file. * * Opens the cache entry, refreshes cached metadata, clears outdated cache data, * optionally starts transcoding immediately, or otherwise probes enough source * information to provide predicted size and segment metadata. * * @param[in,out] virtualfile Virtual file represented by the cache entry. * @param[in] begin_transcode Start the transcoder worker immediately when @c true. * @return Pointer to the opened cache entry, or @c nullptr on error with @c errno set. */ Cache_Entry* transcoder_new(LPVIRTUALFILE virtualfile, bool begin_transcode); /** * @brief Read bytes from the cache buffer, starting transcoding if needed. * * Handles normal file reads and HLS segment reads. Missing HLS segments, * stale cache files, and incomplete partial HLS caches are repaired on demand. * The function waits until the requested byte range is available, then copies * as much data as possible into the caller-provided buffer. * * @param[in,out] cache_entry Cache entry to read from. * @param[out] buff Destination buffer. Must be large enough for @p len bytes. * @param[in] offset Byte offset within the requested cache item. * @param[in] len Maximum number of bytes to read. * @param[out] bytes_read Receives the number of bytes copied to @p buff. * @param[in] segment_no HLS segment number, or 0 for a normal single-output file. * @return Returns @c true on success; otherwise @c false with @c errno set. */ bool transcoder_read(Cache_Entry* cache_entry, char* buff, size_t offset, size_t len, int *bytes_read, uint32_t segment_no); /** * @brief Read one frame from a frame-set cache. * * Reads the requested frame directly from the frame-set cache when available. * If the frame is missing, the requested frame number is stacked and the * transcoder is started or restarted to generate it. The virtual file stat * size is updated to the materialised frame size. * * @param[in,out] cache_entry Cache entry containing the frame set. * @param[out] buff Destination buffer. Must be large enough for @p len bytes. * @param[in] offset Byte offset within the requested frame. * @param[in] len Maximum number of bytes to read. * @param[in] frame_no Frame number to read. * @param[out] bytes_read Receives the number of bytes copied to @p buff. * @param[in,out] virtualfile Virtual frame file whose stat size is updated. * @return Returns @c true on success; otherwise @c false with @c errno set. */ bool transcoder_read_frame(Cache_Entry* cache_entry, char* buff, size_t offset, size_t len, uint32_t frame_no, int * bytes_read, LPVIRTUALFILE virtualfile); /** * @brief Close a cache entry previously returned by transcoder_new(). * * The structure is reference counted. After calling this function, the pointer * may no longer be valid if no other thread keeps the cache entry open. * * @param[in,out] cache_entry Cache entry to close. */ void transcoder_delete(Cache_Entry* cache_entry); /** * @brief Return the logical size of a transcoded cache entry. * * Returns the file size, either the predicted size, which may be inaccurate, * or the real size, which is only available once the file was completely recoded. * * @param[in] cache_entry Cache entry to query. * @return Logical cached or predicted transcoded size in bytes. Function never fails. */ size_t transcoder_get_size(Cache_Entry* cache_entry); /** * @brief Return the number of bytes materialised for a cache item. * * While transcoding, this value reflects the current size of the transcoded file * or HLS segment. This is the maximum byte offset that can be read so far. * * @param[in] cache_entry Cache entry to query. * @param[in] segment_no HLS segment number, or 0 for the normal cache file. * @return Current cache watermark in bytes. */ size_t transcoder_buffer_watermark(Cache_Entry* cache_entry, uint32_t segment_no); /** * @brief Return the current write position for a cache item. * @param[in] cache_entry Cache entry to query. * @param[in] segment_no HLS segment number, or 0 for the normal cache file. * @return Current cache write position in bytes. */ size_t transcoder_buffer_tell(Cache_Entry* cache_entry, uint32_t segment_no); /** * @brief Exit transcoding * * Send signal to exit transcoding (ending all transcoder threads). */ void transcoder_exit(); #endif ffmpegfs-2.50/src/logging.h0000664000175000017500000004152115177713600011320 /* * Copyright (C) 2017 Original author K. Henriksson @n * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file logging.h * @brief Provide various log facilities to stderr, disk or syslog * * @ingroup ffmpegfs * * @author K. Henriksson, Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017 Original author K. Henriksson @n * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef LOGGING_H #define LOGGING_H #pragma once #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include /** * @brief #Logging facility * * @anchor format Within the log message text, escape sequences are supported for dynamically formatting the message. * * Escape sequences have either the form %n or %\n, where the index n starts at 1. * * The index specifies the position of the argument in the list. They can appear in any given order. * An index can be used more than once, and not all indexes have to be present (in which case the * argument will not be printed). The input value is automatically converted to string format and * can be among std::string, char *, int, uint64_t and more. * * The format specifier is the same as used in printf and sprintf. * * @code * int channels = 2; * int sample_rate = 44100: * Logging::debug(filename, "Audio %1 channels %2 Hz", channels, sample_rate); * @endcode * * Prints "Audio 2 channels 44100 Hz". * * @code * int channels = 2; * double sample_rate = 44.1: * Logging::debug(filename, "Audio %1 %<.3f>2 KHz", channels == 2 ? "stereo" : "mono", sample_rate); * @endcode * * Prints "Audio stereo 44.100 KHz". */ class Logging { public: /** @brief Logging level types enum */ enum class LOGLEVEL { LOGERROR = 1, /**< @brief Error level */ LOGWARN = 2, /**< @brief Warning level */ LOGINFO = 3, /**< @brief Info level */ LOGDEBUG = 4, /**< @brief Debug level */ LOGTRACE = 5 /**< @brief Error level */ }; /** * Construct Logging object. * * @param[in] logfile - The name of a file to write logging output to. If empty, no output will be written. * @param[in] max_level - The maximum level of log output to write. * @param[in] to_stderr - Whether to write log output to stderr. * @param[in] to_syslog - Whether to write log output to syslog. */ explicit Logging(const std::string & logfile, LOGLEVEL max_level, bool to_stderr, bool to_syslog); /** * @brief Check whether either failbit or badbit is set. * @return Returns true if either (or both) the failbit or the badbit error state flags are set for the stream. */ bool GetFail() const; private: /** * @brief Logging helper class. */ class Logger : public std::ostringstream { public: /** * @brief Construct Logger object. * @param[in] loglevel - The maximum level of log output to write. * @param[in] filename - Name of file for which this log entry was written. May be empty. */ Logger(LOGLEVEL loglevel, const std::string & filename) : m_loglevel(loglevel), m_filename(filename) {} /** * @brief Construct Logger object */ explicit Logger() : m_loglevel(LOGLEVEL::LOGDEBUG) {} /** * @brief Destroy Logger object */ virtual ~Logger(); private: const LOGLEVEL m_loglevel; /**< @brief Log level required to write log entry */ const std::string m_filename; /**< @brief Name of file for which this log entry was written. May be empty. */ static const std::map m_syslog_level_map; /**< @brief Map our log levels to syslog levels */ static const std::map m_level_name_map; /**< @brief Map log level enums to strings */ static const std::map m_level_colour_map; /**< @brief Map log level enums to colours (logging to stderr only) */ }; public: /** * @brief Initialise the logging facility. * @param[in] logfile - The name of a file to write logging output to. If empty, no output will be written. * @param[in] max_level - The maximum level of log output to write. * @param[in] to_stderr - Whether to write log output to stderr. * @param[in] to_syslog - Whether to write log output to syslog. * @return On success, returns true. On error, returns false. * @note It will only fail if the file cannot be opened. Writing to stderr or syslog will never fail. errno is not set. */ static bool init_logging(const std::string & logfile, LOGLEVEL max_level, bool to_stderr, bool to_syslog); /** * @brief Write trace level log entry. * @param[in] filename - Name of the file for which this log entry was written. May be empty. * @param[in] format_string - Format a string in FFmpegfs logger format. * @param[in] args - 0 or more format arguments. See @ref format. */ template static void trace(const T filename, const std::string &format_string, Args &&...args) { LOGLEVEL loglevel = LOGLEVEL::LOGTRACE; if (!show(loglevel)) { return; } log_with_level(loglevel, filename, format_helper(format_string, 1, std::forward(args)...)); } /** * @brief Write debug level log entry. * @param[in] filename - Name of the file for which this log entry was written. May be empty. * @param[in] format_string - Format a string in FFmpegfs logger format. * @param[in] args - 0 or more format arguments. See @ref format. */ template static void debug(const T filename, const std::string &format_string, Args &&...args) { LOGLEVEL loglevel = LOGLEVEL::LOGDEBUG; if (!show(loglevel)) { return; } log_with_level(loglevel, filename, format_helper(format_string, 1, std::forward(args)...)); } /** * @brief Write info level log entry. * @param[in] filename - Name of the file for which this log entry was written. May be empty. * @param[in] format_string - Format a string in FFmpegfs logger format. * @param[in] args - 0 or more format arguments. See @ref format. */ template static void info(const T filename, const std::string &format_string, Args &&...args) { LOGLEVEL loglevel = LOGLEVEL::LOGINFO; if (!show(loglevel)) { return; } log_with_level(loglevel, filename, format_helper(format_string, 1, std::forward(args)...)); } /** * @brief Write warning level log entry. * @param[in] filename - Name of the file for which this log entry was written. May be empty. * @param[in] format_string - Format a string in FFmpegfs logger format. * @param[in] args - 0 or more format arguments. See @ref format. */ template static void warning(const T filename, const std::string &format_string, Args &&...args) { LOGLEVEL loglevel = LOGLEVEL::LOGWARN; if (!show(loglevel)) { return; } log_with_level(loglevel, filename, format_helper(format_string, 1, std::forward(args)...)); } /** * @brief Write error level log entry. * @param[in] filename - Name of the file for which this log entry was written. May be empty. * @param[in] format_string - Format a string in FFmpegfs logger format. * @param[in] args - 0 or more format arguments. See @ref format. */ template static void error(const T filename, const std::string &format_string, Args &&...args) { LOGLEVEL loglevel = LOGLEVEL::LOGERROR; if (!show(loglevel)) { return; } log_with_level(loglevel, filename, format_helper(format_string, 1, std::forward(args)...)); } /** * @brief Write log entry * @param[in] loglevel - The level of log this message is for. * @param[in] filename - Name of the file for which this log entry was written. May be nullptr. * @param[in] message - Message to log. */ static void log_with_level(LOGLEVEL loglevel, const char *filename, const std::string & message); /** * @brief Write log entry * @param[in] loglevel - The level of log this message is for. * @param[in] filename - Name of the file for which this log entry was written. May be empty. * @param[in] message - Message to log. */ static void log_with_level(LOGLEVEL loglevel, const std::string & filename, const std::string & message); /** * @brief Check if log entry should be displayed at the current log level. * @param[in] loglevel - Log level of log entry. * @return True, if entry should be be shown; false if not. */ static bool show(LOGLEVEL loglevel) { return (m_logging && loglevel <= m_logging->m_max_level); } private: /** * @brief Dummy: return orignal value unchanged * @param[in] val - Original value * @return Reference to original value. */ template static T & convert(T & val) { return val; } /** * @brief Convert const char* to std::string * @param[in] val - Original value as pointer to zero terminated const char * @return Value converted to std::string or (null) if original pointer is NULL. */ static std::string convert(const char * val) { if (val != nullptr) { return val; } else { return "(null)"; } } /** * @brief Convert char* to std::string * @param[in] val - Original value as pointer to zero terminated char * @return Value converted to std::string or (null) if original pointer is NULL. */ static std::string convert(char * val) { if (val != nullptr) { return val; } else { return "(null)"; } } // std::string cannot actually be passed to %s. That's how it works... /** * @brief Dummy: return orignal value unchanged if not type std::string * @param[in] val - Original value * @return Reference to original value. */ template::value, bool>::type = true> static T & fix_std_string(T & val) { return val; } /** * @brief Convert std::string to const char* * @param[in] val - Original value as constant reference to std::string * @return Value converted to const char *. */ static const char * fix_std_string(const std::string & val) { return val.c_str(); } /** * @brief Standard format_helper without parameters. * @param[in] string_to_update - Original string. * @param[in] index_to_replace - unused * @return Returns original string. */ static std::string format_helper( const std::string &string_to_update, const size_t __attribute__((unused)) index_to_replace); /** * @brief format_helper with variadic parameters. * * Calls itself recursively until all tokens are replaced. * * @param[in] string_to_search - format string to be searched. * @param[in] index_to_replace - index number (%n) to be replaced. May be present 0...x times. * @param[in] val - Replacement value to fill in tokens. * @param[in] args - Further arguments. * @return Contents of string_to_search with all tokens replaced. */ template static std::string format_helper( const std::string &string_to_search, const size_t index_to_replace, T &&val, Args &&...args) { // Match %# exactly (e.g. %12 and %123 literally) std::regex exp("%(<([^>]+)>)*" + std::to_string(index_to_replace) + "(?=[^0-9]|$)"); std::smatch res; std::string string_to_update(string_to_search); std::string::const_iterator searchStart(string_to_search.cbegin()); size_t offset = 0; while (std::regex_search(searchStart, string_to_search.cend(), res, exp)) { std::ostringstream ostr; if (res[2].length()) { // Found match with printf format in res[2] std::vector fmt; std::string ftmspec = res[2].str(); if (ftmspec.front() != '%') { fmt.push_back('%'); } fmt.insert(fmt.end(), ftmspec.begin(), ftmspec.end()); fmt.push_back('\0'); size_t size = static_cast(std::snprintf(nullptr, 0, fmt.data(), fix_std_string(val))) + 1; std::vector buffer; buffer.resize(size); std::snprintf(buffer.data(), size, fmt.data(), fix_std_string(val)); ostr << buffer.data(); } else { // No printf format, replace literally ostr << convert(val); } string_to_update.replace(static_cast(res.position()) + offset, static_cast(res[0].length()), ostr.str()); offset += static_cast(res.position()) + ostr.str().length(); searchStart = res.suffix().first; } return format_helper( string_to_update, index_to_replace + 1, std::forward(args)...); } /** * @brief format string with single token * * @param[in] format_string - Format string to be searched. * @param[in] args - arguments * @return Contents of format_string with all tokens replaced. */ template static std::string format(const std::string &format_string, Args &&...args) { return format_helper(format_string, 1, std::forward(args)...); } protected: /** * @brief Make logger class our friend for our constructor * @param[in] loglevel - The level of log this message is for. * @param[in] filename - Name of the file for which this log entry was written. May be empty. */ friend Logger Log(LOGLEVEL loglevel, const std::string & filename); friend Logger; /**< @brief Make logger class our friend */ static std::unique_ptr m_logging; /**< @brief Reference to self, Logging is a singleton */ static std::recursive_mutex m_mutex; /**< @brief Access mutex */ std::ofstream m_logfile; /**< @brief Log file object for writing to disk */ const LOGLEVEL m_max_level; /**< @brief The maximum level of log output to write. */ const bool m_to_stderr; /**< @brief Whether to write log output to stderr. */ const bool m_to_syslog; /**< @brief Whether to write log output to syslog. */ }; constexpr Logging::LOGLEVEL LOGERROR = Logging::LOGLEVEL::LOGERROR; /**< @brief Shorthand for log level ERROR */ constexpr Logging::LOGLEVEL LOGWARN = Logging::LOGLEVEL::LOGWARN; /**< @brief Shorthand for log level WARNING */ constexpr Logging::LOGLEVEL LOGINFO = Logging::LOGLEVEL::LOGINFO; /**< @brief Shorthand for log level INFO */ constexpr Logging::LOGLEVEL LOGDEBUG = Logging::LOGLEVEL::LOGDEBUG; /**< @brief Shorthand for log level DEBUG */ constexpr Logging::LOGLEVEL LOGTRACE = Logging::LOGLEVEL::LOGTRACE; /**< @brief Shorthand for log level TRACE */ #endif ffmpegfs-2.50/src/blurayparser.cc0000664000175000017500000004744715177713600012560 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifdef USE_LIBBLURAY /** * @file blurayparser.cc * @brief Blu-ray disk parser implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "ffmpegfs.h" #include "blurayparser.h" #include "transcode.h" #include "logging.h" #include "libbluray/bluray.h" #include "libbluray/bluray-version.h" extern "C" { #include } static bool audio_stream_info(const std::string &path, BLURAY_STREAM_INFO *ss, int *channels, int *sample_rate); static bool video_stream_info(const std::string &path, BLURAY_STREAM_INFO *ss, int *width, int *height, AVRational *framerate, bool *interleaved); static int parse_find_best_audio_stream(); static int parse_find_best_video_stream(); static bool create_bluray_virtualfile(BLURAY *bd, const BLURAY_TITLE_INFO* ti, const std::string & path, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler, bool is_main_title, bool full_title, uint32_t title_idx, uint32_t chapter_idx); static int parse_bluray(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler); /** * @brief Get information about Blu-ray stream * @param[in] path - Path to Blu-ray disk. * @param[in] ss - BLURAY_STREAM_INFO object. * @param[out] channels - Number of audio channels in stream. * @param[out] sample_rate - Sample rate of stream. * @return Returns true if stream has video, false if not. */ static bool audio_stream_info(const std::string & path, BLURAY_STREAM_INFO *ss, int *channels, int *sample_rate) { bool audio = false; switch (ss->coding_type) { // Video case 0x01: case 0x02: case 0xea: case 0x1b: case 0x24: { break; } // Audio case 0x03: case 0x04: case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0xa1: case 0xa2: { switch (ss->format) { case BLURAY_AUDIO_FORMAT_MONO: { *channels = 1; // Mono break; } case BLURAY_AUDIO_FORMAT_STEREO: { *channels = 2; // Stereo break; } case BLURAY_AUDIO_FORMAT_MULTI_CHAN: { *channels = 2; // Multi Channel break; } case BLURAY_AUDIO_FORMAT_COMBO: { *channels = 2; // Stereo ac3/dts break; } default: { Logging::error(path, "Unknown number of audio channels %1. Assuming 2 channel/stereo - may be totally wrong.", ss->format); *channels = 2; // Stereo break; } } switch (ss->rate) { case BLURAY_AUDIO_RATE_48: { *sample_rate = 48000; break; } case BLURAY_AUDIO_RATE_96: { *sample_rate = 96000; break; } case BLURAY_AUDIO_RATE_192: { *sample_rate = 192000; break; } // 48 or 96 ac3/dts // 192 mpl/dts-hd case BLURAY_AUDIO_RATE_192_COMBO: { // *sample_rate = "48/192 Khz"; break; } // 48 ac3/dts // 96 mpl/dts-hd case BLURAY_AUDIO_RATE_96_COMBO: { // *sample_rate = "48/96 Khz"; break; } default: { Logging::error(path, "Unknown audio sample rate %1. Assuming 48 kHz - may be totally wrong.", ss->rate); *sample_rate = 48000; break; } } audio = true; break; } case 0x90: case 0x91: { // Language ss->lang break; } case 0x92: { // Char Code ss->char_code); // Language ss->lang); break; } default: { Logging::error(path, "Unrecognised coding type %<02x>1.", ss->coding_type); break; } } return audio; } /** * @brief Get information about Blu-ray stream * @param[in] path - Path to Blu-ray disk. * @param[in] ss - BLURAY_STREAM_INFO object. * @param[out] width - Width of video stream. * @param[out] height - Height of video stream. * @param[out] framerate - Frame rate of video stream. * @param[out] interleaved - true: video stream is interleaved, false: video stream is not interleaved. * @return Returns true if stream has video, false if not. */ static bool video_stream_info(const std::string & path, BLURAY_STREAM_INFO *ss, int *width, int *height, AVRational *framerate, bool *interleaved) { bool video = false; switch (ss->coding_type) { // Video case 0x01: case 0x02: case 0xea: case 0x1b: case 0x24: { // SD // 720×480, 59.94i, 4:3 or 16:9 // 720×576, 50i, 4:3 or 16:9 // HD // 1280×720, 59.94p, 16:9 // 1280×720, 50p, 16:9 // 1280×720, 24p, 16:9 // 1280×720, 23.976p, 16:9 // 1440×1080, 59.94i, 16:9 // 1440×1080, 50i, 16:9 // 1440×1080, 24p, 16:9 // 1440×1080, 23.976p, 16:9 // 1920×1080, 59.94i, 16:9 // 1920×1080, 50i, 16:9 // 1920×1080, 24p, 16:9 // 1920×1080, 23.976p, 16:9 // HD // 1920×1080, 60p, 16:9 // 1920×1080, 59.94p, 16:9 // 1920×1080, 50p, 16:9 // 1920×1080, 25p, 16:9 // 4K UHD // 3840×2160, 60p, 16:9 // 3840×2160, 59.94p, 16:9 // 3840×2160, 50p, 16:9 // 3840×2160, 25p, 16:9 // 3840×2160, 24p, 16:9 // 3840×2160, 23.976p, 16:9 switch (ss->format) { case BLURAY_VIDEO_FORMAT_480I: // ITU-R BT.601-5 { *width = 720; *height = 480; *interleaved = true; break; } case BLURAY_VIDEO_FORMAT_576I: // ITU-R BT.601-4 { *width = 720; *height = 576; *interleaved = true; break; } case BLURAY_VIDEO_FORMAT_480P: // SMPTE 293M { *width = 720; *height = 480; *interleaved = false; break; } case BLURAY_VIDEO_FORMAT_1080I: // SMPTE 274M { *width = 1920; *height = 1080; *interleaved = true; break; } case BLURAY_VIDEO_FORMAT_720P: // SMPTE 296M { *height = 1280; *width = 720; *interleaved = false; break; } case BLURAY_VIDEO_FORMAT_1080P: // SMPTE 274M { *width = 1920; *height = 1080; *interleaved = false; break; } case BLURAY_VIDEO_FORMAT_576P: // ITU-R BT.1358 { *width = 720; *height = 576; *interleaved = false; break; } // Added with libluray change 14aa7e9c0 (hpi1 2017-08-28 09:50:43 +0300) // Available since version 1.1.0 #if (BLURAY_VERSION_MAJOR > 1 || (BLURAY_VERSION_MAJOR == 1 && BLURAY_VERSION_MINOR >= 1)) case BLURAY_VIDEO_FORMAT_2160P: // UHD { *width = 3840; *height = 2160; *interleaved = false; break; } #endif default: { Logging::error(path, "Unknown video format %1. Assuming 1920x1080P - may be totally wrong.", ss->format); *width = 1920; *height = 1080; *interleaved = false; break; } } switch (ss->rate) { case BLURAY_VIDEO_RATE_24000_1001: { *framerate = av_make_q(24000, 1001); break; } case BLURAY_VIDEO_RATE_24: { *framerate = av_make_q(24000, 1000); break; } case BLURAY_VIDEO_RATE_25: { *framerate = av_make_q(25000, 1000); break; } case BLURAY_VIDEO_RATE_30000_1001: { *framerate = av_make_q(30000, 1001); break; } case BLURAY_VIDEO_RATE_50: { *framerate = av_make_q(50000, 1000); break; } case BLURAY_VIDEO_RATE_60000_1001: { *framerate = av_make_q(60000, 1001); break; } default: { Logging::error(path, "Unknown video frame rate %1. Assuming 25 fps - may be totally wrong.", ss->rate); *framerate = av_make_q(25000, 1000); break; } } video = true; break; } // Audio case 0x03: case 0x04: case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0xa1: case 0xa2: { break; } case 0x90: case 0x91: { // Language ss->lang break; } case 0x92: { // Char Code ss->char_code); // Language ss->lang); break; } default: { Logging::error(path, "Unrecognised coding type %<02x>1.", ss->coding_type); break; } } return video; } /** * @brief Find best match audio stream. * @todo Returning 0 is not necessarily the best match. Probably better to parse. * For "The life of Brian", e.g., this is the Hungarian audio track. No big deal, * though, I can recite mostly all dialogs in English and German (and Latin :), * but should be fixed anyway. * @return Returns index of best match stream. */ static int parse_find_best_audio_stream() { return 0; } /** * @brief Find best match video stream. * @todo Returning 0 is not necessarily the best match. Probably better to parse. * Most DVDs contain only one video track anyway, so this does not hurt at the moment. * @return Returns index of best match stream. */ static int parse_find_best_video_stream() { return 0; } /** * @brief Create a virtual file entry of a Blu-ray chapter or title. * @param[in] bd - Blu-ray disk clip info. * @param[in] ti - Blu-ray disk title info. * @param[in] path - Path to check. * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @param[in] is_main_title - true if title_idx is the main title * @param[in] full_title - If true, create virtual file of all title. If false, include single chapter only. * @param[in] title_idx - Zero-based title index on Blu-ray * @param[in] chapter_idx - Zero-based chapter index on Blu-ray * @note buf and filler can be nullptr. In that case the call will run faster, so these parameters should only be passed if to be filled in. * @return On error, returns false. On success, returns true. */ static bool create_bluray_virtualfile(BLURAY *bd, const BLURAY_TITLE_INFO* ti, const std::string & path, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler, bool is_main_title, bool full_title, uint32_t title_idx, uint32_t chapter_idx) { BLURAY_CLIP_INFO *clip = &ti->clips[0]; BLURAY_TITLE_CHAPTER *chapter = &ti->chapters[chapter_idx]; std::string title_buf; int64_t duration; if (full_title) { duration = static_cast(ti->duration) * AV_TIME_BASE / 90000; if (duration < AV_TIME_BASE) { Logging::trace(path, "Title %1: skipping empty title.", title_idx + 1); return true; } strsprintf(&title_buf, "%02u. Title [%s]%s.%s", title_idx + 1, replace_all(format_duration(duration), ":", "-").c_str(), is_main_title ? "+" : "", ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); // can safely assume this is a video format } else { duration = static_cast(chapter->duration) * AV_TIME_BASE / 90000; if (duration < AV_TIME_BASE) { Logging::trace(path, "Title %1 Chapter %2: skipping empty chapter.", title_idx + 1, chapter_idx + 1); return true; } strsprintf(&title_buf, "%02u. Chapter %03u [%s]%s.%s", title_idx + 1, chapter_idx + 1, replace_all(format_duration(duration), ":", "-").c_str(), is_main_title ? "+" : "", ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); // can safely assume this is a video format } LPVIRTUALFILE virtualfile = nullptr; if (!ffmpeg_format[FORMAT::VIDEO].is_multiformat()) { virtualfile = insert_file(VIRTUALTYPE::BLURAY, path + title_buf, statbuf); } else { virtualfile = insert_dir(VIRTUALTYPE::BLURAY, path + title_buf, statbuf); } if (virtualfile == nullptr) { Logging::error(path, "Failed to create virtual path: %1", (path + title_buf).c_str()); errno = EIO; return false; } if (add_fuse_entry(buf, filler, title_buf, &virtualfile->m_st, 0)) { // break; } // Blu-ray is video format anyway virtualfile->m_format_idx = 0; // Mark title/chapter/angle virtualfile->m_full_title = full_title; virtualfile->m_bluray.m_title_no = title_idx + 1; virtualfile->m_bluray.m_playlist_no = ti->playlist; virtualfile->m_bluray.m_chapter_no = chapter_idx + 1; virtualfile->m_bluray.m_angle_no = 1; if (!transcoder_cached_filesize(virtualfile, &virtualfile->m_st)) { BITRATE video_bit_rate = 29*1024*1024; // In case the real bitrate cannot be calculated later, assume 20 Mbit video bitrate BITRATE audio_bit_rate = 256*1024; // In case the real bitrate cannot be calculated later, assume 256 kBit audio bitrate bool audio = false; bool interleaved = false; if (!bd_select_title(bd, title_idx)) { Logging::error(path, "The Blu-ray title %1 could not be opened.", title_idx); errno = EIO; return false; } uint64_t size = bd_get_title_size(bd); virtualfile->m_duration = duration; if (duration) { /** * @todo We actually calculate the overall Blu-ray bitrate here, including all audio * streams, not just the video bitrate. This should be the video bitrate alone. We * should also calculate the audio bitrate for the selected stream. */ video_bit_rate = static_cast(size * 8LL * AV_TIME_BASE / static_cast(duration)); // calculate bitrate in bps } // Get details if (clip->audio_stream_count) { audio_stream_info(path, &clip->audio_streams[parse_find_best_audio_stream()], &virtualfile->m_channels, &virtualfile->m_sample_rate); } if (clip->video_stream_count) { audio = video_stream_info(path, &clip->video_streams[parse_find_best_video_stream()], &virtualfile->m_width, &virtualfile->m_height, &virtualfile->m_framerate, &interleaved); } Logging::trace(path, "Video %1 %2x%3@%<5.2f>4%5 fps %6 [%7]", format_bitrate(video_bit_rate).c_str(), virtualfile->m_width, virtualfile->m_height, av_q2d(virtualfile->m_framerate), interleaved ? "i" : "p", format_size(size).c_str(), format_duration(duration).c_str()); if (audio) { Logging::trace(path, "Audio %1 channels %2", virtualfile->m_channels, format_samplerate(virtualfile->m_sample_rate).c_str()); } transcoder_set_filesize(virtualfile, duration, audio_bit_rate, virtualfile->m_channels, virtualfile->m_sample_rate, AV_SAMPLE_FMT_NONE, video_bit_rate, virtualfile->m_width, virtualfile->m_height, interleaved, virtualfile->m_framerate); virtualfile->m_video_frame_count = static_cast(av_rescale_q(duration, av_get_time_base_q(), av_inv_q(virtualfile->m_framerate))); virtualfile->m_predicted_size = static_cast(size); } return true; } /** * @brief Parse Blu-ray directory and get all Blu-ray titles and chapters as virtual files. * @param[in] path - Path to check. * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @return On success, returns number of chapters found. On error, returns -errno. */ static int parse_bluray(const std::string & path, const struct stat * statbuf, void * buf, fuse_fill_dir_t filler) { BLURAY *bd; uint32_t title_count; int main_title; unsigned int seconds = 0; uint8_t flags = TITLES_RELEVANT; const char *bd_dir = nullptr; bool success = true; bd_dir = path.c_str(); Logging::debug(path, "Parsing Blu-ray."); bd = bd_open(bd_dir, nullptr); title_count = bd_get_titles(bd, flags, seconds); main_title = bd_get_main_title(bd); if (main_title >= 0) { Logging::trace(path, "Main title: %1", main_title + 1); } for (uint32_t title_idx = 0; title_idx < title_count && success; title_idx++) { BLURAY_TITLE_INFO* ti = bd_get_title_info(bd, title_idx, 0); bool is_main_title = (main_title >= 0 && title_idx == static_cast(main_title)); // Add separate chapters for (uint32_t chapter_idx = 0; chapter_idx < ti->chapter_count && success; chapter_idx++) { success = create_bluray_virtualfile(bd, ti, path, statbuf, buf, filler, is_main_title, false, title_idx, chapter_idx); } if (success && ti->chapter_count > 1) { // If more than 1 chapter, add full title as well success = create_bluray_virtualfile(bd, ti, path, statbuf, buf, filler, is_main_title, true, title_idx, 0); } bd_free_title_info(ti); } bd_close(bd); if (success) { return static_cast(title_count); } else { return -errno; } } int check_bluray(const std::string & path, void *buf, fuse_fill_dir_t filler) { std::string _path(path); struct stat stbuf; int res = 0; append_sep(&_path); if (stat((_path + "BDMV/index.bdmv").c_str(), &stbuf) == 0) { if (!check_path(_path)) { Logging::trace(_path, "Blu-ray detected."); res = parse_bluray(_path, &stbuf, buf, filler); Logging::trace(_path, "%1 titles were discovered.", res); } else { res = load_path(_path, &stbuf, buf, filler); } add_dotdot(buf, filler, &stbuf, 0); } return res; } #endif // USE_LIBBLURAY ffmpegfs-2.50/src/blurayio.h0000664000175000017500000001416615177713600011525 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file blurayio.h * @brief Blu-ray I/O * * This is only available if built with -DUSE_LIBBLURAY parameter. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef BLURAYIO_H #define BLURAYIO_H #pragma once #ifdef USE_LIBBLURAY #include "fileio.h" #include typedef struct bluray BLURAY; /**< @brief Forward declaration of libbluray handle */ /** * @brief Blu-ray I/O class */ class BlurayIO : public FileIO { public: /** * @brief Create #BlurayIO object */ explicit BlurayIO(); /** * @brief Free #BlurayIO object */ virtual ~BlurayIO(); /** * @brief Get type of the virtual file * @return Returns the type of the virtual file. */ virtual VIRTUALTYPE type() const override; /** * @brief Get the ideal buffer size. * @return Return the ideal buffer size. */ virtual size_t bufsize() const override; /** * @brief Open a virtual file * @param[in] virtualfile - LPCVIRTUALFILE of file to open * @return Upon successful completion, #openio() returns 0. @n * On error, an nonzero value is returned and errno is set to indicate the error. */ virtual int openio(LPVIRTUALFILE virtualfile) override; /** * @brief Read data from file * @param[out] data - buffer to store read bytes in. Must be large enough to hold up to size bytes. * @param[in] size - number of bytes to read * @return Upon successful completion, #readio() returns the number of bytes read. @n * This may be less than size. @n * On error, the value 0 is returned and errno is set to indicate the error. @n * If at end of file, 0 may be returned but errno not set. error() will return 0 if at EOF. */ virtual size_t readio(void *data, size_t size) override; /** * @brief Get last error. * @return errno value of last error. */ virtual int error() const override; /** * @brief Get the duration of the file, in AV_TIME_BASE fractional seconds. * @return Returns the duration of the file, in AV_TIME_BASE fractional seconds. */ virtual int64_t duration() const override; /** * @brief Get the file size. * @return Returns the file size. */ virtual size_t size() const override; /** * @brief Get current read position. * @return Gets the current read position. */ virtual size_t tell() const override; /** * @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. @n * On error, the value -1 is returned and errno is set to indicate the error. */ virtual int seek(int64_t offset, int whence) override; /** * @brief Check if at end of file. * @return Returns true if at end of file. */ virtual bool eof() const override; /** * @brief Close virtual file. */ virtual void closeio() override; private: /** * @brief Close virtual file. * Non-virtual version to be safely called from constructor/destructor */ void pvt_close(); protected: BLURAY * m_bd; /**< @brief Blu-ray disk handle */ bool m_is_eof; /**< @brief true if at end of virtual file */ int m_errno; /**< @brief Last errno */ size_t m_rest_size; /**< @brief Rest bytes in buffer */ size_t m_rest_pos; /**< @brief Position in buffer */ int64_t m_cur_pos; /**< @brief Current position in virtual file */ int64_t m_start_pos; /**< @brief Start offset in bytes */ int64_t m_end_pos; /**< @brief End offset in bytes (not including this byte) */ bool m_full_title; /**< @brief If true, ignore m_chapter_no and provide full track */ uint32_t m_title_idx; /**< @brief Track index (track number - 1) */ unsigned m_chapter_idx; /**< @brief Chapter index (chapter number - 1) */ unsigned m_angle_idx; /**< @brief Selected angle index (angle number -1) */ std::array m_data; /**< @brief Buffer for readio() data */ int64_t m_duration; /**< @brief Track/chapter duration, in AV_TIME_BASE fractional seconds. */ }; #endif // USE_LIBBLURAY #endif // BLURAYIO_H ffmpegfs-2.50/src/ffmpeg_compat.h0000664000175000017500000001360315177713600012501 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_compat.h * @brief FFmpeg API compatibility * * This file makes it possible to support FFmpeg 2.x to 4. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_COMPAT_H #define FFMPEG_COMPAT_H #pragma once #ifdef __clang__ #define FALLTHROUGH_INTENDED [[clang::fallthrough]] /**< @brief Allow fallthrough in case statements for Clang */ #else #define FALLTHROUGH_INTENDED [[gnu::fallthrough]] /**< @brief Allow fallthrough in case statements for GCC */ #endif /** * FFmpeg compatibility layer: Maintain support for older versions while removing * deprecated functions as needed. * * See doc/APIchanges * https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/doc/APIchanges */ /** * 2022-07-xx - xxxxxxxxxx - lavu 57.30.100 - frame.h * Add AVFrame.duration, deprecate AVFrame.pkt_duration. */ #define LAVU_DEP_PKT_DURATION (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 30, 0)) /** * This is big fun... Of course making these const is a good idea, but nevertheless * a PITA to keep the code working with newer AND older versions... * * 2021-04-27 - cb3ac722f4 - lavc 59.0.100 - avcodec.h * Constified AVCodecParserContext.parser. * * 2021-04-27 - 8b3e6ce5f4 - lavd 59.0.100 - avdevice.h * The av_*_device_next API functions now accept and return * pointers to const AVInputFormat resp. AVOutputFormat. * * 2021-04-27 - d7e0d428fa - lavd 59.0.100 - avdevice.h * avdevice_list_input_sources and avdevice_list_output_sinks now accept * pointers to const AVInputFormat resp. const AVOutputFormat. * * 2021-04-27 - 46dac8cf3d - lavf 59.0.100 - avformat.h * av_find_best_stream now uses a const AVCodec ** parameter * for the returned decoder. * * 2021-04-27 - 626535f6a1 - lavc 59.0.100 - codec.h * avcodec_find_encoder_by_name(), avcodec_find_encoder(), * avcodec_find_decoder_by_name() and avcodec_find_decoder() * now return a pointer to const AVCodec. * * 2021-04-27 - 14fa0a4efb - lavf 59.0.100 - avformat.h * Constified AVFormatContext.*_codec. * * 2021-04-27 - 56450a0ee4 - lavf 59.0.100 - avformat.h * Constified the pointers to AVInputFormats and AVOutputFormats * in AVFormatContext, avformat_alloc_output_context2(), * av_find_input_format(), av_probe_input_format(), * av_probe_input_format2(), av_probe_input_format3(), * av_probe_input_buffer2(), av_probe_input_buffer(), * avformat_open_input(), av_guess_format() and av_guess_codec(). * Furthermore, constified the AVProbeData in av_probe_input_format(), * av_probe_input_format2() and av_probe_input_format3(). */ #define IF_DECLARED_CONST (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 0)) /** * 2022-03-15 - cdba98bb80 - lavu 57.24.100 - channel_layout.h frame.h opt.h * Add new channel layout API based on the AVChannelLayout struct. * Add support for Ambisonic audio. * Deprecate previous channel layout API based on uint64 bitmasks. */ #define LAVU_DEP_OLD_CHANNEL_LAYOUT (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 24, 0)) /** * 2022-03-15 - cdba98bb80 - swr 4.5.100 - swresample.h * Add swr_alloc_set_opts2() and swr_build_matrix2(). * Deprecate swr_alloc_set_opts() and swr_build_matrix(). */ #define SWR_DEP_ALLOC_SET_OPTS (LIBSWRESAMPLE_VERSION_INT >= AV_VERSION_INT(4, 5, 0)) /** * 2023-05-04 - xxxxxxxxxx - lavu 58.7.100 - frame.h * Deprecate AVFrame.interlaced_frame, AVFrame.top_field_first, and * AVFrame.key_frame. * Add AV_FRAME_FLAG_INTERLACED, AV_FRAME_FLAG_TOP_FIELD_FIRST, and * AV_FRAME_FLAG_KEY flags as replacement. */ #define LAVU_ADD_NEW_FRAME_FLAGS (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 0)) /** * 2021-09-20 - dd846bc4a91 - lavc 59.8.100 - avcodec.h codec.h * Deprecate AV_CODEC_FLAG_TRUNCATED and AV_CODEC_CAP_TRUNCATED, * as they are redundant with parsers. */ #define LAVC_DEP_FLAG_TRUNCATED (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 8, 0)) /** * 2023-09-07 - 2a68d945cd7 - lavf 60.12.100 - avio.h * Constify the buffer pointees in the write_packet and write_data_type * callbacks of AVIOContext on the next major bump. */ #define LAVF_WRITEPACKET_CONST (LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(61, 0, 0)) /** * 2023-05-15 - 7d1d61cc5f5 - lavc 60 - avcodec.h * Depreate AVCodecContext.ticks_per_frame in favor of * AVCodecContext.framerate (encoding) and * AV_CODEC_PROP_FIELDS (decoding). */ #define LAVC_DEP_TICKSPERFRAME (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 0, 0)) /** * 2024-09-08 - 3305767560a - lavc 61.13.100 - avcodec.h * Add avcodec_get_supported_config() and enum AVCodecConfig; deprecate * AVCodec.pix_fmts, AVCodec.sample_fmts, AVCodec.supported_framerates, * AVCodec.supported_samplerates and AVCodec.ch_layouts. */ #define LAVC_USE_SUPPORTED_CFG (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 0)) #endif // FFMPEG_COMPAT_H ffmpegfs-2.50/src/dvdio.cc0000664000175000017500000006043215177713600011137 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifdef USE_LIBDVD /** * @file dvdio.cc * @brief DvdIO class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "dvdio.h" #include "logging.h" #include #include #include DvdIO::DvdIO() : m_dvd(nullptr) , m_dvd_title(nullptr) , m_vmg_file(nullptr) , m_vts_file(nullptr) , m_cur_pgc(nullptr) , m_start_cell(0) , m_end_cell(0) , m_cur_cell(0) , m_next_cell(0) , m_goto_next_cell(false) , m_cur_block(0) , m_is_eof(false) , m_errno(0) , m_rest_size(0) , m_rest_pos(0) , m_cur_pos(0) , m_full_title(false) , m_title_idx(0) , m_chapter_idx(0) , m_angle_idx(0) , m_duration(AV_NOPTS_VALUE) , m_size(0) { std::memset(&m_data, 0, sizeof(m_data)); std::memset(&m_buffer, 0, sizeof(m_buffer)); } DvdIO::~DvdIO() { pvt_close(); } VIRTUALTYPE DvdIO::type() const { return VIRTUALTYPE::DVD; } size_t DvdIO::bufsize() const { return sizeof(m_data); } int DvdIO::openio(LPVIRTUALFILE virtualfile) { int pgc_id; int ttn; int pgn; tt_srpt_t *tt_srpt; vts_ptt_srpt_t *vts_ptt_srpt; set_virtualfile(virtualfile); if (virtualfile != nullptr) { m_full_title = virtualfile->m_full_title; m_title_idx = virtualfile->m_dvd.m_title_no - 1; m_chapter_idx = virtualfile->m_dvd.m_chapter_no - 1; m_angle_idx = virtualfile->m_dvd.m_angle_no - 1; m_duration = virtualfile->m_duration; } else { m_full_title = false; m_title_idx = 0; m_chapter_idx = 0; m_angle_idx = 0; m_duration = AV_NOPTS_VALUE; } Logging::debug(path(), "Opening the input DVD."); // Open the disc. m_dvd = DVDOpen(path().c_str()); if (m_dvd == nullptr) { Logging::error(path(), "Couldn't open the DVD."); return EINVAL; } // Load the video manager to find out the information about the titles on this disc. m_vmg_file = ifoOpen(m_dvd, 0); if (m_vmg_file == nullptr) { Logging::error(path(), "Can't open VMG info."); DVDClose(m_dvd); return EINVAL; } tt_srpt = m_vmg_file->tt_srpt; // Make sure our title number is valid. Logging::trace(path(), "There are %1 titles on this DVD.", static_cast(tt_srpt->nr_of_srpts)); if (m_title_idx < 0 || m_title_idx >= tt_srpt->nr_of_srpts) { Logging::error(path(), "Invalid title %1.", m_title_idx + 1); ifoClose(m_vmg_file); DVDClose(m_dvd); return EINVAL; } // Make sure the chapter number is valid for this title. Logging::trace(nullptr, "There are %1 chapters in this title.", static_cast(tt_srpt->title[m_title_idx].nr_of_ptts)); if (m_chapter_idx < 0 || m_chapter_idx >= tt_srpt->title[m_title_idx].nr_of_ptts) { Logging::error(path(), "Invalid chapter %1.", m_chapter_idx + 1); ifoClose(m_vmg_file); DVDClose(m_dvd); return EINVAL; } // Make sure the angle number is valid for this title. Logging::trace(path(), "There are %1 angles in this title.", tt_srpt->title[m_title_idx].nr_of_angles); if (m_angle_idx < 0 || m_angle_idx >= tt_srpt->title[m_title_idx].nr_of_angles) { Logging::error(nullptr, "Invalid angle %1.", m_angle_idx + 1); ifoClose(m_vmg_file); DVDClose(m_dvd); return EINVAL; } // Load the VTS information for the title set our title is in. m_vts_file = ifoOpen(m_dvd, tt_srpt->title[m_title_idx].title_set_nr); if (m_vts_file == nullptr) { Logging::error(path(), "Can't open the title %1 info file.", tt_srpt->title[m_title_idx].title_set_nr); ifoClose(m_vmg_file); DVDClose(m_dvd); return EINVAL; } // Determine which program chain we want to watch. This is based on the chapter number. ttn = tt_srpt->title[m_title_idx].vts_ttn; vts_ptt_srpt = m_vts_file->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[m_chapter_idx].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[m_chapter_idx].pgn; m_cur_pgc = m_vts_file->vts_pgcit->pgci_srp[pgc_id - 1].pgc; m_start_cell = m_cur_pgc->program_map[pgn - 1] - 1; if (pgn < m_cur_pgc->nr_of_programs) { m_end_cell = m_cur_pgc->program_map[pgn] - 1; } else { m_end_cell = m_cur_pgc->nr_of_cells; } // We've got enough info, time to open the title set data. m_dvd_title = DVDOpenFile(m_dvd, tt_srpt->title[m_title_idx].title_set_nr, DVD_READ_TITLE_VOBS); if (m_dvd_title == nullptr) { Logging::error(path(), "Can't open title VOBS (VTS_%<02d>1_X.VOB).", tt_srpt->title[m_title_idx].title_set_nr); ifoClose(m_vts_file); ifoClose(m_vmg_file); DVDClose(m_dvd); return EINVAL; } rewind(); // Determine the net file size m_size = 0; size_t bytes_read; while ((bytes_read = readio(nullptr, ULONG_MAX)) != 0 && !eof()) { m_size += bytes_read; } rewind(); return 0; } // Demux and cell navigation nicked from https://www.videolan.org/vlc/download-sources.html // More details see http://stnsoft.com/DVD/vobov.html #define PS_STREAM_ID_END_STREAM 0xB9 /**< @brief ??? */ #define PS_STREAM_ID_PACK_HEADER 0xBA /**< @brief MPEG-2 Pack Header */ #define PS_STREAM_ID_SYSTEM_HEADER 0xBB /**< @brief Program Stream System Header */ #define PS_STREAM_ID_MAP 0xBC /**< @brief ??? */ #define PS_STREAM_ID_PRIVATE_STREAM1 0xBD /**< @brief Private stream 1 (non MPEG audio, subpictures) */ #define PS_STREAM_ID_PADDING 0xBE /**< @brief Padding stream */ #define PS_STREAM_ID_PRIVATE 0xBF /**< @brief Private stream 2 (navigation data) */ #define PS_STREAM_ID_AUDIO 0xC0 /**< @brief - 0xDF MPEG-1 or MPEG-2 audio stream number (note: DVD allows only 8 audio streams) */ #define PS_STREAM_ID_VIDEO 0xE0 /**< @brief - 0xEF MPEG-1 or MPEG-2 video stream number (note: DVD allows only 1 video stream) */ #define PS_STREAM_ID_EXTENDED 0xFD /**< @brief ??? */ #define PS_STREAM_ID_DIRECTORY 0xFF /**< @brief ??? */ #define PS_STREAM_ID 3 /**< @brief ??? */ bool DvdIO::get_packet_size(const uint8_t *p, size_t peek, size_t *size) const { if (peek < 4) { return false; // Invalid size } switch (p[PS_STREAM_ID]) { case PS_STREAM_ID_END_STREAM: { *size = 4; return true; } case PS_STREAM_ID_PACK_HEADER: { // MPEG-2 pack header, see http://stnsoft.com/DVD/packhdr.html if (peek > 4) { if (peek >= 14 && (p[4] >> 6) == 0x01) { *size = 14 + static_cast(p[13] & 0x07); // Byte 13 Bit 0..2: Pack stuffing length return true; } else if (peek >= 12 && (p[4] >> 4) == 0x02) { *size = 12; // unclear what this is for return true; } } break; } case PS_STREAM_ID_SYSTEM_HEADER: // http://stnsoft.com/DVD/sys_hdr.html, see http://stnsoft.com/DVD/sys_hdr.html case PS_STREAM_ID_MAP: // ??? case PS_STREAM_ID_DIRECTORY: // ??? default: { if (peek >= 6) { *size = (6 + ((static_cast(p[4]) << 8) | p[5])); // Byte 4/5: header length return true; } break; } } return false; // unknown ID } int DvdIO::get_pes_id(const uint8_t *buffer, size_t size) const { if (buffer[PS_STREAM_ID] == PS_STREAM_ID_PRIVATE_STREAM1) { uint8_t sub_id = 0; if (size >= 9 && size >= static_cast(9 + buffer[8])) { const size_t start = static_cast(9 + buffer[8]); sub_id = buffer[start]; if ((sub_id & 0xfe) == 0xa0 && size >= start + 7 && (buffer[start + 5] >= 0xc0 || buffer[start + 6] != 0x80)) { // AOB LPCM/MLP extension // XXX for MLP I think that the !=0x80 test is not good and // will fail for some valid files return (0xa000 | (sub_id & 0x01)); } } // VOB extension return (0xbd00 | sub_id); } else if (buffer[PS_STREAM_ID] == PS_STREAM_ID_EXTENDED && size >= 9 && (buffer[6] & 0xC0) == 0x80 && // mpeg2 (buffer[7] & 0x01) == 0x01) // extension_flag { // ISO 13818 amendment 2 and SMPTE RP 227 const uint8_t flags = buffer[7]; size_t skip = 9; // Find PES extension if ((flags & 0x80)) { skip += 5; // pts if ((flags & 0x40)) { skip += 5; // dts } } if ((flags & 0x20)) { skip += 6; } if ((flags & 0x10)) { skip += 3; } if ((flags & 0x08)) { skip += 1; } if ((flags & 0x04)) { skip += 1; } if ((flags & 0x02)) { skip += 2; } if (skip < size && (buffer[skip] & 0x01)) { const uint8_t flags2 = buffer[skip]; // Find PES extension 2 skip += 1; if (flags2 & 0x80) { skip += 16; } if ((flags2 & 0x40) && skip < size) { skip += static_cast(1 + buffer[skip]); } if (flags2 & 0x20) { skip += 2; } if (flags2 & 0x10) { skip += 2; } if (skip + 1 < size) { const int i_extension_field_length = buffer[skip] & 0x7f; if (i_extension_field_length >=1) { int i_stream_id_extension_flag = (buffer[skip+1] >> 7) & 0x1; if (i_stream_id_extension_flag == 0) { return (0xfd00 | (buffer[skip+1] & 0x7f)); } } } } } return buffer[PS_STREAM_ID]; } size_t DvdIO::demux_pes(uint8_t *out, const uint8_t *in, size_t len) const { size_t netsize = 0; while (len > 0) { size_t pktsize = 0; if (!get_packet_size(in, len, &pktsize) || pktsize > len) { break; } // Parse block and copy to buffer switch (0x100 | in[PS_STREAM_ID]) { // Ignore thesse... case (0x100 | PS_STREAM_ID_END_STREAM): case (0x100 | PS_STREAM_ID_SYSTEM_HEADER): case (0x100 | PS_STREAM_ID_MAP): { break; } case (0x100 | PS_STREAM_ID_PACK_HEADER): // MPEG-2 Pack Header { std::memcpy(out, in, pktsize); out += pktsize; netsize += pktsize; break; } default: { int id = get_pes_id(in, pktsize); if (id >= PS_STREAM_ID_AUDIO) // Audio/Video/Extended or Directory { // Probably this is sufficient here: // 110x xxxx 0xC0 - 0xDF MPEG-1 or MPEG-2 audio stream number x xxxx // 1110 xxxx 0xE0 - 0xEF MPEG-1 or MPEG-2 video stream number xxxx std::memcpy(out, in, pktsize); out += pktsize; netsize += pktsize; } break; } } in += pktsize; len -= pktsize; } return netsize; } DvdIO::DSITYPE DvdIO::handle_DSI(void *_dsi_pack, size_t * cur_output_size, unsigned int *next_block, uint8_t *data) { dsi_t * dsi_pack = reinterpret_cast(_dsi_pack); DSITYPE dsitype = DSITYPE::CONTINUE; bool end_of_cell; navRead_DSI(dsi_pack, &data[DSI_START_BYTE]); // Determine where we go next. These values are the ones we mostly // care about. m_cur_block = dsi_pack->dsi_gi.nv_pck_lbn; *cur_output_size = dsi_pack->dsi_gi.vobu_ea; // If we're not at the end of this cell, we can determine the next // VOBU to display using the VOBU_SRI information section of the // DSI. Using this value correctly follows the current angle, // avoiding the doubled scenes in The Matrix, and makes our life // really happy. *next_block = m_cur_block + (dsi_pack->vobu_sri.next_vobu & 0x7fffffff); end_of_cell = (dsi_pack->vobu_sri.next_vobu == SRI_END_OF_CELL || *next_block >= m_cur_pgc->cell_playback[m_cur_cell].last_sector); // Double check end of cell: DVD transcoding stops in the middle of the chapter #48 if (!end_of_cell && m_angle_idx > 1) { switch ((dsi_pack->sml_pbi.category & 0xf000) >> 12) { case 0x4: { // Interleaved unit with no angle // dsi_pack->sml_pbi.ilvu_sa // relative offset to the next ILVU block (not VOBU) for this angle or scene. // 00 00 00 00 for PREU and non-interleaved blocks // ff ff ff ff for the last interleaved block, indicating the end of interleaving if (dsi_pack->sml_pbi.ilvu_sa != 0 && dsi_pack->sml_pbi.ilvu_sa != 0xffffffff) { *next_block = m_cur_block + dsi_pack->sml_pbi.ilvu_sa; *cur_output_size = dsi_pack->sml_pbi.ilvu_ea; } else { *next_block = m_cur_block + dsi_pack->dsi_gi.vobu_ea + 1; } break; } case 0x5: { // vobu is end of ilvu if (dsi_pack->sml_agli.data[m_angle_idx].address) { *next_block = m_cur_block + dsi_pack->sml_agli.data[m_angle_idx].address; *cur_output_size = dsi_pack->sml_pbi.ilvu_ea; break; } } // fall through case 0x6: // vobu is beginning of ilvu case 0x9: // next scr is 0 case 0xa: // entering interleaved section case 0x8: // non interleaved cells in interleaved section default: { *next_block = m_cur_block + (dsi_pack->vobu_sri.next_vobu & 0x7fffffff); break; } } } else if (end_of_cell) { if (m_next_cell >= m_cur_pgc->nr_of_cells) { *next_block = 0; dsitype = DSITYPE::EOF_TITLE; } else { m_cur_cell = m_next_cell; next_cell(); if (m_cur_cell >= m_end_cell) { dsitype = DSITYPE::EOF_CHAPTER; } *next_block = m_cur_pgc->cell_playback[m_cur_cell].first_sector; } } return dsitype; } void DvdIO::next_cell() { // Check if we're entering an angle block if (m_cur_pgc->cell_playback[m_cur_cell].block_type == static_cast(BLOCK_TYPE_ANGLE_BLOCK)) { m_cur_cell += m_angle_idx; for (int i = 0;; ++i) { if (m_cur_pgc->cell_playback[m_cur_cell + i].block_mode == static_cast(BLOCK_MODE_LAST_CELL)) { m_next_cell = m_cur_cell + i + 1; break; } } } else { m_next_cell = m_cur_cell + 1; } } size_t DvdIO::readio(void * data, size_t size) { size_t cur_output_size; size_t result_len = 0; DSITYPE dsitype; if (m_rest_size) { size_t rest_size = m_rest_size; if (m_rest_size > size) { errno = EINVAL; return 0; } if (data != nullptr) { std::memcpy(data, &m_data[m_rest_pos], rest_size); } m_rest_size = m_rest_pos = 0; return rest_size; } // Playback by cell in this pgc, starting at the cell for our chapter. //while (next_cell < last_cell) { if (m_goto_next_cell) { m_goto_next_cell = false; m_cur_cell = m_next_cell; next_cell(); m_cur_block = m_cur_pgc->cell_playback[m_cur_cell].first_sector; } if (m_cur_block >= m_cur_pgc->cell_playback[m_cur_cell].last_sector) { m_is_eof = false; return 0; } // We loop until we're out of this cell. //for(cur_pack = cur_pgc->cell_playback[cur_cell].first_sector; // cur_pack < cur_pgc->cell_playback[cur_cell].last_sector;) { dsi_t dsi_pack; unsigned int next_block; ssize_t maxlen; // Read NAV packet. maxlen = DVDReadBlocks(m_dvd_title, static_cast(m_cur_block), 1, m_buffer.data()); if (maxlen != 1) { Logging::error(path(), "Read failed for block at %1.", m_cur_block); m_errno = EIO; return 0; } if (!is_nav_pack(m_buffer.data())) { Logging::warning(path(), "Block at %1 is probably not a NAV packet. Transcode may fail.", m_cur_block); } // Parse the contained dsi packet. dsitype = handle_DSI(&dsi_pack, &cur_output_size, &next_block, m_buffer.data()); if (m_cur_block != dsi_pack.dsi_gi.nv_pck_lbn) { Logging::error(path(), "Read failed at %1 because current block != dsi_pack.dsi_gi.nv_pck_lbn.", m_cur_block); m_errno = EIO; return 0; } if (cur_output_size >= 1024) { Logging::error(path(), "Read failed at %1 because current output size %2 >= 1024.", m_cur_block, cur_output_size); m_errno = EIO; return 0; } m_cur_block++; // Read in and output cur_output_size packs. maxlen = DVDReadBlocks(m_dvd_title, static_cast(m_cur_block), cur_output_size, m_buffer.data()); if (maxlen != static_cast(cur_output_size)) { Logging::error(path(), "Read failed for %1 blocks at %2.", cur_output_size, m_cur_block); m_errno = EIO; return 0; } size_t netsize = cur_output_size * DVD_VIDEO_LB_LEN; netsize = demux_pes(m_data.data(), m_buffer.data(), netsize); if (data != nullptr) { if (netsize > size) { result_len = size; std::memcpy(data, m_data.data(), result_len); m_rest_size = netsize - size; m_rest_pos = size; } else { result_len = netsize; std::memcpy(data, m_data.data(), result_len); } } else { if (netsize > size) { result_len = size; } else { result_len = netsize; } } m_cur_block = next_block; } //break; } // DSITYPE::EOF_TITLE - end of title // DSITYPE::EOF_CHAPTER - end of chapter if ((dsitype != DSITYPE::CONTINUE && !m_full_title) || // Stop at end of chapter/title (dsitype == DSITYPE::EOF_TITLE)) // Stop at end of title { m_is_eof = true; } m_cur_pos += result_len; return result_len; } int DvdIO::error() const { return m_errno; } int64_t DvdIO::duration() const { return m_duration; } size_t DvdIO::size() const { return m_size; } size_t DvdIO::tell() const { return m_cur_pos; } void DvdIO::rewind() { m_next_cell = m_start_cell; m_cur_cell = m_start_cell; m_goto_next_cell = true; m_is_eof = false; m_errno = 0; m_rest_size = 0; m_rest_pos = 0; m_cur_pos = 0; } int DvdIO::seek(int64_t offset, int whence) { errno = 0; if (whence == SEEK_SET && !offset) { // Only rewind (seek(0, SEEK_SET) is implemented yet rewind(); return 0; } size_t cur_pos = tell(); size_t abs_offset = 0; switch (whence) { case SEEK_SET: abs_offset = static_cast(offset); break; case SEEK_CUR: abs_offset += cur_pos; break; case SEEK_END: abs_offset = size() - abs_offset; break; default: errno = EINVAL; return (EOF); } if (cur_pos == abs_offset) { // Already at right position return static_cast(abs_offset); } if (cur_pos > abs_offset) { // Need to start from beginning to find byte position rewind(); cur_pos = 0; } size_t total_read = 0; size_t bytes_read; while ((bytes_read = readio(nullptr, abs_offset - total_read - cur_pos)) != 0 && total_read < abs_offset && !eof()) { total_read += bytes_read; } if (total_read) { return static_cast(cur_pos + total_read); } errno = EPERM; return (EOF); } bool DvdIO::eof() const { return m_is_eof; } void DvdIO::closeio() { pvt_close(); } void DvdIO::pvt_close() { ifo_handle_t * vts_file = m_vts_file; if (vts_file != nullptr) { m_vts_file = nullptr; ifoClose(vts_file); } ifo_handle_t * vmg_file = m_vmg_file; if (vmg_file != nullptr) { m_vmg_file = nullptr; ifoClose(vmg_file); } dvd_file_t * dvd_title = m_dvd_title; if (dvd_title != nullptr) { m_dvd_title = nullptr; DVDCloseFile(dvd_title); } dvd_reader_t * dvd = m_dvd; if (dvd != nullptr) { m_dvd = nullptr; DVDClose(dvd); } } // Code nicked from Handbrake (https://github.com/HandBrake/HandBrake/blob/master/libhb/dvd.c) bool DvdIO::is_nav_pack(const unsigned char *buffer) const { /* * The NAV Pack is comprised of the PCI Packet and DSI Packet, both * of these start at known offsets and start with a special identifier. * * NAV = { * PCI = { 00 00 01 bf # private stream header * ?? ?? # length * 00 # substream * ... * } * DSI = { 00 00 01 bf # private stream header * ?? ?? # length * 01 # substream * ... * } * * The PCI starts at offset 0x26 into the sector, and the DSI starts at 0x400 * * This information from: http://dvd.sourceforge.net/dvdinfo/ */ if ((buffer[0x26] == 0x00 && // PCI buffer[0x27] == 0x00 && buffer[0x28] == 0x01 && buffer[0x29] == 0xbf && buffer[0x2c] == 0x00) && (buffer[0x400] == 0x00 && // DSI buffer[0x401] == 0x00 && buffer[0x402] == 0x01 && buffer[0x403] == 0xbf && buffer[0x406] == 0x01)) { return true; } else { return false; } } #endif // USE_LIBDVD ffmpegfs-2.50/src/transcode.cc0000664000175000017500000016346015215723104012012 /* * Copyright (C) 2006-2008 David Collett * Copyright (C) 2008-2013 K. Henriksson * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file transcode.cc * @brief File transcoder interface implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2006-2008 David Collett @n * Copyright (C) 2008-2013 K. Henriksson @n * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) */ #include "transcode.h" #include "ffmpegfs.h" #include "ffmpeg_transcoder.h" #include "buffer.h" #include "cache.h" #include "logging.h" #include "cache_entry.h" #include "thread_pool.h" #include #include #include #include const int GRANULARITY = 250; /**< @brief Image frame conversion: ms between checks if a picture frame is available */ const int FRAME_TIMEOUT = 60; /**< @brief Image frame conversion: timout seconds to wait if a picture frame is available */ const int TOTAL_RETRIES = FRAME_TIMEOUT*1000/GRANULARITY; /**< @brief Number of retries */ /** * @brief THREAD_DATA struct to pass data from parent to child thread */ typedef struct THREAD_DATA { std::mutex m_thread_running_mutex; /**< @brief Mutex when thread is running */ std::condition_variable m_thread_running_cond; /**< @brief Condition when thread is running */ std::atomic_bool m_thread_running_lock_guard; /**< @brief Lock guard to avoid spurious or missed unlocks */ bool m_initialised; /**< @brief True when this object is completely initialised */ Cache_Entry * m_cache_entry; /**< @brief Cache entry object. Will not be freed by child thread. */ } THREAD_DATA; static std::unique_ptr cache; /**< @brief Global cache manager object */ static std::atomic_bool thread_exit; /**< @brief Used for shutdown: if true, forcibly exit all threads */ static bool transcode(std::shared_ptr thread_data, Cache_Entry *cache_entry, FFmpeg_Transcoder & transcoder, bool *timeout); static int transcoder_thread(std::shared_ptr thread_data); static int start_transcoder_thread(Cache_Entry* cache_entry); static bool transcode_until(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no); static int transcode_finish(Cache_Entry* cache_entry, FFmpeg_Transcoder & transcoder); static void reopen_finished_incomplete_cache(Cache_Entry* cache_entry, uint32_t item_no, const char* item_name); static bool cached_item_available(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no); static bool invalidate_stale_cache_file(Cache_Entry* cache_entry, uint32_t segment_no, uint32_t item_no, const char* item_name); static void wait_for_active_transcoder(Cache_Entry* cache_entry, uint32_t item_no, const char* item_name); /** * @brief Transcode the buffer until the buffer has enough or until an error occurs. * The buffer needs at least 'end' bytes before transcoding stops. Returns true * if no errors and false otherwise. * @param[in] cache_entry - corresponding cache entry * @param[in] offset - byte offset to start reading at * @param[in] len - length of data chunk to be read. * @param[in] segment_no - HLS segment file number. * @return On success, returns true. Returns false if an error occurred. */ static bool transcode_until(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no) { bool success = false; if (cached_item_available(cache_entry, offset, len, segment_no)) { return true; } try { // Wait until decoder thread has reached the desired position if (cache_entry->m_is_decoding) { bool reported = false; while (!cached_item_available(cache_entry, offset, len, segment_no) && !cache_entry->m_cache_info.m_error) { if (fuse_interrupted()) { Logging::info(cache_entry->virtname(), "The client has gone away."); errno = 0; // No error break; } if (thread_exit) { Logging::warning(cache_entry->virtname(), "Thread exit was received."); errno = EINTR; throw false; } if (!reported) { if (!segment_no) { Logging::trace(cache_entry->virtname(), "Cache miss at offset %1 with length %2.", offset, len); } else { Logging::trace(cache_entry->virtname(), "Cache miss at offset %1 with length %2 for segment no. %3.", offset, len, segment_no); } reported = true; } if (segment_no && !cache_entry->m_is_decoding) { // The worker may have finished just after this read entered // the wait loop. If the requested HLS segment is still not // physically available, do not wait forever: reopen the // incomplete HLS cache and start an on-demand repair worker. cache_entry->m_seek_to_no = segment_no; reopen_finished_incomplete_cache(cache_entry, segment_no, "HLS segment"); int ret = start_transcoder_thread(cache_entry); if (ret) { errno = ret; throw false; } } mssleep(250); } if (reported) { if (!segment_no) { Logging::trace(cache_entry->virtname(), "Cache hit at offset %1 with length %2.", offset, len); } else { Logging::trace(cache_entry->virtname(), "Cache hit at offset %1 with length %2 for segment no. %3.", offset, len, segment_no); } } success = !cache_entry->m_cache_info.m_error && cached_item_available(cache_entry, offset, len, segment_no); } } catch (bool _success) { success = _success; } return success; } /** * @brief Close the input file and free everything but the initial buffer. * @param[in] cache_entry - corresponding cache entry * @param[in] transcoder - Current FFmpeg_Transcoder object. * @return On success, returns 0; on error, a negative AVERROR value. */ static int transcode_finish(Cache_Entry* cache_entry, FFmpeg_Transcoder & transcoder) { int res = transcoder.encode_finish(); if (res < 0) { return res; } // Check encoded buffer size. Does not affect HLS segments. cache_entry->m_cache_info.m_duration = transcoder.duration(); cache_entry->m_cache_info.m_encoded_filesize = cache_entry->m_buffer->buffer_watermark(); cache_entry->m_cache_info.m_video_frame_count = transcoder.video_frame_count(); cache_entry->m_cache_info.m_segment_count = transcoder.segment_count(); cache_entry->m_cache_info.m_result = !transcoder.have_seeked() ? RESULTCODE::FINISHED_SUCCESS : RESULTCODE::FINISHED_INCOMPLETE; cache_entry->m_is_decoding = false; cache_entry->m_cache_info.m_errno = 0; cache_entry->m_cache_info.m_averror = 0; Logging::debug(transcoder.virtname(), "Finishing file."); if (!cache_entry->m_buffer->reserve(cache_entry->m_cache_info.m_encoded_filesize)) { Logging::debug(transcoder.virtname(), "Unable to truncate the buffer."); } if (!transcoder.is_multiformat()) { Logging::debug(transcoder.virtname(), "Predicted size: %1 Final: %2 Diff: %3 (%4%).", format_size_ex(cache_entry->m_cache_info.m_predicted_filesize).c_str(), format_size_ex(cache_entry->m_cache_info.m_encoded_filesize).c_str(), format_result_size_ex(cache_entry->m_cache_info.m_encoded_filesize, cache_entry->m_cache_info.m_predicted_filesize).c_str(), ((static_cast(cache_entry->m_cache_info.m_encoded_filesize) * 1000 / (static_cast(cache_entry->m_cache_info.m_predicted_filesize) + 1)) + 5) / 10); } cache_entry->flush(); return 0; } /** * @brief Re-open an incomplete multi-format cache for an on-demand repair run. * * HLS and frame-set caches may intentionally be marked FINISHED_INCOMPLETE * after a seek/direct access reached EOF. That state means that existing * segments/frames are usable, but missing items may still be generated later. * Before starting such a repair worker, clear only the finished/error state; * do not clear the buffer, because the already valid segments/frames must be * kept. * * @param[inout] cache_entry Cache entry to re-open. * @param[in] item_no Requested HLS segment or frame number. * @param[in] item_name Human-readable item name for logging. */ static void reopen_finished_incomplete_cache(Cache_Entry* cache_entry, uint32_t item_no, const char* item_name) { if (cache_entry == nullptr || !cache_entry->is_finished_incomplete()) { return; } Logging::warning(cache_entry->virtname(), "Re-opening incomplete cache to generate missing %1 no. %2.", item_name, item_no); cache_entry->m_cache_info.m_result = RESULTCODE::NONE; cache_entry->m_cache_info.m_error = false; cache_entry->m_cache_info.m_errno = 0; cache_entry->m_cache_info.m_averror = 0; } /** * @brief Invalidate a stale physical cache file and make the entry eligible for recoding. * * The cache database/in-memory state may still say that a file or segment is * available even though the physical cache file has been deleted or truncated. * In that case the on-disk state wins: the affected cache item is invalidated * and the cache entry is re-opened for transcoding/repair. * * @param[inout] cache_entry Cache entry containing the stale cache file. * @param[in] segment_no HLS segment number, or 0 for the single cache file used by normal files and frame sets. * @param[in] item_no Human-readable item number used in the log message. * @param[in] item_name Human-readable item name used in the log message. * @return Always returns false, so the caller can directly use it as a cache-miss result. */ static bool invalidate_stale_cache_file(Cache_Entry* cache_entry, uint32_t segment_no, uint32_t item_no, const char* item_name) { if (cache_entry == nullptr || cache_entry->m_buffer == nullptr) { errno = EINVAL; return false; } const std::string cachefile = cache_entry->m_buffer->cachefile(segment_no); if (item_no) { Logging::warning(cache_entry->virtname(), "Cached %1 no. %2 is marked available, but cache file '%3' is missing or empty. Recreating it.", item_name, item_no, cachefile.c_str()); } else { Logging::warning(cache_entry->virtname(), "Cached file is marked available, but cache file '%1' is missing or empty. Recreating it.", cachefile.c_str()); } cache_entry->m_buffer->invalidate_segment(segment_no); cache_entry->m_cache_info.m_result = RESULTCODE::NONE; cache_entry->m_cache_info.m_error = false; cache_entry->m_cache_info.m_errno = 0; cache_entry->m_cache_info.m_averror = 0; cache_entry->m_suspend_timeout = false; if (!segment_no) { cache_entry->m_cache_info.m_encoded_filesize = 0; } const FFmpegfs_Format *current_format = params.current_format(cache_entry->virtualfile()); if (item_no && current_format != nullptr && current_format->is_multiformat()) { cache_entry->m_seek_to_no = item_no; } return false; } /** * @brief Wait until an already active transcoder worker has left the cache entry. * * Stale physical HLS files are sometimes detected in a very small race window: * the worker has just finished writing a seeked/incomplete HLS run, but still * owns the cache entry while the client immediately requests an older segment. * Starting a repair worker in that window is intentionally rejected by * start_transcoder_thread(), because a real worker is still active. If we then * continue waiting on the missing segment, no later worker is started and the * request can stall. * * For stale-cache repair we therefore wait until the active worker has really * left the cache entry, and only then invalidate and start the repair run. * Normal sequential reads do not use this path. * * @param[in] cache_entry Cache entry whose worker should become idle. * @param[in] item_no Human-readable item number used in the log message. * @param[in] item_name Human-readable item name used in the log message. */ static void wait_for_active_transcoder(Cache_Entry* cache_entry, uint32_t item_no, const char* item_name) { if (cache_entry == nullptr || !cache_entry->m_is_decoding) { return; } if (item_no) { Logging::debug(cache_entry->virtname(), "Waiting for active transcoder to finish before repairing stale %1 no. %2.", item_name, item_no); } else { Logging::debug(cache_entry->virtname(), "Waiting for active transcoder to finish before repairing stale %1.", item_name); } std::unique_lock lock_active_mutex(cache_entry->m_active_mutex); } /** * @brief Check whether the requested cache data is available and physically readable. * * This combines the logical cache state (finished cache, finished HLS segment, * or enough bytes already written) with a physical cache-file check. A stale * database entry must not result in an empty file being served to the client. * * @param[inout] cache_entry Cache entry to check. * @param[in] offset Requested byte offset. * @param[in] len Requested byte count. * @param[in] segment_no HLS segment number, or 0 for normal files/frame sets. * @return Returns true if the requested data can be read from the cache. */ static bool cached_item_available(Cache_Entry* cache_entry, size_t offset, size_t len, uint32_t segment_no) { if (cache_entry == nullptr || cache_entry->m_buffer == nullptr) { errno = EINVAL; return false; } const size_t end = offset + len; // For HLS each segment is materialised independently. A // FINISHED_INCOMPLETE cache only means that a seeked/direct-access run // reached EOF; it must not imply that earlier skipped segments exist. // Only a fully successful, non-seeked HLS transcode may make all segment // files logically available through the cache entry result. Otherwise the // per-segment finished flag or the current write position is authoritative. const bool logically_available = segment_no ? (cache_entry->is_finished_success() || cache_entry->m_buffer->is_segment_finished(segment_no) || cache_entry->m_buffer->tell(segment_no) >= end) : (cache_entry->is_finished() || cache_entry->m_buffer->tell(segment_no) >= end); if (!logically_available) { return false; } if (cache_entry->m_buffer->cachefile_valid(segment_no)) { return true; } return invalidate_stale_cache_file(cache_entry, segment_no, segment_no, segment_no ? "segment" : "file"); } void transcoder_cache_path(std::string * path) { if (params.m_cachepath.size()) { *path = params.m_cachepath; } else { if (geteuid() == 0) { // Running as root *path = "/var/cache"; } else { // Running as regular user, put cache in home dir if (const char* cache_home = std::getenv("XDG_CACHE_HOME")) { *path = cache_home; } else { expand_path(path, "~/.cache"); } } } append_sep(path); *path += PACKAGE; append_sep(path); } bool transcoder_init() { if (cache == nullptr) { Logging::debug(nullptr, "Creating new media file cache."); cache = std::make_unique(); if (cache == nullptr) { Logging::error(nullptr, "Unable to create new media file cache. Out of memory."); std::fprintf(stderr, "ERROR: Creating new media file cache. Out of memory.\n"); return false; } if (!cache->load_index()) { std::fprintf(stderr, "ERROR: Creating media file cache failed.\n"); return false; } } return true; } void transcoder_free() { if (cache != nullptr) { cache.reset(); Logging::debug(nullptr, "Deleting media file cache."); } } bool transcoder_cached_filesize(LPVIRTUALFILE virtualfile, struct stat *stbuf) { Cache_Entry* cache_entry = cache->openio(virtualfile); if (cache_entry == nullptr) { return false; } size_t encoded_filesize = cache_entry->m_cache_info.m_encoded_filesize; if (!encoded_filesize) { // If not yet encoded, return predicted file size encoded_filesize = cache_entry->m_cache_info.m_predicted_filesize; } if (encoded_filesize) { stat_set_size(stbuf, encoded_filesize); return true; } else { return false; } } bool transcoder_set_filesize(LPVIRTUALFILE virtualfile, int64_t duration, BITRATE audio_bit_rate, int channels, int sample_rate, AVSampleFormat sample_format, BITRATE video_bit_rate, int width, int height, bool interleaved, const AVRational &framerate) { Cache_Entry* cache_entry = cache->openio(virtualfile); if (cache_entry == nullptr) { Logging::error(nullptr, "Out of memory getting file size."); return false; } const FFmpegfs_Format *current_format = params.current_format(virtualfile); if (current_format == nullptr) { Logging::error(cache_entry->virtname(), "Internal error getting file size."); return false; } size_t filesize = 0; if (!FFmpeg_Transcoder::audio_size(&filesize, current_format->audio_codec(), audio_bit_rate, duration, channels, sample_rate, sample_format)) { Logging::warning(cache_entry->virtname(), "Unsupported audio codec '%1' for format %2.", get_codec_name(current_format->audio_codec()), current_format->desttype().c_str()); } if (!FFmpeg_Transcoder::video_size(&filesize, current_format->video_codec(), video_bit_rate, duration, width, height, interleaved, framerate)) { Logging::warning(cache_entry->virtname(), "Unsupported video codec '%1' for format %2.", get_codec_name(current_format->video_codec()), current_format->desttype().c_str()); } if (!FFmpeg_Transcoder::total_overhead(&filesize, current_format->filetype())) { Logging::warning(cache_entry->virtname(), "Unsupported file type '%1' for format %2.", get_filetype_text(current_format->filetype()).c_str(), current_format->desttype().c_str()); } cache_entry->m_cache_info.m_predicted_filesize = virtualfile->m_predicted_size = filesize; Logging::trace(cache_entry->virtname(), "Predicted transcoded size of %1.", format_size_ex(cache_entry->m_cache_info.m_predicted_filesize).c_str()); return true; } bool transcoder_predict_filesize(LPVIRTUALFILE virtualfile, Cache_Entry* cache_entry) { FFmpeg_Transcoder transcoder; bool success = false; if (transcoder.open_input_file(virtualfile) >= 0) { if (cache_entry != nullptr) { cache_entry->m_cache_info.m_predicted_filesize = transcoder.predicted_filesize(); cache_entry->m_cache_info.m_video_frame_count = transcoder.video_frame_count(); cache_entry->m_cache_info.m_segment_count = transcoder.segment_count(); cache_entry->m_cache_info.m_duration = transcoder.duration(); } Logging::trace(transcoder.filename(), "Predicted transcoded size of %1.", format_size_ex(transcoder.predicted_filesize()).c_str()); transcoder.closeio(); success = true; } return success; } /** * @brief Start the transcoder thread for an existing cache entry. * * For HLS/frame-set partial cache repair, the caller may already have placed * the requested segment/frame number in m_seek_to_no. In that case transcode() * keeps existing segment files and starts directly at the requested item. * * @param[inout] cache_entry Cache entry to transcode. * @return 0 on success, otherwise errno-compatible error code. */ static int start_transcoder_thread(Cache_Entry* cache_entry) { if (cache_entry == nullptr) { return EINVAL; } bool expected_is_decoding = false; if (!cache_entry->m_is_decoding.compare_exchange_strong(expected_is_decoding, true)) { std::unique_lock lock_active_mutex(cache_entry->m_active_mutex, std::try_to_lock); if (!lock_active_mutex.owns_lock()) { Logging::warning(cache_entry->virtname(), "Transcoder thread already running."); return 0; } Logging::warning(cache_entry->virtname(), "Transcoder thread was marked as running, but no active worker owns the cache entry. Resetting decoding state and starting a new worker."); cache_entry->m_is_decoding = false; expected_is_decoding = false; if (!cache_entry->m_is_decoding.compare_exchange_strong(expected_is_decoding, true)) { Logging::warning(cache_entry->virtname(), "Transcoder thread already running."); return 0; } } const FFmpegfs_Format *current_format = params.current_format(cache_entry->virtualfile()); if (current_format != nullptr && current_format->is_multiformat() && !cache_entry->virtualfile()->get_segment_count() && !cache_entry->m_cache_info.m_segment_count) { // HLS/frame-set buffers need a known segment/frame count before // Buffer::init() can create the cache file table. With deferred // transcoding this may not have happened during open(), so predict // the source properties now, before the worker calls openio(true). if (!transcoder_predict_filesize(cache_entry->virtualfile(), cache_entry)) { int ret = errno; if (!ret) { ret = EIO; } cache_entry->m_is_decoding = false; return ret; } } Logging::debug(cache_entry->filename(), "Starting transcoder thread."); std::shared_ptr thread_data = std::make_shared(); thread_data->m_initialised = false; thread_data->m_cache_entry = cache_entry; thread_data->m_thread_running_lock_guard = false; { std::unique_lock lock_thread_running_mutex(thread_data->m_thread_running_mutex); tp->schedule_thread(std::bind(&transcoder_thread, thread_data)); // Let decoder get into gear before returning from open/read. while (!thread_data->m_thread_running_lock_guard) { thread_data->m_thread_running_cond.wait(lock_thread_running_mutex); } } if (cache_entry->m_cache_info.m_error) { int ret = cache_entry->m_cache_info.m_errno; if (!ret) { ret = EIO; } return ret; } Logging::debug(cache_entry->filename(), "Transcoder thread is running."); return 0; } Cache_Entry* transcoder_new(LPVIRTUALFILE virtualfile, bool begin_transcode) { // Allocate transcoder structure Cache_Entry* cache_entry = cache->openio(virtualfile); if (cache_entry == nullptr) { return nullptr; } Logging::trace(cache_entry->filename(), "Creating transcoder object."); try { cache_entry->lock(); const FFmpegfs_Format *current_format = params.current_format(virtualfile); const bool create_cache = begin_transcode || (current_format != nullptr && current_format->is_multiformat() && virtualfile->get_segment_count() != 0); if (!cache_entry->openio(create_cache)) { throw static_cast(errno); } if (params.m_disable_cache) { // Disable cache cache_entry->clear(); } else if (!cache_entry->m_is_decoding && cache_entry->outdated()) { cache_entry->clear(); } if (cache_entry->m_cache_info.m_duration) { virtualfile->m_duration = cache_entry->m_cache_info.m_duration; } if (cache_entry->m_cache_info.m_predicted_filesize) { virtualfile->m_predicted_size = cache_entry->m_cache_info.m_predicted_filesize; } if (cache_entry->m_cache_info.m_video_frame_count) { virtualfile->m_video_frame_count = cache_entry->m_cache_info.m_video_frame_count; } if (!cache_entry->m_is_decoding && !cache_entry->is_finished_success()) { if (begin_transcode) { int ret = start_transcoder_thread(cache_entry); if (ret) { Logging::trace(cache_entry->filename(), "Transcoder error!"); throw ret; } } else if (!cache_entry->m_cache_info.m_predicted_filesize) { if (!transcoder_predict_filesize(virtualfile, cache_entry)) { throw static_cast(errno); } } } else if (begin_transcode) { Logging::info(cache_entry->virtname(), "Reading file from cache."); } cache_entry->unlock(); } catch (int orgerrno) { cache_entry->m_is_decoding = false; cache_entry->unlock(); cache->closeio(&cache_entry, CACHE_CLOSE_DELETE); cache_entry = nullptr; // Make sure to return NULL here even if the cache could not be deleted now (still in use) errno = orgerrno; // Restore last errno } return cache_entry; } bool transcoder_read(Cache_Entry* cache_entry, char* buff, size_t offset, size_t len, int * bytes_read, uint32_t segment_no) { bool success = true; if (!segment_no) { Logging::trace(cache_entry->virtname(), "Reading %1 bytes from offset %2 to %3.", len, offset, len + offset); } else { Logging::trace(cache_entry->virtname(), "Reading %1 bytes from offset %2 to %3 for segment no. %4.", len, offset, len + offset, segment_no); } // Store access time cache_entry->update_access(); // Update read counter cache_entry->update_read_count(); try { // For HLS partial/seeked caches, FINISHED_INCOMPLETE must not mark // skipped segments as complete. Only FINISHED_SUCCESS covers all // segments; otherwise the per-segment finished flag decides. const bool segment_logically_complete = segment_no && (cache_entry->m_buffer->is_segment_finished(segment_no) || cache_entry->is_finished_success()); bool segment_complete = segment_logically_complete; bool repair_requested = false; if (segment_logically_complete && !cache_entry->m_buffer->cachefile_valid(segment_no)) { wait_for_active_transcoder(cache_entry, segment_no, "segment"); if (!cache_entry->m_buffer->cachefile_valid(segment_no)) { invalidate_stale_cache_file(cache_entry, segment_no, segment_no, "segment"); segment_complete = false; repair_requested = true; } } if (!segment_no && cache_entry->is_finished() && !cache_entry->m_buffer->cachefile_valid(0)) { wait_for_active_transcoder(cache_entry, 0, "file"); if (!cache_entry->m_buffer->cachefile_valid(0)) { invalidate_stale_cache_file(cache_entry, 0, 0, "file"); repair_requested = true; } } const bool segment_missing = segment_no && !segment_complete; // For HLS partial caches, an incomplete cache is still usable. Only // start/restart the transcoder when the requested segment is not // finished yet. Physical cache files are checked as well: if a segment // is marked as finished in memory/the cache index but the actual file // has been deleted or truncated, it is treated as missing and repaired. if (segment_missing) { if (!cache_entry->m_is_decoding) { // Segment 1 naturally starts at the beginning. For later HLS // segments, stack the target before the worker starts so a // direct request does not first encode segment 1 and only seek // afterwards. cache_entry->m_seek_to_no = segment_no; // A FINISHED_INCOMPLETE HLS cache may still contain useful // segments, but a missing requested segment needs a fresh // repair worker. Only re-open it when no worker is active; an // active worker must keep its current cache state. reopen_finished_incomplete_cache(cache_entry, segment_no, "HLS segment"); } else { // A running HLS transcoder can satisfy large jumps by // consuming m_seek_to_no in transcode(). However, during // normal linear playback the current or immediately following // segment is often requested before it has been finalised. Do // not queue a seek for those normal sequential reads: doing so // can make the encoder reopen the same segment and corrupt the // cache file. // // Only queue a seek while the worker is active if the request // is farther away than the configured minimum seek distance. // The FFmpeg_Transcoder still performs its own final check and // logs discarded short seeks. const uint32_t current_segment = cache_entry->m_buffer->current_segment_no(); if (current_segment && segment_no > current_segment) { uint32_t min_seek_segments = 0; if (params.m_segment_duration > 0) { min_seek_segments = static_cast(params.m_min_seek_time_diff / params.m_segment_duration); } if (segment_no > current_segment + min_seek_segments) { cache_entry->m_seek_to_no = segment_no; } } } } if (repair_requested || (!cache_entry->m_is_decoding && (segment_missing || (!cache_entry->is_finished_success() && !(cache_entry->is_finished_incomplete() && !segment_missing))))) { int ret = start_transcoder_thread(cache_entry); if (ret) { errno = ret; throw false; } } if (!cache_entry->is_finished_success() && !cache_entry->is_finished_incomplete()) { switch (params.current_format(cache_entry->virtualfile())->filetype()) { case FILETYPE::MP3: { // If we are encoding to MP3 and the requested data overlaps the ID3v1 tag // at the end of the file, do not encode data first up to that position. // This optimises the case where applications read the end of the file // first to read the ID3v1 tag. if ((offset > cache_entry->m_buffer->tell(segment_no)) && (offset + len >= (cache_entry->size() - ID3V1_TAG_LENGTH))) { // Stuff buffer with garbage, apps won't try to play that chunk anyway. std::memset(buff, 0xFF, len); if (cache_entry->size() - offset == ID3V1_TAG_LENGTH) { std::memcpy(buff, &cache_entry->m_id3v1, std::min(len, ID3V1_TAG_LENGTH)); } errno = 0; throw true; // OK } break; } default: { break; } } // Windows seems to access the files on Samba drives starting at the last 64K segment simply when // the file is opened. Setting win_smb_fix=1 will ignore these attempts (not decode the file up // to this point). // Access will only be ignored if occurring at the second access. if (params.m_win_smb_fix && cache_entry->read_count() == 2) { if ((offset > cache_entry->m_buffer->tell(segment_no)) && (len <= 65536) && check_ignore(cache_entry->size(), offset) && ((offset + len) > (cache_entry->size()))) { Logging::warning(cache_entry->virtname(), "Ignoring Windows' groundless access to the last 8K boundary of the file."); errno = 0; *bytes_read = 0; // We've read nothing len = 0; throw true; // OK } } } success = transcode_until(cache_entry, offset, len, segment_no); if (!success) { errno = cache_entry->m_cache_info.m_errno ? cache_entry->m_cache_info.m_errno : EIO; throw false; } // Open for reading if necessary. This intentionally happens after // transcode_until(), so a missing HLS segment is created by the // transcoder, not by a read-only cache probe. if (!cache_entry->m_buffer->open_file(segment_no, CACHE_FLAG_RO)) { throw false; } // truncate if we didn't actually get len if (cache_entry->m_buffer->buffer_watermark(segment_no) < offset) { len = 0; } else if (cache_entry->m_buffer->buffer_watermark(segment_no) < offset + len) { len = cache_entry->m_buffer->buffer_watermark(segment_no) - offset; } if (len) { if (!cache_entry->m_buffer->copy(reinterpret_cast(buff), offset, len, segment_no)) { len = 0; // We already capped len to not overread the buffer, so it is an error if we end here. throw false; } if (cache_entry->m_cache_info.m_error) { errno = cache_entry->m_cache_info.m_errno ? cache_entry->m_cache_info.m_errno : EIO; throw false; } } errno = 0; } catch (bool _success) { success = _success; } *bytes_read = static_cast(len); return success; } bool transcoder_read_frame(Cache_Entry* cache_entry, char* buff, size_t offset, size_t len, uint32_t frame_no, int * bytes_read, LPVIRTUALFILE virtualfile) { bool success = false; Logging::trace(cache_entry->virtname(), "Reading %1 bytes from offset %2 to %3 for frame no. %4.", len, offset, len + offset, frame_no); // Store access time cache_entry->update_access(); // Update read counter cache_entry->update_read_count(); try { if (cache_entry->is_finished() && !cache_entry->m_buffer->cachefile_valid(0)) { invalidate_stale_cache_file(cache_entry, 0, frame_no, "frame-set cache"); cache_entry->m_seek_to_no = frame_no; } // Open for reading if necessary. For frame sets this only opens the // frame index/cache for probing; a missing frame still has to start the // transcoder below. if (!cache_entry->m_buffer->open_file(0, CACHE_FLAG_RO)) { throw false; } std::vector data; // Try to read the requested frame first. Frame-set files are opened // through their parent object without starting the transcoder, because // the exact frame number is only known here. Therefore a cache miss // must start or wake the transcoder from this read path. success = cache_entry->m_buffer->read_frame(&data, frame_no); if (!success) { if (errno != EAGAIN) { Logging::error(cache_entry->virtname(), "Reading image frame no. %1: (%2) %3", frame_no, errno, strerror(errno)); throw false; } bool reported = false; // Stack the requested frame before starting the worker so a direct // frame-set access does not begin at frame 1 first. cache_entry->m_seek_to_no = frame_no; // A FINISHED_INCOMPLETE frame-set cache means that some frames are // valid, but the requested missing frame still has to be generated. // Clear the finished state before starting the repair worker, // otherwise transcode() exits immediately. reopen_finished_incomplete_cache(cache_entry, frame_no, "frame"); if (!cache_entry->m_is_decoding) { int ret = start_transcoder_thread(cache_entry); if (ret) { errno = ret; throw false; } } int retries = TOTAL_RETRIES; while (!cache_entry->m_buffer->read_frame(&data, frame_no) && !thread_exit) { if (errno != EAGAIN) { Logging::error(cache_entry->virtname(), "Reading image frame no. %1: (%2) %3", frame_no, errno, strerror(errno)); throw false; } // A previous frame-set worker may have reached EOF just after // this request stacked m_seek_to_no. In that case the worker // exits, the requested frame is still missing, and simply // waiting would time out. Start a fresh repair worker for the // same frame as soon as the old worker is no longer decoding. if (!cache_entry->m_is_decoding) { cache_entry->m_seek_to_no = frame_no; reopen_finished_incomplete_cache(cache_entry, frame_no, "frame"); int ret = start_transcoder_thread(cache_entry); if (ret) { errno = ret; throw false; } // The new worker is now responsible for producing the // requested frame, so give it a full retry budget. retries = TOTAL_RETRIES; } if (!cache_entry->m_suspend_timeout) { if (!--retries) { errno = ETIMEDOUT; Logging::error(cache_entry->virtname(), "Timeout reading image frame no. %1: (%2) %3", frame_no, errno, strerror(errno)); throw false; } } else { retries = TOTAL_RETRIES; } if (thread_exit) { Logging::warning(cache_entry->virtname(), "Thread exit was received."); errno = EINTR; throw false; } if (!reported) { Logging::trace(cache_entry->virtname(), "Frame no. %1: Cache miss at offset %<11zu>2 (length %<6u>3).", frame_no, offset, len); reported = true; } mssleep(GRANULARITY); } if (thread_exit) { Logging::warning(cache_entry->virtname(), "Thread exit was received."); errno = EINTR; throw false; } Logging::trace(cache_entry->virtname(), "Frame no. %1: Cache hit at offset %<11zu>2 (length %<6u>3).", frame_no, offset, len); success = !cache_entry->m_cache_info.m_error; } if (success) { if (data.size() < offset) { len = 0; } else if (data.size() < offset + len) { len = data.size() - offset; } if (len) { std::memcpy(buff, data.data() + offset, len); } stat_set_size(&virtualfile->m_st, data.size()); *bytes_read = static_cast(len); } } catch (bool _success) { success = _success; } return success; } void transcoder_delete(Cache_Entry* cache_entry) { cache->closeio(&cache_entry); } size_t transcoder_get_size(Cache_Entry* cache_entry) { return cache_entry->size(); } size_t transcoder_buffer_watermark(Cache_Entry* cache_entry, uint32_t segment_no) { return cache_entry->m_buffer->buffer_watermark(segment_no); } size_t transcoder_buffer_tell(Cache_Entry* cache_entry, uint32_t segment_no) { return cache_entry->m_buffer->tell(segment_no); } void transcoder_exit() { thread_exit = true; } bool transcoder_cache_maintenance() { if (cache != nullptr) { return cache->maintenance(); } else { return false; } } bool transcoder_cache_clear() { if (cache != nullptr) { return cache->clear(); } else { return false; } } /** * @brief Actually transcode file * @param[inout] thread_data - Thread data with lock objects * @param[inout] cache_entry - Underlying thread entry * @param[in] transcoder - Transcoder object for transcoding * @param[out] timeout - True if transcoding timed out, false if not * @return On success, returns true; on error, returns false */ static bool transcode(std::shared_ptr thread_data, Cache_Entry *cache_entry, FFmpeg_Transcoder & transcoder, bool *timeout) { int averror = 0; int syserror = 0; bool success = true; const bool partial_multiformat_recode = cache_entry->m_seek_to_no != 0 && params.current_format(cache_entry->virtualfile())->is_multiformat(); if (partial_multiformat_recode) { // HLS/frame-set repair run: keep existing segments/frames and start // directly at the requested item. Existing later items may be // overwritten by the encoder; missing earlier gaps intentionally stay // missing. cache_entry->m_cache_info.m_result = RESULTCODE::NONE; cache_entry->m_cache_info.m_error = false; cache_entry->m_cache_info.m_errno = 0; cache_entry->m_cache_info.m_averror = 0; } else { // Full transcode run: remove any older remains. cache_entry->clear(); } // Must decode the file, otherwise simply use cache cache_entry->m_is_decoding = true; try { bool unlocked = false; Logging::info(cache_entry->filename(), "Transcoding to %1.", params.current_format(cache_entry->virtualfile())->desttype().c_str()); if (!cache_entry->openio()) { throw (static_cast(errno)); } averror = transcoder.open_input_file(cache_entry->virtualfile()); if (averror < 0) { throw (static_cast(errno)); } if (!cache_entry->m_cache_info.m_duration) { cache_entry->m_cache_info.m_duration = transcoder.duration(); } if (!cache_entry->m_cache_info.m_predicted_filesize) { cache_entry->m_cache_info.m_predicted_filesize = transcoder.predicted_filesize(); } if (!cache_entry->m_cache_info.m_video_frame_count) { cache_entry->m_cache_info.m_video_frame_count = transcoder.video_frame_count(); } if (!cache_entry->m_cache_info.m_segment_count) { cache_entry->m_cache_info.m_segment_count = transcoder.segment_count(); } if (cache != nullptr && !cache->maintenance(transcoder.predicted_filesize())) { throw (static_cast(errno)); } if (transcoder.is_hls()) { const uint32_t segment_no = cache_entry->m_seek_to_no; if (segment_no) { // Initial direct HLS access: stack the requested segment before // opening the output. open_output() consumes this immediately // and seeks the input before segment 1 is opened/written. cache_entry->m_seek_to_no = 0; averror = transcoder.stack_seek_segment(segment_no); if (averror < 0) { throw (static_cast(errno)); } } } averror = transcoder.open_output_file(cache_entry->m_buffer.get()); if (averror < 0) { throw (static_cast(errno)); } std::memcpy(&cache_entry->m_id3v1, transcoder.id3v1tag(), sizeof(ID3v1)); thread_data->m_initialised = true; unlocked = false; if ((!params.m_prebuffer_size && !params.m_prebuffer_time) || transcoder.is_frameset()) { // Unlock frame set from beginning unlocked = true; thread_data->m_thread_running_lock_guard = true; thread_data->m_thread_running_cond.notify_all(); // signal that we are running } else { if (params.m_prebuffer_time) { Logging::debug(cache_entry->virtname(), "Pre-buffering up to %1.", format_time(params.m_prebuffer_time).c_str()); } if (params.m_prebuffer_size) { Logging::debug(cache_entry->virtname(), "Pre-buffering up to %1.", format_size(params.m_prebuffer_size).c_str()); } } cache_entry->m_suspend_timeout = false; while (!cache_entry->is_finished() && !(*timeout = cache_entry->decode_timeout()) && !thread_exit) { DECODER_STATUS status = DECODER_STATUS::DEC_SUCCESS; if (cache_entry->ref_count() > 1) { // Set last access time cache_entry->update_access(false); } if (transcoder.is_frameset()) { uint32_t frame_no = cache_entry->m_seek_to_no; if (frame_no) { cache_entry->m_seek_to_no = 0; averror = transcoder.stack_seek_frame(frame_no); if (averror < 0) { throw (static_cast(errno)); } } } else if (transcoder.is_hls()) { uint32_t segment_no = cache_entry->m_seek_to_no; if (segment_no) { cache_entry->m_seek_to_no = 0; averror = transcoder.stack_seek_segment(segment_no); if (averror < 0) { throw (static_cast(errno)); } } } averror = transcoder.process_single_fr(&status); if (status == DECODER_STATUS::DEC_ERROR) { errno = EIO; throw (static_cast(errno)); } else if (status == DECODER_STATUS::DEC_EOF) { cache_entry->m_suspend_timeout = true; // Suspend read_frame time out until transcoder is reopened. averror = transcode_finish(cache_entry, transcoder); if (averror < 0) { errno = EIO; throw (static_cast(errno)); } } if (!unlocked && cache_entry->m_buffer->buffer_watermark() > params.m_prebuffer_size && transcoder.pts() > static_cast(params.m_prebuffer_time) * AV_TIME_BASE) { unlocked = true; Logging::info(cache_entry->virtname(), "Pre-buffer limit reached."); thread_data->m_thread_running_lock_guard = true; thread_data->m_thread_running_cond.notify_all(); // signal that we are running } if (cache_entry->ref_count() <= 1 && cache_entry->suspend_timeout()) { if (!unlocked && (params.m_prebuffer_size || params.m_prebuffer_time)) { unlocked = true; thread_data->m_thread_running_lock_guard = true; thread_data->m_thread_running_cond.notify_all(); // signal that we are running } Logging::info(cache_entry->virtname(), "Timeout! Transcoding suspended after %1 seconds inactivity.", params.m_max_inactive_suspend); while (cache_entry->suspend_timeout() && !(*timeout = cache_entry->decode_timeout()) && !thread_exit) { mssleep(GRANULARITY); } if (*timeout) { break; } Logging::info(cache_entry->virtname(), "Transcoding resumed."); } } if (!unlocked && (params.m_prebuffer_size || params.m_prebuffer_time)) { Logging::debug(cache_entry->virtname(), "File transcode complete, releasing buffer early: Size %1.", cache_entry->m_buffer->buffer_watermark()); thread_data->m_thread_running_lock_guard = true; thread_data->m_thread_running_cond.notify_all(); // signal that we are running } } catch (int _syserror) { success = false; syserror = _syserror; if (!syserror && averror > -512) { // If no system error reported explicitly, and averror is a POSIX error // (we simply assume that if averror < 512, I think averrors are all higher values) syserror = AVUNERROR(averror); } cache_entry->m_cache_info.m_error = true; if (!syserror) { // If system error is still zero, set to EIO to return at least anything else than success. syserror = EIO; } thread_data->m_thread_running_lock_guard = true; thread_data->m_thread_running_cond.notify_all(); // unlock main thread } cache_entry->m_suspend_timeout = false; // Should end that suspension; otherwise, read may hang. cache_entry->m_cache_info.m_errno = syserror; // Preserve errno cache_entry->m_cache_info.m_averror = averror; // Preserve averror transcoder.closeio(); return success; } /** * @brief Log the final result of a transcoder worker run. * * Emits exactly one final status message for a transcoder thread after the * worker has finished, timed out, been asked to exit, or failed. * * Successful transcodes include the elapsed runtime in milliseconds. Timeout, * thread-exit, and error cases keep their specific diagnostic messages, with * system and FFmpeg error details emitted when available in the cache entry. * * @param cache_entry Cache entry associated with the transcoder worker. If this * is @c nullptr, no message is emitted. * @param transcoder Transcoder instance used to decide whether this was a * seeked partial multi-format run. * @param timeout Set to @c true if transcoding was aborted because the * inactivity timeout expired. * @param success Set to @c true if transcoding completed successfully. * @param start_time Monotonic start time captured when the transcoder worker * started; used to calculate elapsed runtime for successful * transcodes. */ static void log_transcoding_result(Cache_Entry* cache_entry, const FFmpeg_Transcoder& transcoder, bool timeout, bool success, const std::chrono::steady_clock::time_point& start_time) { if (cache_entry == nullptr) { return; } if (timeout) { Logging::warning(cache_entry->virtname(), "Timeout! Transcoding aborted after %1 seconds inactivity.", params.m_max_inactive_abort); return; } if (thread_exit) { Logging::info(cache_entry->virtname(), "Thread exit! Transcoding aborted."); return; } if (success) { const auto end_time = std::chrono::steady_clock::now(); const auto elapsed_ms = std::chrono::duration(end_time - start_time).count(); char elapsed_ms_text[32]; std::snprintf(elapsed_ms_text, sizeof(elapsed_ms_text), "%.1f", elapsed_ms); Logging::info(cache_entry->virtname(), "Transcoding completed successfully after %1 ms.", elapsed_ms_text); return; } Logging::error(cache_entry->virtname(), "Transcoding exited with error."); if (cache_entry->m_cache_info.m_errno) { Logging::error(cache_entry->virtname(), "System error: (%1) %2", cache_entry->m_cache_info.m_errno, strerror(cache_entry->m_cache_info.m_errno)); } if (cache_entry->m_cache_info.m_averror) { Logging::error(cache_entry->virtname(), "FFMpeg error: (%1) %2", cache_entry->m_cache_info.m_averror, ffmpeg_geterror(cache_entry->m_cache_info.m_averror).c_str()); } } /** * @brief Transcoding thread * @param[in] thread_data - Corresponding thread data object. * @returns Returns 0 on success; or errno code on error. */ static int transcoder_thread(std::shared_ptr thread_data) { Cache_Entry * cache_entry = thread_data->m_cache_entry; FFmpeg_Transcoder transcoder; bool timeout = false; bool success = true; const auto start_time = std::chrono::steady_clock::now(); std::unique_lock lock_active_mutex(cache_entry->m_active_mutex); std::unique_lock lock_restart_mutex(cache_entry->m_restart_mutex); uint32_t seek_frame = 0; do { if (seek_frame) { Logging::error(transcoder.virtname(), "Transcoder completed with last seek frame to %1. Transcoder is being restarted.", seek_frame); } success = transcode(thread_data, cache_entry, transcoder, &timeout); seek_frame = cache_entry->m_seek_to_no != 0 ? cache_entry->m_seek_to_no.load() : transcoder.last_seek_frame_no(); if (transcoder.is_multiformat() && transcoder.have_seeked()) { // HLS/frame-set caches are intentionally allowed to remain // incomplete after a seek. Missing segments/frames are repaired // on demand; restarting from segment/frame 1 here would recreate // the old loop: transcode, invalidate/restart, seek, repeat. seek_frame = 0; } } while (success && !thread_exit && cache != nullptr && seek_frame); cache_entry->m_is_decoding = false; if (timeout || thread_exit || transcoder.have_seeked()) { if (!transcoder.have_seeked()) { cache_entry->m_cache_info.m_error = true; cache_entry->m_cache_info.m_errno = EIO; // Report I/O error } else { // Must restart from scratch, but this is not an error. cache_entry->m_cache_info.m_error = false; cache_entry->m_cache_info.m_errno = 0; cache_entry->m_cache_info.m_averror = 0; } } else { cache_entry->m_cache_info.m_error = !success; if (success) { cache_entry->m_cache_info.m_errno = 0; cache_entry->m_cache_info.m_averror = 0; } } log_transcoding_result(cache_entry, transcoder, timeout, success, start_time); int _errno = cache_entry->m_cache_info.m_errno; if (cache != nullptr) { cache->closeio(&cache_entry, timeout ? CACHE_CLOSE_DELETE : CACHE_CLOSE_NOOPT); } thread_data.reset(); errno = _errno; return errno; } ffmpegfs-2.50/src/ffmpeg_profiles.cc0000664000175000017500000006002015177713600013172 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #include "ffmpeg_transcoder.h" /** * @file ffmpeg_profiles.cc * @brief Make audience audience specific optimisations * @see https://www.ffmpeg.org/ffmpeg-formats.html#mov_002c-mp4_002c-ismv * * MOV/MP4/ISMV (Smooth Streaming) muxer * ===================================== * * The mov/mp4/ismv muxer supports fragmentation. Normally, a MOV/MP4 file has all the metadata about all packets * stored in one location (written at the end of the file, it can be moved to the start for better playback by * adding faststart to the movflags, or using the qt-faststart tool). A fragmented file consists of a number of * fragments, where packets and metadata about these packets are stored together. Writing a fragmented file has * the advantage that the file is decodable even if the writing is interrupted (while a normal MOV/MP4 is undecodable * if it is not properly finished), and it requires less memory when writing very long files (since writing normal * MOV/MP4 files stores info about every single packet in memory until the file is closed). The downside is that it * is less compatible with other applications. * * Options * ------- * * Fragmentation is enabled by setting one of the AVOptions that define how to cut the file into fragments: * * -moov_size bytes * * Reserves space for the moov atom at the beginning of the file instead of placing the moov atom at the end. * If the space reserved is insufficient, muxing will fail. * * -movflags frag_keyframe * * Start a new fragment at each video keyframe. * * -frag_duration duration * * Create fragments that are duration microseconds long. * * -frag_size size * * Create fragments that contain up to size bytes of payload data. * * -movflags frag_custom * * Allow the caller to manually choose when to cut fragments, by calling av_write_frame(ctx, nullptr) to write a * fragment with the packets written so far. (This is only useful with other applications integrating libavformat, * not from ffmpeg.) * * -min_frag_duration duration * * Don’t create fragments that are shorter than duration microseconds long. * * If more than one condition is specified, fragments are cut when one of the specified conditions is fulfilled. * The exception to this is -min_frag_duration, which has to be fulfilled for any of the other conditions to apply. * * Additionally, the way the output file is written can be adjusted through a few other options: * * -movflags empty_moov * * Write an initial moov atom directly at the start of the file, without describing any samples in it. Generally, * an mdat/moov pair is written at the start of the file, as a normal MOV/MP4 file, containing only a short portion * of the file. With this option set, there is no initial mdat atom, and the moov atom only describes the tracks * but has a zero duration. * * This option is implicitly set when writing ismv (Smooth Streaming) files. * * -movflags separate_moof * * Write a separate moof (movie fragment) atom for each track. Normally, packets for all tracks are written in a * moof atom (which is slightly more efficient), but with this option set, the muxer writes one moof/mdat pair for * each track, making it easier to separate tracks. * * This option is implicitly set when writing ismv (Smooth Streaming) files. * * -movflags faststart * * Run a second pass moving the index (moov atom) to the beginning of the file. This operation can take a while, * and will not work in various situations such as fragmented output, thus it is not enabled by default. * * -movflags rtphint * * Add RTP hinting tracks to the output file. * * -movflags disable_chpl * * Disable Nero chapter markers (chpl atom). Normally, both Nero chapters and a QuickTime chapter track are written * to the file. With this option set, only the QuickTime chapter track will be written. Nero chapters can cause * failures when the file is reprocessed with certain tagging programs, like mp3Tag 2.61a and iTunes 11.3, most likely * other versions are affected as well. * * -movflags omit_tfhd_offset * * Do not write any absolute base_data_offset in tfhd atoms. This avoids tying fragments to absolute byte positions * in the file/streams. * * -movflags default_base_moof * * Similarly to the omit_tfhd_offset, this flag avoids writing the absolute base_data_offset field in tfhd atoms, but * does so by using the new default-base-is-moof flag instead. This flag is new from 14496-12:2012. This may make the * fragments easier to parse in certain circumstances (avoiding basing track fragment location calculations on the * implicit end of the previous track fragment). * * -write_tmcd * * Specify on to force writing a timecode track, off to disable it and auto to write a timecode track only for mov and * mp4 output (default). * * -movflags negative_cts_offsets * * Enables utilization of version 1 of the CTTS box, in which the CTS offsets can be negative. This enables the initial * sample to have DTS/CTS of zero, and reduces the need for edit lists for some cases such as video tracks with B-frames. * Additionally, eases conformance with the DASH-IF interoperability guidelines. * * Possible codec options: * * @code * // -profile:v baseline -level 3.0 * { "profile", "baseline", 0, 0 }, * { "level", "3.0", 0, 0 }, * * // -profile:v high -level 3.1 - REQUIRED FOR PLAYBACK UNDER WIN7 * { "profile", "high", 0, 0 }, * { "level", "3.1", 0, 0 }, * * // Set speed (changes profile!): utra/veryfast and zerolatency * { "preset", "ultrafast", 0, OPT_SW_ONLY }, * { "preset", "veryfast", 0, OPT_SW_ONLY }, * { "tune", "zerolatency", 0, OPT_SW_ONLY }, * @endcode * * Possible format options: * * @code * { "moov_size", "1000000", 0, OPT_ALL }, // bytes * { "movflags", "+frag_keyframe", 0, OPT_ALL }, * { "frag_duration", "1000000", 0, OPT_ALL }, // microseconds * { "frag_size", "100000", 0, OPT_ALL }, // bytes * { "min_frag_duration", "1000", 0, OPT_ALL }, // microseconds * { "movflags", "+empty_moov", 0, OPT_ALL }, * { "movflags", "+delay_moov", 0, OPT_ALL }, * { "movflags", "+separate_moof", 0, OPT_ALL }, * { "movflags", "+faststart", 0, OPT_ALL }, * { "movflags", "+rtphint", 0, OPT_ALL }, * { "movflags", "+disable_chpl", 0, OPT_ALL }, * { "movflags", "+omit_tfhd_offset", 0, OPT_ALL }, * { "movflags", "+default_base_moof", 0, OPT_ALL }, * { "write_tmcd", "on", 0, OPT_ALL }, // on, off or auto * { "movflags", "+negative_cts_offsets", 0, OPT_ALL }, * { "movflags", "+isml", 0, OPT_ALL }, * @endcode * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ // **************************************************************************************************************** // Profiles // **************************************************************************************************************** const FFmpeg_Profiles::PROFILE_LIST_VEC FFmpeg_Profiles::m_profile = { // **************************************************************************************************************** // MP4 Container // **************************************************************************************************************** { FILETYPE::MP4, PROFILE::DEFAULT, //! @brief No opimisations, just plain mp4. MP4 codec options. { // -profile:v high -level 3.1 - REQUIRED FOR PLAYBACK UNDER WIN7. (Partially or totally overwritten by profile!) { "profile", "high", 0, 0 }, { "level", "3.1", 0, 0 }, // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! @brief No optimisations, just plain mp4. MP4 format options. { { "movflags", "+faststart", 0, OPT_ALL }, { "frag_duration", "1000000", 0, OPT_ALL }, // microseconds { "movflags", "+empty_moov", 0, OPT_ALL }, } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_FF, //! @brief Firefox profile: MP4 codec options. //! Use: -movflags +empty_moov @n //! -frag_duration 1000000 (for audio files only) @n //! GOOD: Starts immediately while still decoding. { // -profile:v high -level 3.1 - REQUIRED FOR PLAYBACK UNDER WIN7 (Partially or totally overwritten by profile!) { "profile", "high", 0, 0 }, { "level", "3.1", 0, 0 }, // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, // // @brief Firefox profile: MP4 format options. // { { "frag_duration", "1000000", 0, OPT_AUDIO }, // microseconds { "movflags", "+empty_moov", 0, OPT_ALL }, } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_EDGE, //! //! @brief MS Edge profile: MP4 codec options. //! Use: -movflags +faststart+empty_moov+separate_moof -frag_duration 1000000 @n //! GOOD: Starts immediately while still decoding. //! { // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief MS Edge profile: MP4 format options. //! { { "frag_duration", "1000000", 0, OPT_ALL }, // microseconds { "movflags", "+empty_moov", 0, OPT_ALL }, { "movflags", "+separate_moof", 0, OPT_ALL }, { "movflags", "+faststart", 0, OPT_ALL }, } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_IE, //! //! @brief MS Internet Explorer profile: MP4 codec options. //! //! Use: -movflags +faststart+empty_moov+separate_moof -frag_duration 1000000 @n //! NOT GOOD: Only starts after decode is complete. //! { // -profile:v high -level 3.1 - REQUIRED FOR PLAYBACK UNDER WIN7 (Partially or totally overwritten by profile!) { "profile", "high", 0, 0 }, { "level", "3.1", 0, 0 }, // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief MS Internet Explorer profile: MP4 format options. //! { } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_CHROME, //! //! @brief Google Chrome profile: MP4 codec options. //! //! NOT GOOD: Only starts after decode is complete. //! { // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief Google Chrome profile: MP4 format options. //! { }, }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_SAFARI, //! //! @brief Apple Safari profile: MP4 codec options. //! Safari uses Quicktime for playback. Files must be suitable for playback with Quicktime. //! NOT GOOD: Only starts after decode is complete. Makes crazy things, stops, jumps, gaga. Comes from the inventors of MP4... //! { // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief Apple Safari profile: MP4 format options. //! { } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_OPERA, //! //! @brief Opera profile: MP4 codec options. //! //! NOT GOOD: Only starts after decode is complete. //! { // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief Opera profile: MP4 format options. //! { } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, PROFILE::MP4_MAXTHON, //! //! @brief Maxthon profile: MP4 codec options. //! //! NOT GOOD: Only starts after decode is complete. //! { // -profile:v high -level 3.1 (Partially or totally overwritten by profile!) { "profile", "high", 0, 0 }, { "level", "3.1", 0, 0 }, // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief Maxthon profile: MP4 format options. //! { } }, // **************************************************************************************************************** // MOV container // **************************************************************************************************************** { FILETYPE::MOV, PROFILE::MOV_DEFAULT, //! //! @brief Basic MOV profile: MOV codec options. //! { // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief Basic MOV profile: MOV format options. //! { { "movflags", "+delay_moov", 0, OPT_ALL }, } }, // **************************************************************************************************************** // ProRes/MOV container // **************************************************************************************************************** { FILETYPE::PRORES, PROFILE::PRORES_DEFAULT, //! //! @brief ProRes profile: ProRes/MOV codec options. //! { }, //! //! @brief ProRes profile: ProRes/MOV format options. //! { { "movflags", "+delay_moov", 0, OPT_ALL }, } }, // **************************************************************************************************************** // ProRes/ALAC container // **************************************************************************************************************** { FILETYPE::ALAC, PROFILE::ALAC_DEFAULT, //! //! @brief ALAC profile: ProRes/ALAC codec options. //! { }, //! //! @brief ALAC profile: ProRes/ALAC format options. //! { { "movflags", "+delay_moov", 0, OPT_ALL }, } }, // ---------------------------------------------------------------------------------------------------------------- { FILETYPE::ALAC, PROFILE::ALAC_ITUNES, //! //! @brief ALAC profile: ProRes/ALAC codec options. //! { }, //! //! @brief ALAC profile: ProRes/ALAC format options. //! { //-movflags E……. MOV muxer flags (default 0) // empty_moov E……. Make the initial moov atom empty (not supported by QuickTime) // separate_moof E……. Write separate moof/mdat atoms for each track // isml E……. Create a live smooth streaming feed (for pushing to a publishing point) // faststart E……. Run a second pass to put the index (moov atom) at the beginning of the file // omit_tfhd_offset E……. Omit the base data offset in tfhd atoms //-moov_size E……. maximum moov size so it can be placed at the begin (from 0 to INT_MAX) (default 0) //{ "movflags", "+empty_moov", 0, OPT_ALL }, //{ "movflags", "+delay_moov", 0, OPT_ALL }, //{ "movflags", "+separate_moof", 0, OPT_ALL }, //{ "movflags", "+faststart", 0, OPT_ALL }, // FEHLER: Unable to re-open .m4a output file for shifting data //{ "movflags", "+delay_moov", 0, OPT_ALL }, //{ "moov_size", "200000", 0, OPT_ALL }, // No options: iTunes plays the files, but only after they have been fully recoded. } }, // **************************************************************************************************************** // HLS/ts container // **************************************************************************************************************** { FILETYPE::HLS, PROFILE::HLS_DEFAULT, //! //! @brief HLS/ts codec options. //! { { "profile", "high", 0, 0 }, { "level", "3.1", 0, 0 }, // Set speed (changes profile!) { "preset", "ultrafast", 0, OPT_SW_ONLY }, }, //! //! @brief No optimisations, just plain ts. ts format options. //! { //{ "flags", "+cgop", 0, 0 }, { "movflags", "+faststart", 0, OPT_ALL }, { "frag_duration", "1000000", 0, OPT_ALL }, // microseconds { "movflags", "+empty_moov", 0, OPT_ALL }, } }, //! //! ******************************************************************************************************************* //! //! webm //! ==== //! //! https://trac.ffmpeg.org/wiki/Encode/VP9 //! //! Controlling Speed and Quality //! ============================= //! //! libvpx-vp9 has two main control knobs for speed and quality: //! //! Deadline / Quality //! //! -deadline can be set to realtime, good, or best. For legacy reasons, the option is also accessible with -quality in ffmpeg. //! //! good is the default and recommended for most applications. //! best is recommended if you have lots of time and want the best compression efficiency. //! realtime is recommended for live / fast encoding. //! //! CPU Utilization / Speed //! ======================= //! //! -cpu-used sets how efficient the compression will be. For legacy reasons, the option is also accessible with -speed in ffmpeg. //! //! When the deadline/quality parameter is good or best, values for -cpu-used can be set between 0 and 5. The default is 0. //! Using 1 or 2 will increase encoding speed at the expense of having some impact on quality and rate control accuracy. //! 4 or 5 will turn off rate distortion optimization, having even more of an impact on quality. //! //! When the deadline/quality is set to realtime, the encoder will try to hit a particular CPU utilization target, and encoding quality //! will depend on CPU speed and the complexity of the clip that you are encoding. See ​the vpx documentation for more info. In this case, //! the valid values for -cpu-used are between 0 and 15, where the CPU utilization target (in per cent) is calculated as //! (100*(16-cpu-used)/16)%. //! //! ******************************************************************************************************************* //! { FILETYPE::WEBM, PROFILE::WEBM_DEFAULT, //! //! @brief WebM codec options. //! { { "deadline", "realtime", 0, OPT_SW_ONLY }, { "cpu-used", "8", 0, OPT_SW_ONLY }, // ffmpeg -i -c:v libvpx-vp9 -pass 2 -b:v 1000K -threads 8 -speed 1 // -tile-columns 6 -frame-parallel 1 -auto-alt-ref 1 -lag-in-frames 25 // -c:a libopus -b:a 64k -f webm out.webm // Most of the current VP9 decoders use tile-based, multi-threaded decoding. In order for the decoders to take advantage // of multiple cores, the encoder must set tile-columns and frame-parallel. // Setting auto-alt-ref and lag-in-frames >= 12 will turn on VP9's alt-ref frames, a VP9 feature that enhances quality. // speed 4 tells VP9 to encode really fast, sacrificing quality. Useful to speed up the first pass. // speed 1 is a good speed vs. quality compromise. Produces output quality typically very close to speed 0, but usually encodes much faster. // Multi-threaded encoding may be used if -threads > 1 and -tile-columns > 0. //{ "threads", "8", 0, OPT_SW_ONLY }, //{ "speed", "4", 0, OPT_SW_ONLY }, { "tile-columns", "6", 0, OPT_SW_ONLY }, { "frame-parallel", "1", 0, OPT_SW_ONLY }, { "auto-alt-ref", "1", 0, OPT_SW_ONLY }, { "lag-in-frames", "25", 0, OPT_SW_ONLY }, }, //! //! @brief WebM format options. //! { } } }; ffmpegfs-2.50/src/ffmpeg_base.h0000664000175000017500000003076715177713600012142 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_base.h * @brief FFmpeg transcoder base * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_BASE_H #define FFMPEG_BASE_H #pragma once #include "ffmpeg_utils.h" struct VIRTUALFILE; struct AVCodecContext; /** * @brief The #FFmpeg_Base class */ class FFmpeg_Base { public: /** * @brief Construct FFmpeg_Base object. */ explicit FFmpeg_Base(); /** * @brief Destruct FFmpeg_Base object. */ virtual ~FFmpeg_Base() = default; protected: /** * @brief Set up a video stream. * @param[in] output_codec_ctx - Output codec context. * @param[in] output_stream - Output stream object. * @param[in] input_codec_ctx - Input codec context. * @param[in] framerate - Frame rate of input stream. * @param[in] enc_hw_pix_fmt - Forcibly set the destination pixel format. Set it to AV_PIX_FMT_NONE for automatic selection. */ void video_stream_setup(AVCodecContext *output_codec_ctx, AVStream* output_stream, AVCodecContext *input_codec_ctx, AVRational framerate, AVPixelFormat enc_hw_pix_fmt) const; /** * @brief Call av_dict_set and check the result code. It displays an error message if appropriate. * @param[in] pm - Pointer to a pointer to a dictionary struct. * @param[in] key - Entry key to add to *pm. * @param[in] value - Entry value to add to *pm. * @param[in] flags - AV_DICT_* flags. * @param[in] filename - Filename this frame was created for. Only used for logging; may be nullptr. * @param[in] nodelete - If true, the tag is not deleted if the value is 0. * @return On success, returns 0; on error, a negative AVERROR value. */ int dict_set_with_check(AVDictionary **pm, const char *key, const char *value, int flags, const char *filename = nullptr, bool nodelete = false) const; /** * @brief Call av_dict_set and check the result code. It displays an error message if appropriate. * @param[in] pm - Pointer to a pointer to a dictionary struct. * @param[in] key - Entry key to add to *pm. * @param[in] value - Entry value to add to *pm. * @param[in] flags - AV_DICT_* flags. * @param[in] filename - Filename this frame was created for. Only used for logging; may be nullptr. * @param[in] nodelete - If true, the tag is not deleted if the value is 0. * @return On success, returns 0; on error, a negative AVERROR value. */ int dict_set_with_check(AVDictionary **pm, const char *key, int64_t value, int flags, const char *filename = nullptr, bool nodelete = false) const; /** * @brief Call av_opt_set and check result code. Displays an error message if appropriate. * @param[in] obj - A structure whose first element is a pointer to an AVClass. * @param[in] key - The name of the field to be set. * @param[in] value - The value to be set. * @param[in] flags - Flags to be passed to av_opt_find2. * @param[in] filename - Filename this frame was created for. Only used for logging; may be nullptr. * @return On success, returns 0; on error, a negative AVERROR value. */ int opt_set_with_check(void *obj, const char *key, const char *value, int flags, const char *filename = nullptr) const; /** * @brief Print data from the video stream to a log. * @param[in] out_file - True if the file is output. * @param[in] format_ctx - AVFormatContext belonging to the stream. * @param[in] stream - Stream to show data for. */ void video_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const; /** * @brief Print data from the audio stream to log. * @param[in] out_file - True if the file is output. * @param[in] format_ctx - AVFormatContext belonging to the stream. * @param[in] stream - Stream to show data for. */ void audio_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const; /** * @brief Print data from the subtitle stream to log. * @param[in] out_file - True if the file is output. * @param[in] format_ctx - AVFormatContext belonging to the stream. * @param[in] stream - Stream to show data for. */ void subtitle_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const; /** * @brief Calls av_get_pix_fmt_name and returns a std::string with the pix format name. * @param[in] pix_fmt - AVPixelFormat enum to convert. * @return Returns a std::string with the pix format name. */ static std::string get_pix_fmt_name(AVPixelFormat pix_fmt); /** * @brief Calls av_get_sample_fmt_name and returns a std::string with the format name. * @param[in] sample_fmt - AVSampleFormat enum to convert. * @return Returns a std::string with the format name. */ static std::string get_sample_fmt_name(AVSampleFormat sample_fmt); #if LAVU_DEP_OLD_CHANNEL_LAYOUT /** * @brief Calls av_channel_layout_describe and returns a std::string with the channel layout. * @param[in] ch_layout - Channel layout * @return Returns a std::string with the channel layout. */ static std::string get_channel_layout_name(const AVChannelLayout * ch_layout); #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT /** * @brief Calls av_get_channel_layout_string and returns a std::string with the channel layout. * @param[in] nb_channels - Number of channels. * @param[in] channel_layout - Channel layout index. * @return Returns a std::string with the channel layout. */ static std::string get_channel_layout_name(int nb_channels, uint64_t channel_layout); #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT /** * @brief Return source filename. Must be implemented in child class. * @return Returns filename. */ virtual const char *filename() const = 0; /** * @brief Return destination filename. Must be implemented in child class. * @return Returns filename. */ virtual const char *destname() const = 0; /** * @brief Return virtual filename. Must be implemented in child class. * @return Returns filename. */ virtual const char *virtname() const = 0; /** * @brief Convert PTS value to frame number. * @param[in] stream - Source video stream. * @param[in] pts - PTS of current frame in stream's time_base units. * @return Returns frame number. */ uint32_t pts_to_frame(AVStream* stream, int64_t pts) const; /** * @brief Convert frame number to PTS value. * @param[in] stream - Source video stream. * @param[in] frame_no - Number of frame. * @return Returns PTS of frame in the stream's time_base units. */ int64_t frame_to_pts(AVStream* stream, uint32_t frame_no) const; /** * @brief Get the number of channels from AVCodecParameters. * @param[in] codecpar - AVCodecParameters to check. * @return Returns the number of channels. */ int get_channels(const AVCodecParameters *codecpar) const; /** * @brief Set the number of channels from AVCodecParameters. * @param[inout] codecpar_out - AVCodecParameters to set. * @param[in] codecpar_in - AVCodecParameters to get channels from. */ void set_channels(AVCodecParameters *codecpar_out, const AVCodecParameters *codecpar_in) const; /** * @brief Get the number of channels from AVCodecContext. * @param[in] codec_ctx - AVCodecContext to check. * @return Returns the number of channels. */ int get_channels(const AVCodecContext *codec_ctx) const; /** * @brief Set the number of channels from AVCodecContext. * @param[inout] codec_ctx_out - AVCodecContext to set channels for. * @param[in] codec_ctx_in - AVCodecContext to copy channels from. */ void set_channels(AVCodecContext *codec_ctx_out, const AVCodecContext *codec_ctx_in) const; /** * @brief Set the number of channels from AVCodecContext. * @param[inout] codec_ctx_out - AVCodecContext to set. * @param[in] channels - Number of channels to set. */ void set_channels(AVCodecContext *codec_ctx_out, int channels) const; #define ASS_DEFAULT_PLAYRESX 384 /**< @brief Default X resolution */ #define ASS_DEFAULT_PLAYRESY 288 /**< @brief Default Y resolution */ #define ASS_DEFAULT_FONT "Arial" /**< @brief Default font name */ #define ASS_DEFAULT_FONT_SIZE 16 /**< @brief Default font size */ /** * @brief Default foreground colour: white. * * Colour values are expressed in hexadecimal BGR format * as &HBBGGRR& or ABGR (with alpha channel) as &HAABBGGRR&. * * Transparency (alpha) can be expressed as &HAA&. * * Note that in the alpha channel, 00 is opaque and FF is transparent. */ #define ASS_DEFAULT_COLOUR 0xffffff #define ASS_DEFAULT_BACK_COLOUR 0 /**< @brief Default background colour */ #define ASS_DEFAULT_BOLD 0 /**< @brief Default no bold font */ #define ASS_DEFAULT_ITALIC 0 /**< @brief Default no italics */ #define ASS_DEFAULT_UNDERLINE 0 /**< @brief Default no underline */ /** * @brief Default alignment: bottom centre * Alignment values are based on the numeric keypad. * 1 - bottom left, * 2 - bottom centre, * 3 - bottom right, * 4 - center left, * 5 - center centre, * 6 - center right, * 7 - top left, * 8 - top center, * 9 - top right. * In addition to determining the position of the subtitle, * this also determines the alignment of the text itself. */ #define ASS_DEFAULT_ALIGNMENT 2 /** * @brief Default border style: outline with shadow * 1 - Outline with shadow, * 3 - Rendered with an opaque box. */ #define ASS_DEFAULT_BORDERSTYLE 1 /** * @brief Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS. * Nicked from the FFmpeg API function ff_ass_subtitle_header_full(). :) * @param[in] codec_ctx pointer to the AVCodecContext * @param[in] play_res_x subtitle frame width * @param[in] play_res_y subtitle frame height * @param[in] font name of the default font face to use * @param[in] font_size default font size to use * @param[in] primary_color default text color to use (ABGR) * @param[in] secondary_color default secondary text color to use (ABGR) * @param[in] outline_color default outline color to use (ABGR) * @param[in] back_color default background color to use (ABGR) * @param[in] bold 1 for bold text, 0 for normal text * @param[in] italic 1 for italic text, 0 for normal text * @param[in] underline 1 for underline text, 0 for normal text * @param[in] border_style 1 for outline, 3 for opaque box * @param[in] alignment position of the text (left, center, top...), defined after the layout of the numpad (1-3 sub, 4-6 mid, 7-9 top) * @return On success, returns 0; on error, negative AVERROR value. */ int get_script_info(AVCodecContext *codec_ctx, int play_res_x, int play_res_y, const char *font, int font_size, int primary_color, int secondary_color, int outline_color, int back_color, int bold, int italic, int underline, int border_style, int alignment) const; protected: VIRTUALFILE * m_virtualfile; /**< @brief Underlying virtual file object */ }; #endif // FFMPEG_BASE_H ffmpegfs-2.50/src/ffmpeg_audiofifo.cc0000664000175000017500000000553415177713600013325 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_audiofifo.cc * @brief FFmpeg AVAudioFifo RAII wrapper * * @ingroup ffmpegfs */ #ifdef __cplusplus extern "C" { #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_audiofifo.h" FFmpeg_AudioFifo::FFmpeg_AudioFifo() : m_fifo(nullptr) { } FFmpeg_AudioFifo::FFmpeg_AudioFifo(AVSampleFormat sample_fmt, int channels, int nb_samples) : m_fifo(nullptr) { alloc(sample_fmt, channels, nb_samples); } FFmpeg_AudioFifo::~FFmpeg_AudioFifo() { reset(); } FFmpeg_AudioFifo::FFmpeg_AudioFifo(FFmpeg_AudioFifo&& fifo) noexcept : m_fifo(fifo.m_fifo) { fifo.m_fifo = nullptr; } FFmpeg_AudioFifo& FFmpeg_AudioFifo::operator=(FFmpeg_AudioFifo&& fifo) noexcept { if (this != &fifo) { reset(); m_fifo = fifo.m_fifo; fifo.m_fifo = nullptr; } return *this; } int FFmpeg_AudioFifo::alloc(AVSampleFormat sample_fmt, int channels, int nb_samples) { reset(); m_fifo = av_audio_fifo_alloc(sample_fmt, channels, nb_samples); if (m_fifo == nullptr) { return AVERROR(ENOMEM); } return 0; } void FFmpeg_AudioFifo::reset() { if (m_fifo != nullptr) { av_audio_fifo_free(m_fifo); m_fifo = nullptr; } } bool FFmpeg_AudioFifo::empty() const { return m_fifo == nullptr; } int FFmpeg_AudioFifo::size() const { if (m_fifo == nullptr) { return 0; } return av_audio_fifo_size(m_fifo); } int FFmpeg_AudioFifo::realloc(int nb_samples) { if (m_fifo == nullptr) { return AVERROR(EINVAL); } return av_audio_fifo_realloc(m_fifo, nb_samples); } int FFmpeg_AudioFifo::write(void **data, int nb_samples) { if (m_fifo == nullptr) { return AVERROR(EINVAL); } return av_audio_fifo_write(m_fifo, data, nb_samples); } int FFmpeg_AudioFifo::read(void **data, int nb_samples) { if (m_fifo == nullptr) { return AVERROR(EINVAL); } return av_audio_fifo_read(m_fifo, data, nb_samples); } AVAudioFifo* FFmpeg_AudioFifo::get() { return m_fifo; } const AVAudioFifo* FFmpeg_AudioFifo::get() const { return m_fifo; } AVAudioFifo* FFmpeg_AudioFifo::release() { AVAudioFifo *fifo = m_fifo; m_fifo = nullptr; return fifo; } FFmpeg_AudioFifo::operator AVAudioFifo*() { return m_fifo; } FFmpeg_AudioFifo::operator const AVAudioFifo*() const { return m_fifo; } ffmpegfs-2.50/src/ffmpeg_swscontext.cc0000664000175000017500000000276215177713600013601 /* * This file is part of FFmpegfs. * * FFmpegfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ #include "ffmpeg_swscontext.h" extern "C" { #include } FFmpeg_SwsContext::FFmpeg_SwsContext() : m_ctx(nullptr) { } FFmpeg_SwsContext::FFmpeg_SwsContext(SwsContext *ctx) : m_ctx(ctx) { } FFmpeg_SwsContext::~FFmpeg_SwsContext() { reset(); } FFmpeg_SwsContext::FFmpeg_SwsContext(FFmpeg_SwsContext&& ctx) noexcept : m_ctx(ctx.release()) { } FFmpeg_SwsContext& FFmpeg_SwsContext::operator=(FFmpeg_SwsContext&& ctx) noexcept { if (this != &ctx) { reset(ctx.release()); } return *this; } SwsContext *FFmpeg_SwsContext::get() const { return m_ctx; } SwsContext *FFmpeg_SwsContext::release() { SwsContext *ctx = m_ctx; m_ctx = nullptr; return ctx; } bool FFmpeg_SwsContext::reset(SwsContext *ctx) { bool closed = false; if (m_ctx != nullptr) { sws_freeContext(m_ctx); closed = true; } m_ctx = ctx; return closed; } bool FFmpeg_SwsContext::empty() const { return m_ctx == nullptr; } FFmpeg_SwsContext::operator bool() const { return m_ctx != nullptr; } FFmpeg_SwsContext::operator SwsContext *() const { return m_ctx; } SwsContext *FFmpeg_SwsContext::operator->() const { return m_ctx; } ffmpegfs-2.50/src/dvdparser.cc0000664000175000017500000005310315177713600012021 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifdef USE_LIBDVD /** * @file dvdparser.cc * @brief dvdparser class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "ffmpegfs.h" #include "dvdparser.h" #include "transcode.h" #include "logging.h" #include #include extern "C" { #include } typedef struct AUDIO_SETTINGS /** @brief Audio stream settings */ { BITRATE m_audio_bit_rate; /**< @brief average bitrate of audio data (in bits per second) */ int m_channels; /**< @brief number of channels (1: mono, 2: stereo, or more) */ int m_sample_rate; /**< @brief number of audio samples per second */ } AUDIO_SETTINGS; typedef AUDIO_SETTINGS const *LPCAUDIO_SETTINGS; /**< @brief Pointer to const version of AUDIO_SETTINGS */ typedef AUDIO_SETTINGS *LPAUDIO_SETTINGS; /**< @brief Pointer version of AUDIO_SETTINGS */ typedef struct VIDEO_SETTINGS /** @brief Video stream settings */ { BITRATE m_video_bit_rate; /**< @brief average bitrate of video data (in bits per second) */ int m_width; /**< @brief video width in pixels */ int m_height; /**< @brief video height in pixels */ } VIDEO_SETTINGS; typedef VIDEO_SETTINGS const *LPCVIDEO_SETTINGS; /**< @brief Pointer to const version of VIDEO_SETTINGS */ typedef VIDEO_SETTINGS *LPVIDEO_SETTINGS; /**< @brief Pointer version of VIDEO_SETTINGS */ static int dvd_find_best_audio_stream(const vtsi_mat_t *vtsi_mat, int *best_channels, int *best_sample_frequency); static AVRational dvd_frame_rate(const uint8_t * ptr); static int64_t BCDtime(const dvd_time_t * dvd_time); static bool create_dvd_virtualfile(const ifo_handle_t *vts_file, const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler, bool full_title, int title_idx, int chapter_idx, int angles, int ttnnum, int audio_stream, const AUDIO_SETTINGS & audio_settings, const VIDEO_SETTINGS & video_settings); static int parse_dvd(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler); /** * @brief Locate best matching audio stream. * @param[in] vtsi_mat - Video Title Set Information (VTSI) * @param[out] best_channels - Number of channels in best stream. * @param[out] best_sample_frequency - Sample frequency in best stream. * @return Returns number of best stream (0..8). */ static int dvd_find_best_audio_stream(const vtsi_mat_t *vtsi_mat, int *best_channels, int *best_sample_frequency) { int best_stream = -1; int best_application_mode = INT_MAX; int best_lang_extension = INT_MAX; int best_quantization = 0; *best_channels = 0; *best_sample_frequency = 0; for(int i = 0; i < vtsi_mat->nr_of_vts_audio_streams; i++) { const audio_attr_t *attr = &vtsi_mat->vts_audio_attr[i]; if (attr->audio_format == 0 && attr->multichannel_extension == 0 && attr->lang_type == 0 && attr->application_mode == 0 && attr->quantization == 0 && attr->sample_frequency == 0 && attr->unknown1 == 0 && attr->channels == 0 && attr->lang_extension == 0 && attr->unknown3 == 0) { // Unspecified continue; } // Preference in this order, if higher value is same, compare next and so on. // // application_mode: prefer not specified. // 0: not specified // 1: karaoke mode // 2: surround sound mode // lang_extension: prefer not specified or normal audio // 0: Not specified // 1: Normal audio/Caption // 2: visually impaired // 3: Director's comments 1 // 4: Director's comments 2 // sample_frequency: maybe 48K only // 0: 48kHz // 1: ??kHz // quantization prefer highest bit width or drc // 0: 16bit // 1: 20bit // 2: 24bit // 3: drc // channels: prefer no extension // multichannel_extension // if ((best_multiframe > multiframe) || // (best_multiframe == multiframe && best_bitrate > bitrate) || // (best_multiframe == multiframe && best_bitrate == bitrate && best_count >= count)) // Specs keep the meaning of the values of this field other than 0 secret, so we nail it to 48 kHz. int sample_frequency = 48000; if ((best_application_mode < attr->application_mode) || (best_application_mode == attr->application_mode && best_lang_extension < attr->lang_extension) || (best_application_mode == attr->application_mode && best_lang_extension == attr->lang_extension && *best_sample_frequency > sample_frequency) || (best_application_mode == attr->application_mode && best_lang_extension == attr->lang_extension && *best_sample_frequency == sample_frequency && *best_channels > attr->channels) || (best_application_mode == attr->application_mode && best_lang_extension == attr->lang_extension && *best_sample_frequency == sample_frequency && *best_channels == attr->channels && best_quantization > attr->quantization) ) { continue; } best_stream = i; best_application_mode = attr->application_mode; best_lang_extension = attr->lang_extension; *best_sample_frequency = sample_frequency; *best_channels = attr->channels; best_quantization = attr->quantization; } if (best_stream > -1) { ++*best_channels; } return best_stream; } /** * @brief Get the frame rate of the DVD. Can be 25 fps (PAL) or 29.97 (NTCS). * @param[in] ptr - Pointer to frame_u element in dvd_time_t structure. * @return On success, returns AVRational with frame rate. On error, returns frame rate { 0, 0 }. */ static AVRational dvd_frame_rate(const uint8_t * ptr) { AVRational framerate = { 0, 0 }; // 11 = 30 fps, 10 = illegal, 01 = 25 fps, 00 = illegal unsigned fps = ((ptr[3] & 0xC0) >> 6) & 0x03; switch (fps) { case 3: // PAL { framerate = av_make_q(25000, 1000); break; } case 1: // NTSC { framerate = av_make_q(30000, 10001); break; } default: { // Frame rate is illegal, so we need to set anything. Assume PAL. framerate = av_make_q(25000, 1000); break; } } return framerate; } /** * @brief Convert a time in BCD format into AV_TIMEBASE fractional seconds. * @param[in] dvd_time - dvd_time_t object. * @return Time in AV_TIMEBASE fractional seconds. */ static int64_t BCDtime(const dvd_time_t * dvd_time) { std::array time; AVRational framerate = dvd_frame_rate(&dvd_time->frame_u); if (!framerate.den) { framerate = av_make_q(25000, 1000); // Avoid divisions by 0 } time[0] = dvd_time->hour; time[1] = dvd_time->minute; time[2] = dvd_time->second; time[3] = dvd_time->frame_u & 0x3F; // Number of frame // convert BCD (two digits) to binary for (int64_t & tm : time) { tm = ((tm & 0xf0) >> 4) * 10 + (tm & 0x0f); } return (AV_TIME_BASE * (time[0] * 3600 + time[1] * 60 + time[2]) + static_cast(static_cast(AV_TIME_BASE * time[3]) / av_q2d(framerate))); } /** * @brief Create a virtual file for a DVD. * @param[in] vts_file - Structure defines an IFO file * @param[in] path - Path to DVD files. * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @param[in] full_title - If true, create virtual file of all title. If false, include single chapter only. * @param[in] title_idx - Index of DVD title. * @param[in] chapter_idx - Index of DVD chapter. * @param[in] angles - Number of angles. * @param[in] ttnnum - DVD title number. * @param[in] audio_stream - Audio stream index. * @param[in] audio_settings - Audio stream settings. * @param[in] video_settings - Video stream settings. * @return Returns true if successful. Returns false on error. */ static bool create_dvd_virtualfile(const ifo_handle_t *vts_file, const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler, bool full_title, int title_idx, int chapter_idx, int angles, int ttnnum, int audio_stream, const AUDIO_SETTINGS & audio_settings, const VIDEO_SETTINGS & video_settings) { const vts_ptt_srpt_t *vts_ptt_srpt = vts_file->vts_ptt_srpt; int title_no = title_idx + 1; int chapter_no = chapter_idx + 1; int pgcnum = vts_ptt_srpt->title[ttnnum - 1].ptt[chapter_idx].pgcn; int pgn = vts_ptt_srpt->title[ttnnum - 1].ptt[chapter_idx].pgn; const pgc_t *cur_pgc = vts_file->vts_pgcit->pgci_srp[pgcnum - 1].pgc; AVRational framerate; int64_t duration = 0; uint64_t size = 0; bool interleaved = false; int start_cell = cur_pgc->program_map[pgn - 1] - 1; int end_cell = 0; if (pgn < cur_pgc->nr_of_programs && !full_title) { end_cell = cur_pgc->program_map[pgn] - 1; } else { end_cell = cur_pgc->nr_of_cells; } interleaved = cur_pgc->cell_playback[start_cell].interleaved ? true : false; framerate = dvd_frame_rate(&cur_pgc->cell_playback[start_cell].playback_time.frame_u); bool has_angles = false; for (int cell_no = start_cell; cell_no < end_cell; cell_no++) { cell_playback_t *cell_playback = &cur_pgc->cell_playback[cell_no]; // Only count normal cells and the first of an angle to avoid duplicate sizes if (cell_playback->block_mode == static_cast(BLOCK_MODE_NOT_IN_BLOCK) || cell_playback->block_mode == static_cast(BLOCK_MODE_FIRST_CELL)) { size += (cell_playback->last_sector - cell_playback->first_sector) * 2048; duration += BCDtime(&cell_playback->playback_time); } if (cell_playback->block_type == static_cast(BLOCK_TYPE_ANGLE_BLOCK)) { has_angles = true; } } if (duration < params.m_min_dvd_chapter_duration * AV_TIME_BASE) { Logging::debug(nullptr, "Skipping short DVD chapter."); return true; } if (!has_angles) { // If this chapter has no angle cells, reset angles to 1 angles = 1; } // Split file if chapter has several angles for (int angle_idx = 0; angle_idx < angles; angle_idx++) { std::string title_buf; int angle_no = angle_idx + 1; // can safely assume this a video if (!full_title) { // Single chapter if (angles > 1) { strsprintf(&title_buf, "%02d. Chapter %03d (Angle %d) [%s].%s", title_no, chapter_no, angle_no, replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); } else { strsprintf(&title_buf, "%02d. Chapter %03d [%s].%s", title_no, chapter_no, replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); } } else { // Full title if (angles > 1) { strsprintf(&title_buf, "%02d. Title (Angle %d) [%s].%s", title_no, angle_no, replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); } else { strsprintf(&title_buf, "%02d. Title [%s].%s", title_no, replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[FORMAT::VIDEO].fileext().c_str()); } } LPVIRTUALFILE virtualfile = nullptr; if (!ffmpeg_format[FORMAT::VIDEO].is_multiformat()) { virtualfile = insert_file(VIRTUALTYPE::DVD, path + title_buf, statbuf); } else { virtualfile = insert_dir(VIRTUALTYPE::DVD, path + title_buf, statbuf); } if (virtualfile == nullptr) { Logging::error(path, "Failed to create virtual path: %1", (path + title_buf).c_str()); errno = EIO; return false; } if (add_fuse_entry(buf, filler, title_buf, &virtualfile->m_st, 0)) { // break; } // DVD is video format anyway virtualfile->m_format_idx = 0; // Mark title/chapter/angle virtualfile->m_full_title = full_title; virtualfile->m_dvd.m_title_no = title_no; virtualfile->m_dvd.m_chapter_no = chapter_no; virtualfile->m_dvd.m_angle_no = angle_no; if (!transcoder_cached_filesize(virtualfile, &virtualfile->m_st)) { virtualfile->m_duration = duration; BITRATE video_bit_rate = 8*1024*1024; // In case the real bitrate cannot be calculated later, assume 8 Mbit video bitrate if (duration) { /** * @todo We actually calculate the overall DVD bitrate here, including all audio * streams, not just the video bitrate. This should be the video bitrate alone. We * should also calculate the audio bitrate for the selected stream. */ video_bit_rate = static_cast(size * 8LL * AV_TIME_BASE / static_cast(duration)); // calculate bitrate in bps } Logging::trace(virtualfile->m_destfile, "Video %1 %2x%3@%<5.2f>4%5 fps %6 [%7]", format_bitrate(video_settings.m_video_bit_rate).c_str(), video_settings.m_width, video_settings.m_height, av_q2d(framerate), interleaved ? "i" : "p", format_size(size).c_str(), format_duration(duration).c_str()); virtualfile->m_width = video_settings.m_width; virtualfile->m_height = video_settings.m_height; virtualfile->m_framerate = framerate; if (audio_stream > -1) { Logging::trace(virtualfile->m_destfile, "Audio Channels %1 Sample Rate %2", audio_settings.m_channels, audio_settings.m_sample_rate); virtualfile->m_channels = audio_settings.m_channels; virtualfile->m_sample_rate = audio_settings.m_sample_rate; } transcoder_set_filesize(virtualfile, duration, audio_settings.m_audio_bit_rate, audio_settings.m_channels, audio_settings.m_sample_rate, AV_SAMPLE_FMT_NONE, video_bit_rate, video_settings.m_width, video_settings.m_height, interleaved, framerate); virtualfile->m_video_frame_count = static_cast(av_rescale_q(duration, av_get_time_base_q(), av_inv_q(framerate))); virtualfile->m_predicted_size = static_cast(size); } } return true; } /** * @brief Parse DVD directory and get all DVD titles and chapters as virtual files. * @param[in] path - Path to check. * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @return On success, returns number of chapters found. On error, returns -errno. */ static int parse_dvd(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler) { dvd_reader_t *dvd; ifo_handle_t *ifo_file; tt_srpt_t *tt_srpt; int titles; bool success = true; Logging::debug(path, "Parsing the DVD."); dvd = DVDOpen(path.c_str()); if (dvd == nullptr) { Logging::error(path, "Couldn't open the DVD."); return ENOENT; } ifo_file = ifoOpen(dvd, 0); if (ifo_file == nullptr) { Logging::error(path, "Can't open VMG info for the DVD."); DVDClose(dvd); return -EINVAL; } tt_srpt = ifo_file->tt_srpt; titles = tt_srpt->nr_of_srpts; Logging::debug(path, "There are %1 titles on this DVD.", titles); for (int title_idx = 0; title_idx < titles && success; ++title_idx) { ifo_handle_t *vts_file; int vtsnum = tt_srpt->title[title_idx].title_set_nr; int ttnnum = tt_srpt->title[title_idx].vts_ttn; int chapters = tt_srpt->title[title_idx].nr_of_ptts; int angles = tt_srpt->title[title_idx].nr_of_angles; Logging::trace(path, "Title: %1 VTS: %2 TTN: %3", title_idx + 1, vtsnum, ttnnum); Logging::trace(path, "DVD title has %1 chapters and %2 angles.", chapters, angles); vts_file = ifoOpen(dvd, vtsnum); if (vts_file == nullptr) { Logging::error(path, "Can't open info file for title %1.", vtsnum); DVDClose(dvd); return -EINVAL; } // Set reasonable defaults AUDIO_SETTINGS audio_settings; audio_settings.m_audio_bit_rate = 256000; audio_settings.m_channels = 2; audio_settings.m_sample_rate = 48000; int audio_stream = 0; VIDEO_SETTINGS video_settings; video_settings.m_video_bit_rate = 8000000; video_settings.m_width = 720; video_settings.m_height = 576; if (vts_file->vtsi_mat) { audio_stream = dvd_find_best_audio_stream(vts_file->vtsi_mat, &audio_settings.m_channels, &audio_settings.m_sample_rate); video_settings.m_height = (vts_file->vtsi_mat->vts_video_attr.video_format != 0) ? 576 : 480; switch(vts_file->vtsi_mat->vts_video_attr.picture_size) { case 0: { video_settings.m_width = 720; break; } case 1: { video_settings.m_width = 704; break; } case 2: { video_settings.m_width = 352; break; } case 3: { video_settings.m_width = 352; video_settings.m_height /= 2; break; } default: { Logging::warning(path, "DVD video contains invalid picture size attribute."); } } } // Add separate chapters for (int chapter_idx = 0; chapter_idx < chapters && success; ++chapter_idx) { success = create_dvd_virtualfile(vts_file, path, statbuf, buf, filler, false, title_idx, chapter_idx, angles, ttnnum, audio_stream, audio_settings, video_settings); } if (success && chapters > 1) { // If more than 1 chapter, add full title as well success = create_dvd_virtualfile(vts_file, path, statbuf, buf, filler, true, title_idx, 0, 1, ttnnum, audio_stream, audio_settings, video_settings); } ifoClose(vts_file); } ifoClose(ifo_file); DVDClose(dvd); if (success) { return titles; // Number of titles on disk } else { return -errno; } } int check_dvd(const std::string & path, void *buf, fuse_fill_dir_t filler) { std::string _path(path); struct stat stbuf; int res = 0; append_sep(&_path); if (stat((_path + "VIDEO_TS.IFO").c_str(), &stbuf) == 0 || stat((_path + "VIDEO_TS/VIDEO_TS.IFO").c_str(), &stbuf) == 0) { if (!check_path(_path)) { Logging::trace(_path, "DVD detected."); res = parse_dvd(_path, &stbuf, buf, filler); Logging::trace(_path, "%1 titles were discovered.", res); } else { res = load_path(_path, &stbuf, buf, filler); } add_dotdot(buf, filler, &stbuf, 0); } return res; } #endif // USE_LIBDVD ffmpegfs-2.50/src/ffmpeg_dictionary.cc0000664000175000017500000000356115177713600013523 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_dictionary.cc * @brief FFmpeg_Dictionary class implementation * * @ingroup ffmpegfs */ #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_dictionary.h" FFmpeg_Dictionary::FFmpeg_Dictionary() : m_dict(nullptr) { } FFmpeg_Dictionary::~FFmpeg_Dictionary() { reset(); } FFmpeg_Dictionary::FFmpeg_Dictionary(FFmpeg_Dictionary&& dict) noexcept : m_dict(dict.m_dict) { dict.m_dict = nullptr; } FFmpeg_Dictionary& FFmpeg_Dictionary::operator=(FFmpeg_Dictionary&& dict) noexcept { if (this != &dict) { reset(); m_dict = dict.m_dict; dict.m_dict = nullptr; } return *this; } void FFmpeg_Dictionary::reset() { if (m_dict != nullptr) { av_dict_free(&m_dict); } } bool FFmpeg_Dictionary::empty() const { return m_dict == nullptr; } AVDictionary* FFmpeg_Dictionary::get() { return m_dict; } const AVDictionary* FFmpeg_Dictionary::get() const { return m_dict; } AVDictionary** FFmpeg_Dictionary::address() { return &m_dict; } AVDictionary* FFmpeg_Dictionary::release() { AVDictionary* dict = m_dict; m_dict = nullptr; return dict; } FFmpeg_Dictionary::operator AVDictionary*() { return m_dict; } FFmpeg_Dictionary::operator const AVDictionary*() const { return m_dict; } ffmpegfs-2.50/src/fuseops.cc0000664000175000017500000024400315201130214011472 /* * Copyright (C) 2006-2008 David Collett * Copyright (C) 2008-2012 K. Henriksson * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file fuseops.cc * @brief Fuse operations implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2006-2008 David Collett @n * Copyright (C) 2008-2013 K. Henriksson @n * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) */ #include "transcode.h" #include "cache_maintenance.h" #include "logging.h" #ifdef USE_LIBVCD #include "vcdparser.h" #endif // USE_LIBVCD #ifdef USE_LIBDVD #include "dvdparser.h" #endif // USE_LIBDVD #ifdef USE_LIBBLURAY #include "blurayparser.h" #endif // USE_LIBBLURAY #include "cuesheetparser.h" #include "thread_pool.h" #include "buffer.h" #include "cache_entry.h" #include #include #include #include typedef std::map FILENAME_MAP; /**< @brief Map virtual file names to virtual file objects. */ typedef std::map RFILENAME_MAP; /**< @brief Map source file names to virtual file objects. */ static void init_stat(struct stat *stbuf, size_t fsize, time_t ftime, bool directory); static LPVIRTUALFILE make_file(void *buf, fuse_fill_dir_t filler, VIRTUALTYPE type, const std::string & origpath, const std::string & filename, size_t fsize, time_t ftime = time(nullptr), int flags = VIRTUALFLAG_NONE); static void prepare_script(); static bool is_passthrough(const std::string & ext); static bool virtual_name(std::string *virtualpath, const std::string &origpath = "", const FFmpegfs_Format **current_format = nullptr); static FILENAME_MAP::const_iterator find_prefix(const FILENAME_MAP & map, const std::string & search_for); static void stat_to_dir(struct stat *stbuf); static void flags_to_dir(int *flags); static void insert(const VIRTUALFILE & virtualfile); static int get_source_properties(const std::string & origpath, LPVIRTUALFILE virtualfile); static int make_hls_fileset(void * buf, fuse_fill_dir_t filler, const std::string & origpath, LPVIRTUALFILE virtualfile); static int kick_next(LPVIRTUALFILE virtualfile); static void sighandler(int signum); static std::string get_number(const char *path, uint32_t *value); static size_t guess_format_idx(const std::string & filepath); static int parse_file(LPVIRTUALFILE newvirtualfile); static const FFmpegfs_Format * get_format(LPVIRTUALFILE newvirtualfile); static int selector(const struct dirent * de); static int scandir(const char *dirp, std::vector * _namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **)); static int ffmpegfs_readlink(const char *path, char *buf, size_t size); static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags); static int ffmpegfs_getattr(const char *path, struct stat *stbuf, fuse_file_info *fi); //static int ffmpegfs_fgetattr(const char *path, struct stat * stbuf, struct fuse_file_info *fi); static int ffmpegfs_open(const char *path, struct fuse_file_info *fi); static int ffmpegfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi); static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf); static int ffmpegfs_release(const char *path, struct fuse_file_info *fi); static void * ffmpegfs_init(struct fuse_conn_info *conn, fuse_config *cfg); static void ffmpegfs_destroy(__attribute__((unused)) void * p); static FILENAME_MAP filenames; /**< @brief Map files to virtual files */ static RFILENAME_MAP rfilenames; /**< @brief Reverse map virtual files to real files */ static std::vector script_file; /**< @brief Buffer for the virtual script if enabled */ static struct sigaction oldHandler; /**< @brief Saves old SIGINT handler to restore on shutdown */ bool docker_client; /**< @brief True if running inside a Docker container */ fuse_operations ffmpegfs_ops; /**< @brief FUSE file system operations */ std::unique_ptr tp; /**< @brief Thread pool object */ /** * @brief Check if a file should be treated passthrough, i.e. bitmaps etc. * @param[in] ext - Extension of file to check * @return Returns true if file is passthrough, false if not */ static bool is_passthrough(const std::string & ext) { /** * * List of passthrough file extensions */ static const std::set passthrough_set = { "AA", "ACR", // Dicom/ACR/IMA file format for medical images "AI", // PostScript Formats (Ghostscript required) "ANI", // Animated Cursor "ARW", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "AWD", // Artweaver format "B3D", // BodyPaint 3D format "BMP", // Windows Bitmap "CAM", // Casio digital camera format (JPG version only) "CEL", "CGM", // CAD Formats (Shareware PlugIns) "CIN", // Digital Picture Exchange/Cineon Format "CLP", // Windows Clipboard "CPT", // CorelDraw Photopaint format (CPT version 6 only) "CR2", // Canon RAW format "CRW", // Canon RAW format "CUR", // Animated Cursor "DCM", // Dicom/ACR/IMA file format for medical images "DCR", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "DCX", // Multipage PCX format "DDS", // Direct Draw Surface format "DIB", // Windows Bitmap "DJVU", // DjVu File Format "DNG", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "DPX", // Digital Picture Exchange/Cineon Format "DWG", // CAD Formats (Shareware PlugIns) "DXF", // Drawing Interchange Format, CAD format "ECW", // Enhanced Compressed Wavelet "EEF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "EMF", // Enhanced Metafile Format "EPS", // PostScript Formats (Ghostscript required) "EXR", // EXR format "FITS", // Flexible Image Transport System "FLI", "FLIF", // Free Lossless Image format "FPX", // FlashPix format "G3", // Group 3 Facsimile Apparatus format "GIF", // Graphics Interchange Format "HDP", // JPEG-XR/Microsoft HD Photo format "HDR", // High Dynamic Range format "HEIC", // High Efficiency Image format "HPGL", // CAD Formats (Shareware PlugIns) "HRZ", "ICL", // Icon Library formats "ICO", // Windows Icon "ICS", // Image Cytometry Standard format "IFF", // Interchange File Format "IMA", // Dicom/ACR/IMA file format for medical images "IMG", // GEM Raster format "IW44", // DjVu File Format "J2K", // JPEG 2000 format "JLS", // JPEG-LS, JPEG Lossless "JNG", // Multiple Network Graphics "JP2", // JPEG 2000 format "JPC", // JPEG 2000 format "JPEG", // Joint Photographic Experts Group "JPG", // Joint Photographic Experts Group "JPM", // JPEG2000/Part6, LuraDocument.jpm "JXR", // JPEG-XR/Microsoft HD Photo format "KDC", // Kodak digital camera format "LBM", // Interchange File Format "M3U8", // Apple HTTP Live Streaming "MIFF", "MNG", // Multiple Network Graphics "MRC", // MRC format "MrSID", // LizardTech's SID Wavelet format "MRW", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "NEF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "NRW", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "ORF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "PBM", // Portable Bitmap format "PCD", // Kodak Photo CD "PCX", // PC Paintbrush format from ZSoft Corporation "PDF", // PostScript Formats (Ghostscript required) "PDF", // Portable Document format "PDN", // Paint.NET file format "PEF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "PGM", // Portable Greymap format "PICT", // Macintosh PICT format "PIX", "PNG", // Portable Network Graphics "PNM", "PPM", // Portable Pixelmap format "PS", // PostScript Formats (Ghostscript required) "PSD", // Adobe PhotoShop format "PSP", // Paint Shop Pro format "PVR", // DreamCast Texture format "QTIF", // Macintosh PICT format "RAF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "RAS", // Sun Raster format "RAW", // Raw (binary) data "RGB", // Silicon Graphics format "RLE", // Utah RLE format "RW2", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "SFF", // Structured Fax File "SFW", // Seattle Film Works format "SGI", // Silicon Graphics format "SID", // LizardTech's SID Wavelet format "SIF", // SIF format "SRF", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "SUN", // Sun Raster format "Sunras", "SVG", // CAD Formats (Shareware PlugIns) "TGA", // Truevision Advanced Raster Graphics Adapter (TARGA) "TIF", // Tagged Image File Format "TIFF", "TTF", // True Type Font "TXT", // Text (ASCII) File (as image) "VTF", // Valve Texture format "WAD", // WAD3 Game format "WAL", // Quake 2 textures "WBC", // Webshots formats "WBZ", // Webshots formats "WBMP", // WAP Bitmap format "WDP", // JPEG-XR/Microsoft HD Photo format "WebP", // Weppy file format "WMF", // Windows Metafile Format "WSQ", // Wavelet Scaler Quantization format "X", "X3F", // Digital camera RAW formats (Adobe, Epson, Nikon, Minolta, Olympus, Fuji, Kodak, Sony, Pentax, Sigma) "XBM", // X11 Bitmap "XCF", // GIMP file format "XPM", "XWD", "YUV" // Raw (binary) data }; return (passthrough_set.find(ext) != passthrough_set.cend()); } void init_fuse_ops() { std::memset(&ffmpegfs_ops, 0, sizeof(fuse_operations)); ffmpegfs_ops.getattr = ffmpegfs_getattr; // ffmpegfs_ops.fgetattr = ffmpegfs_fgetattr; ffmpegfs_ops.readlink = ffmpegfs_readlink; ffmpegfs_ops.readdir = ffmpegfs_readdir; ffmpegfs_ops.open = ffmpegfs_open; ffmpegfs_ops.read = ffmpegfs_read; ffmpegfs_ops.statfs = ffmpegfs_statfs; ffmpegfs_ops.release = ffmpegfs_release; ffmpegfs_ops.init = ffmpegfs_init; ffmpegfs_ops.destroy = ffmpegfs_destroy; } /** * @brief Read the target of a symbolic link. * @param[in] path * @param[in] buf - FUSE buffer to fill. * @param[in] size * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_readlink(const char *path, char *buf, size_t size) { std::string origpath; std::string transcoded; ssize_t len; Logging::trace(path, "readlink"); append_basepath(&origpath, path); find_original(&origpath); len = readlink(origpath.c_str(), buf, size - 2); if (len != -1) { buf[len] = '\0'; transcoded = buf; virtual_name(&transcoded, origpath); buf[0] = '\0'; strncat(buf, transcoded.c_str(), size); errno = 0; // Just to make sure - reset any error } return -errno; } /** * @brief Read directory contents. * @param[in] path Virtual path to list. * @param[in] buf FUSE buffer to fill. * @param[in] filler FUSE callback used to add directory entries. * @param[in] offset Directory offset supplied by FUSE; currently unused. * @param[in] fi FUSE file information; currently unused. * @param[in] flags FUSE readdir flags; currently unused. * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags) { (void) offset; (void) fi; (void) flags; std::string origpath; Logging::trace(path, "readdir"); append_basepath(&origpath, path); append_sep(&origpath); // Add a virtual script if enabled if (params.m_enablescript) { LPVIRTUALFILE virtualfile = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, params.m_scriptfile, script_file.size()); virtualfile->m_file_contents = script_file; } LPVIRTUALFILE virtualfile = find_original(origpath); if (virtualfile == nullptr) { #if defined(USE_LIBBLURAY) || defined(USE_LIBDVD) || defined(USE_LIBVCD) int res; #endif #ifdef USE_LIBVCD res = check_vcd(origpath, buf, filler); if (res != 0) { // Found VCD or error reading VCD return (res >= 0 ? 0 : res); } #endif // USE_LIBVCD #ifdef USE_LIBDVD res = check_dvd(origpath, buf, filler); if (res != 0) { // Found DVD or error reading DVD return (res >= 0 ? 0 : res); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY res = check_bluray(origpath, buf, filler); if (res != 0) { // Found Blu-ray or error reading Blu-ray return (res >= 0 ? 0 : res); } #endif // USE_LIBBLURAY } if (virtualfile == nullptr || !(virtualfile->m_flags & VIRTUALFLAG_FILESET)) { DIR *dp = opendir(origpath.c_str()); if (dp != nullptr) { try { std::map files; // Read directory contents for (struct dirent *de = readdir(dp); de != nullptr; de = readdir(dp)) { struct stat stbuf; if (lstat((origpath + de->d_name).c_str(), &stbuf) == -1) { // Should actually not happen, file listed by readdir, so it should exist throw ENOENT; } files.insert({ de->d_name, stbuf }); } // Process files for (auto& [key, value] : files) { std::string origname(key); if (is_blocked(origname)) { continue; } std::string origfile; std::string filename(key); struct stat & stbuf = value; int flags = 0; origfile = origpath + origname; std::string origext; find_ext(&origext, filename); if (S_ISREG(stbuf.st_mode) || S_ISLNK(stbuf.st_mode)) { const FFmpegfs_Format *current_format = nullptr; // Check if file can be transcoded if (virtual_name(&filename, origpath, ¤t_format)) { if (current_format->video_codec() == AV_CODEC_ID_NONE) { LPVIRTUALFILE newvirtualfile = find_file_from_orig(origfile); if (newvirtualfile == nullptr) { // Should never happen, we've just created this entry in virtual_name... Logging::error(origfile, "INTERNAL ERROR: ffmpegfs_readdir()! newvirtualfile is NULL."); throw EINVAL; } // If target supports no video, we need to do some extra work and checkF // the input file to actually have an audio stream. If not, hide the file, // makes no sense to transcode anyway. if (!newvirtualfile->m_has_audio) { Logging::debug(origfile, "Unable to transcode. The source has no audio stream, but the target just supports audio."); flags |= VIRTUALFLAG_HIDDEN; } } if (!(flags & VIRTUALFLAG_HIDDEN)) { if (check_cuesheet(origfile, buf, filler) < 0) { throw EINVAL; } } std::string newext; find_ext(&newext, filename); if (!current_format->is_multiformat()) { if (origext != newext || params.m_recodesame == RECODESAME::YES) { insert_file(VIRTUALTYPE::DISK, origpath + filename, origfile, &stbuf, flags); } else { insert_file(VIRTUALTYPE::DISK, origpath + filename, origfile, &stbuf, flags | VIRTUALFLAG_PASSTHROUGH); } } else { // Change file to directory for the frame set stat_to_dir(&stbuf); flags_to_dir(&flags); filename = origname; // Restore original name insert_file(VIRTUALTYPE::DISK, origfile, &stbuf, flags); } } } if (!(flags & VIRTUALFLAG_HIDDEN)) { if (add_fuse_entry(buf, filler, filename, &stbuf, 0)) { break; } } } closedir(dp); errno = 0; // Just to make sure - reset any error } catch (int _errno) { closedir(dp); errno = _errno; } } } else { try { if (virtualfile->m_flags & (VIRTUALFLAG_FILESET | VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS | VIRTUALFLAG_DIRECTORY)) { add_dotdot(buf, filler, &virtualfile->m_st, 0); } const FFmpegfs_Format *ffmpegfs_format = params.current_format(virtualfile); if (ffmpegfs_format->is_frameset()) { // Generate set of all frames if (!virtualfile->m_video_frame_count) { int res = get_source_properties(origpath, virtualfile); if (res < 0) { throw EINVAL; } } //Logging::debug(origpath, "readdir: Creating frame set of %1 frames. %2", virtualfile->m_video_frame_count, virtualfile->m_origfile); for (uint32_t frame_no = 1; frame_no <= virtualfile->m_video_frame_count; frame_no++) { make_file(buf, filler, virtualfile->m_type, origpath, make_filename(frame_no, params.current_format(virtualfile)->fileext()), virtualfile->m_predicted_size, virtualfile->m_st.st_ctime, VIRTUALFLAG_FRAME); /**< @todo Calculate correct file size for frame image in set */ } } else if (ffmpegfs_format->is_hls()) { int res = make_hls_fileset(buf, filler, origpath, virtualfile); if (res < 0) { throw EINVAL; } } else if (/*virtualfile != nullptr && */virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { // Fill in list for cue sheet load_path(origpath, nullptr, buf, filler); } errno = 0; // Just to make sure - reset any error } catch (int _errno) { errno = _errno; } } return -errno; } /** * @brief Get file attributes. * @param[in] path - Path of virtual file. * @param[in] stbuf - Buffer to store information. * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_getattr(const char *path, struct stat *stbuf, struct fuse_file_info * /*fi*/) { Logging::trace(path, "getattr"); if (is_blocked(path)) { errno = ENOENT; return -errno; } std::string origpath; append_basepath(&origpath, path); LPVIRTUALFILE virtualfile = find_original(&origpath); VIRTUALTYPE type = (virtualfile != nullptr) ? virtualfile->m_type : VIRTUALTYPE::DISK; int flags = (virtualfile != nullptr) ? virtualfile->m_flags : VIRTUALFLAG_NONE; if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_HIDDEN)) { errno = ENOENT; return -errno; } if (virtualfile == nullptr && lstat(origpath.c_str(), stbuf) == 0) { // File was not yet created as virtual file, but physically exists const FFmpegfs_Format *current_format = nullptr; std::string filename(origpath); if (S_ISREG(stbuf->st_mode) && virtual_name(&filename, "", ¤t_format)) { if (!current_format->is_multiformat()) { // Regular files Logging::trace(origpath, "getattr: Existing file."); errno = 0; return 0; } else { Logging::trace(origpath, "getattr: Creating frame set directory of file."); flags |= VIRTUALFLAG_FILESET; if (current_format->is_frameset()) { flags |= VIRTUALFLAG_FRAME; } else if (current_format->is_hls()) { flags |= VIRTUALFLAG_HLS; } } } else { // Pass-through for regular files Logging::trace(origpath, "getattr: Treating existing file/directory as passthrough."); errno = 0; return 0; } } else if ((flags & VIRTUALFLAG_PASSTHROUGH) && lstat(origpath.c_str(), stbuf) == 0) { // File physically exists and is marked as passthrough Logging::debug(origpath, "getattr: File not recoded because --recodesame=NO."); errno = 0; return 0; } else { // Not really an error. errno = 0; } // This is a virtual file bool no_check = false; switch (type) { case VIRTUALTYPE::SCRIPT: { // Use stored status mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); errno = 0; break; } #ifdef USE_LIBVCD case VIRTUALTYPE::VCD: #endif // USE_LIBVCD #ifdef USE_LIBDVD case VIRTUALTYPE::DVD: #endif // USE_LIBDVD #ifdef USE_LIBBLURAY case VIRTUALTYPE::BLURAY: #endif // USE_LIBBLURAY { // Use stored status mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); no_check = true; // FILETYPE already known, no need to check again. FALLTHROUGH_INTENDED; } case VIRTUALTYPE::DISK: { if (virtualfile != nullptr && (flags & (VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS | VIRTUALFLAG_DIRECTORY | VIRTUALFLAG_CUESHEET))) { mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); errno = 0; // Just to make sure - reset any error break; } if (virtualfile == nullptr || !(virtualfile->m_flags & VIRTUALFLAG_FILESET)) { if (!no_check && lstat(origpath.c_str(), stbuf) == -1) { // If file does not exist here we can assume it's some sort of virtual file: Regular, DVD, S/VCD, cue sheet track int error = -errno; virtualfile = find_original(&origpath); if (virtualfile == nullptr) { std::string pathonly(origpath); int res = 0; remove_filename(&pathonly); #ifdef USE_LIBVCD //if (res <= 0) Not necessary here, will always be true { // Returns -errno or number or titles on VCD res = check_vcd(pathonly); } #endif // USE_LIBVCD #ifdef USE_LIBDVD if (res <= 0) { // Returns -errno or number or titles on DVD res = check_dvd(pathonly); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY if (res <= 0) { // Returns -errno or number or titles on Blu-ray res = check_bluray(pathonly); } #endif // USE_LIBBLURAY if (res <= 0) { if (check_ext(TRACKDIR, origpath)) { std::string origfile(origpath); remove_ext(&origfile); // remove TRACKDIR extension to get original media file name Logging::trace(origfile, "getattr: Checking for cue sheet."); if (virtual_name(&origfile)) { // Returns -errno or number or titles in cue sheet res = check_cuesheet(origfile); } } } if (ffmpeg_format[FORMAT::VIDEO].is_frameset()) { LPVIRTUALFILE parent_file = find_parent(origpath); if (parent_file != nullptr && (parent_file->m_flags & VIRTUALFLAG_DIRECTORY) && (parent_file->m_flags & VIRTUALFLAG_FILESET)) { // Generate set of all frames if (!parent_file->m_video_frame_count) { int res2 = get_source_properties(origpath, parent_file); if (res2 < 0) { return res2; } } for (uint32_t frame_no = 1; frame_no <= parent_file->m_video_frame_count; frame_no++) { make_file(nullptr, nullptr, parent_file->m_type, parent_file->m_destfile + "/", make_filename(frame_no, params.current_format(parent_file)->fileext()), parent_file->m_predicted_size, parent_file->m_st.st_ctime, VIRTUALFLAG_FRAME); /**< @todo Calculate correct file size for frame image in set */ } LPVIRTUALFILE virtualfile2 = find_original(origpath); if (virtualfile2 == nullptr) { // File does not exist return -ENOENT; } mempcpy(stbuf, &virtualfile2->m_st, sizeof(struct stat)); // Clear errors errno = 0; return 0; } } else if (ffmpeg_format[FORMAT::VIDEO].is_hls()) { LPVIRTUALFILE parent_file = find_parent(origpath); if (parent_file != nullptr && (parent_file->m_flags & VIRTUALFLAG_DIRECTORY) && (parent_file->m_flags & VIRTUALFLAG_FILESET)) { if (!parent_file->m_video_frame_count) //***< @todo HLS format: Do audio files source properties get checked over and over? { int res2 = get_source_properties(origpath, parent_file); if (res2 < 0) { return res2; } } make_hls_fileset(nullptr, nullptr, parent_file->m_destfile + "/", parent_file); LPVIRTUALFILE virtualfile2 = find_original(origpath); if (virtualfile2 == nullptr) { // File does not exist return -ENOENT; } mempcpy(stbuf, &virtualfile2->m_st, sizeof(struct stat)); // Clear errors errno = 0; return 0; } } if (res <= 0) { // No Blu-ray/DVD/VCD found or error reading disk return (!res ? error : res); } } virtualfile = find_original(&origpath); if (virtualfile == nullptr) { // Not a DVD/VCD/Blu-ray file or cue sheet track return -ENOENT; } mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); } if (flags & VIRTUALFLAG_FILESET) { int flags2 = 0; // Change file to virtual directory for the frame set. Keep permissions. stat_to_dir(stbuf); flags_to_dir(&flags2); append_sep(&origpath); insert_file(type, origpath, stbuf, flags2); } else if (S_ISREG(stbuf->st_mode)) { // Get size for resulting output file from regular file, otherwise it's a symbolic link or a virtual frame set. if (virtualfile == nullptr) { // We should not never end here - report bad file number. return -EBADF; } if (!transcoder_cached_filesize(virtualfile, stbuf)) { Cache_Entry* cache_entry = transcoder_new(virtualfile, false); if (cache_entry == nullptr) { return -errno; } stat_set_size(stbuf, transcoder_get_size(cache_entry)); transcoder_delete(cache_entry); } } errno = 0; // Just to make sure - reset any error } else // if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_DIRECTORY)) { // Frame set, simply report stat. mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); errno = 0; } break; } // We should never come here but this shuts up a warning case VIRTUALTYPE::PASSTHROUGH: case VIRTUALTYPE::BUFFER: { break; } } return 0; } /* * @brief Get attributes from an open file. * @param[in] path Virtual path to inspect. * @param[out] stbuf Buffer receiving the attributes. * @param[in] fi FUSE file information. * @return On success, returns 0. On error, returns -errno. */ //static int ffmpegfs_fgetattr(const char *path, struct stat * stbuf, struct fuse_file_info *fi) //{ // std::string origpath; // Logging::trace(path, "fgetattr"); // errno = 0; // append_basepath(&origpath, path); // LPCVIRTUALFILE virtualfile = find_original(&origpath); // if (virtualfile != nullptr && (virtualfile->m_flags & VIRTUALFLAG_HIDDEN)) // { // errno = ENOENT; // return -errno; // } // if ((virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH)) && lstat(origpath.c_str(), stbuf) == 0) // { // // passthrough for regular files // errno = 0; // return 0; // } // else // { // // Not really an error. // errno = 0; // } // // This is a virtual file // bool no_check = false; // switch (virtualfile->m_type) // { //#ifdef USE_LIBVCD // case VIRTUALTYPE::VCD: //#endif // USE_LIBVCD //#ifdef USE_LIBDVD // case VIRTUALTYPE::DVD: //#endif // USE_LIBDVD //#ifdef USE_LIBBLURAY // case VIRTUALTYPE::BLURAY: //#endif // USE_LIBBLURAY // { // // Use stored status // mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); // no_check = true; // FALLTHROUGH_INTENDED; // } // case VIRTUALTYPE::DISK: // { // if (virtualfile->m_flags & (VIRTUALFLAG_FILESET | VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS | VIRTUALFLAG_DIRECTORY)) // { // mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); // } // else // { // if (!no_check) // { // if (lstat(origpath.c_str(), stbuf) == -1) // { // return -errno; // } // } // // Get size for resulting output file from regular file, otherwise it's a symbolic link. // if (S_ISREG(stbuf->st_mode)) // { // Cache_Entry* cache_entry = reinterpret_cast(fi->fh); // if (cache_entry == nullptr) // { // Logging::error(path, "fgetattr: Tried to stat unopen file."); // errno = EBADF; // return -errno; // } // uint32_t segment_no = 0; // stat_set_size(stbuf, transcoder_buffer_watermark(cache_entry, segment_no)); // } // } // errno = 0; // Just to make sure - reset any error // break; // } // case VIRTUALTYPE::SCRIPT: // { // mempcpy(stbuf, &virtualfile->m_st, sizeof(struct stat)); // break; // } // // We should never come here but this shuts up a warning // case VIRTUALTYPE::PASSTHROUGH: // case VIRTUALTYPE::BUFFER: // { // break; // } // } // return 0; //} /** * @brief Open a virtual or passthrough file. * @param[in] path Virtual path to open. * @param[in,out] fi FUSE file information. For transcoded files, fh receives the cache entry handle. * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_open(const char *path, struct fuse_file_info *fi) { std::string origpath; Logging::trace(path, "open"); append_basepath(&origpath, path); LPVIRTUALFILE virtualfile = find_original(&origpath); if (virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH)) { int fd = open(origpath.c_str(), fi->flags); if (fd != -1) { close(fd); // File is real and can be opened. errno = 0; } else { if (errno == ENOENT) { // File does not exist? We should never end up here... errno = EINVAL; } // If file does exist, but can't be opened, return error. } return -errno; } // This is a virtual file kick_next(virtualfile); switch (virtualfile->m_type) { case VIRTUALTYPE::SCRIPT: { errno = 0; break; } #ifdef USE_LIBVCD case VIRTUALTYPE::VCD: #endif // USE_LIBVCD #ifdef USE_LIBDVD case VIRTUALTYPE::DVD: #endif // USE_LIBDVD #ifdef USE_LIBBLURAY case VIRTUALTYPE::BLURAY: #endif // USE_LIBBLURAY case VIRTUALTYPE::DISK: { if (virtualfile->m_flags & (VIRTUALFLAG_FRAME | VIRTUALFLAG_HLS)) { LPVIRTUALFILE parent_file = find_parent(origpath); if (parent_file != nullptr) { Cache_Entry* cache_entry; // HLS segments and frame-set files are opened through their // parent cache entry. The exact segment/frame number is only // known in ffmpegfs_read(), so do not start transcoding here. // Starting here would always begin at segment/frame 1 and only // seek afterwards. cache_entry = transcoder_new(parent_file, false); if (cache_entry == nullptr) { return -errno; } // Store transcoder in the fuse_file_info structure. fi->fh = reinterpret_cast(cache_entry); // Need this because we do not know the exact size in advance. fi->direct_io = 1; //fi->keep_cache = 1; // Clear errors errno = 0; } } else if (!(virtualfile->m_flags & VIRTUALFLAG_FILESET)) { Cache_Entry* cache_entry; cache_entry = transcoder_new(virtualfile, true); if (cache_entry == nullptr) { return -errno; } // Store transcoder in the fuse_file_info structure. fi->fh = reinterpret_cast(cache_entry); // Need this because we do not know the exact size in advance. fi->direct_io = 1; //fi->keep_cache = 1; // Clear errors errno = 0; } break; } // We should never come here but this shuts up a warning case VIRTUALTYPE::PASSTHROUGH: case VIRTUALTYPE::BUFFER: { break; } } return 0; } /** * @brief Read data from an open file * @param[in] path * @param[in] buf * @param[in] size * @param[in] offset * @param[in] fi * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { std::string origpath; size_t locoffset = static_cast(offset); // Cast OK: offset can never be < 0. int bytes_read = 0; Logging::trace(path, "read: Reading %1 bytes from offset %2 to %3.", size, locoffset, size + locoffset); append_basepath(&origpath, path); LPVIRTUALFILE virtualfile = find_original(&origpath); if (virtualfile == nullptr || (virtualfile->m_flags & VIRTUALFLAG_PASSTHROUGH)) { int fd = open(origpath.c_str(), O_RDONLY); if (fd != -1) { // If this is a real file, pass the call through. bytes_read = static_cast(pread(fd, buf, size, offset)); close(fd); if (bytes_read >= 0) { return bytes_read; } else { return -errno; } } else if (errno != ENOENT) { // File does exist, but can't be opened. return -errno; } else { // File does not exist, and this is fine. errno = 0; } } // This is a virtual file bool success = true; if (virtualfile == nullptr) { errno = EINVAL; Logging::error(origpath.c_str(), "read: INTERNAL ERROR: ffmpegfs_read()! virtualfile == NULL"); return -errno; } switch (virtualfile->m_type) { case VIRTUALTYPE::SCRIPT: { if (locoffset >= virtualfile->m_file_contents.size()) { bytes_read = 0; break; } size_t bytes = size; if (locoffset + bytes > virtualfile->m_file_contents.size()) { bytes = virtualfile->m_file_contents.size() - locoffset; } if (bytes) { std::memcpy(buf, &virtualfile->m_file_contents[locoffset], bytes); } bytes_read = static_cast(bytes); break; } #ifdef USE_LIBVCD case VIRTUALTYPE::VCD: #endif // USE_LIBVCD #ifdef USE_LIBDVD case VIRTUALTYPE::DVD: #endif // USE_LIBDVD #ifdef USE_LIBBLURAY case VIRTUALTYPE::BLURAY: #endif // USE_LIBBLURAY case VIRTUALTYPE::DISK: { if (virtualfile->m_flags & VIRTUALFLAG_FRAME) { Cache_Entry* cache_entry; cache_entry = reinterpret_cast(fi->fh); if (cache_entry == nullptr) { if (errno) { Logging::error(origpath.c_str(), "read: Tried to read from unopen file: (%1) %2", errno, strerror(errno)); } return -errno; } uint32_t frame_no = 0; std::string filename = get_number(path, &frame_no); if (!frame_no) { errno = EINVAL; Logging::error(origpath.c_str(), "read: Unable to deduct frame no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno)); return -errno; } success = transcoder_read_frame(cache_entry, buf, locoffset, size, frame_no, &bytes_read, virtualfile); } else if (!(virtualfile->m_flags & VIRTUALFLAG_FILESET)) { Cache_Entry* cache_entry; cache_entry = reinterpret_cast(fi->fh); if (cache_entry == nullptr) { if (errno) { Logging::error(origpath.c_str(), "read: Tried to read from unopen file: (%1) %2", errno, strerror(errno)); } return -errno; } uint32_t segment_no = 0; if (virtualfile->m_flags & VIRTUALFLAG_HLS) { std::string filename = get_number(path, &segment_no); if (!segment_no) { errno = EINVAL; Logging::error(origpath.c_str(), "read: Unable to deduct segment no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno)); return -errno; } } success = transcoder_read(cache_entry, buf, locoffset, size, &bytes_read, segment_no); } break; } case VIRTUALTYPE::PASSTHROUGH: case VIRTUALTYPE::BUFFER: { break; } } if (success) { if (bytes_read) { Logging::trace(path, "read: Read %1 bytes from offset %2 to %3.", bytes_read, locoffset, static_cast(bytes_read) + locoffset); } else { Logging::trace(path, "read: Read output file to EOF."); } return bytes_read; } else { return -errno; } } /** * @brief Get file system statistics * @param[in] path * @param[in] stbuf * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_statfs(const char *path, struct statvfs *stbuf) { std::string origpath; Logging::trace(path, "statfs"); append_basepath(&origpath, path); // passthrough for regular files if (!origpath.empty() && statvfs(origpath.c_str(), stbuf) == 0) { errno = 0; // Just to make sure - reset any error return 0; } else { // Not really an error. errno = 0; } find_original(&origpath); statvfs(origpath.c_str(), stbuf); errno = 0; // Just to make sure - reset any error return 0; } /** * @brief Release an open file * @param[in] path * @param[in] fi * @return On success, returns 0. On error, returns -errno. */ static int ffmpegfs_release(const char *path, struct fuse_file_info *fi) { Cache_Entry* cache_entry = reinterpret_cast(fi->fh); Logging::trace(path, "release"); if (cache_entry != nullptr) { uint32_t segment_no = 0; if (cache_entry->virtualfile()->m_flags & VIRTUALFLAG_HLS) { std::string filename = get_number(path, &segment_no); if (!segment_no) { errno = EINVAL; Logging::error(path, "release: Unable to deduct segment no. from file name (%1): (%2) %3", filename.c_str(), errno, strerror(errno)); } else { cache_entry->m_buffer->close_file(segment_no - 1, CACHE_FLAG_RO); } } transcoder_delete(cache_entry); } return 0; } /** * @brief Initialise the filesystem. * @param[in,out] conn FUSE connection information. Currently not modified. * @param[in,out] cfg FUSE configuration. Currently not modified. * @return nullptr. */ static void *ffmpegfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { (void) conn; (void) cfg; Logging::info(nullptr, "%1 V%2 initialising.", PACKAGE_NAME, FFMPEFS_VERSION); Logging::info(nullptr, "Mapping '%1' to '%2'.", params.m_basepath.c_str(), params.m_mountpath.c_str()); if (docker_client) { Logging::info(nullptr, "Running inside Docker."); } struct sigaction sa; std::memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGINT); sa.sa_handler = sighandler; sigaction(SIGINT, &sa, &oldHandler); // We need synchronous reads. //conn->async_read = 0; //conn->async_read = 1; //conn->want |= FUSE_CAP_ASYNC_READ; //conn->want |= FUSE_CAP_SPLICE_READ; if (params.m_cache_maintenance) { if (!start_cache_maintenance(params.m_cache_maintenance)) { exit(1); } } if (params.m_enablescript) { prepare_script(); } if (tp == nullptr) { tp = std::make_unique(params.m_max_threads); } tp->init(); return nullptr; } /** * @brief Clean up filesystem * @param[in] p - unused */ static void ffmpegfs_destroy(__attribute__((unused)) void * p) { Logging::info(nullptr, "%1 V%2 terminating.", PACKAGE_NAME, FFMPEFS_VERSION); std::printf("%s V%s terminating\n", PACKAGE_NAME, FFMPEFS_VERSION); stop_cache_maintenance(); transcoder_exit(); transcoder_free(); if (tp != nullptr) { tp->tear_down(); tp.reset(); } script_file.clear(); Logging::info(nullptr, "%1 V%2 terminated.", PACKAGE_NAME, FFMPEFS_VERSION); } /** * @brief Calculate the video frame count. * @param[in] origpath - Path of original file. * @param[inout] virtualfile - Virtual file object to modify. * @return On success, returns 0. On error, returns -errno. */ static int get_source_properties(const std::string & origpath, LPVIRTUALFILE virtualfile) { Cache_Entry* cache_entry = transcoder_new(virtualfile, false); if (cache_entry == nullptr) { return -errno; } transcoder_delete(cache_entry); Logging::debug(origpath, "Duration: %1 Frames: %2 Segments: %3", virtualfile->m_duration, virtualfile->m_video_frame_count, virtualfile->get_segment_count()); return 0; } /** * @brief Initialise a stat structure. * @param[in] stbuf - struct stat to fill in. * @param[in] fsize - size of the corresponding file. * @param[in] ftime - File time (creation/modified/access) of the corresponding file. * @param[in] directory - If true, the structure is set up for a directory. */ static void init_stat(struct stat * stbuf, size_t fsize, time_t ftime, bool directory) { std::memset(stbuf, 0, sizeof(struct stat)); stbuf->st_mode = DEFFILEMODE; //S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; if (directory) { stbuf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; stbuf->st_nlink = 2; } else { stbuf->st_mode |= S_IFREG; stbuf->st_nlink = 1; } stat_set_size(stbuf, fsize); // Set current user as owner stbuf->st_uid = getuid(); stbuf->st_gid = getgid(); // Use current date/time stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = ftime; } /** * @brief Make a virtual file. * @param[in] buf - FUSE buffer to fill. * @param[in] filler - Filler function. * @param[in] type - Type of virtual file. * @param[in] origpath - Original path. * @param[in] filename - Name of virtual file. * @param[in] flags - On of the VIRTUALFLAG_ macros. * @param[in] fsize - Size of virtual file. * @param[in] ftime - Time of virtual file. * @return Returns constant pointer to VIRTUALFILE object of file. */ static LPVIRTUALFILE make_file(void *buf, fuse_fill_dir_t filler, VIRTUALTYPE type, const std::string & origpath, const std::string & filename, size_t fsize, time_t ftime, int flags) { struct stat stbuf; init_stat(&stbuf, fsize, ftime, false); if (add_fuse_entry(buf, filler, filename, &stbuf, 0)) { return nullptr; } return insert_file(type, origpath + filename, &stbuf, flags); } /** * @brief Read the virtual script file into memory and store in buffer. */ static void prepare_script() { std::string scriptsource; exepath(&scriptsource); scriptsource += params.m_scriptsource; Logging::debug(scriptsource, "Reading virtual script source."); FILE *fpi = fopen(scriptsource.c_str(), "rt"); if (fpi == nullptr) { Logging::warning(scriptsource, "File open failed. Disabling script: (%1) %2", errno, strerror(errno)); params.m_enablescript = false; } else { struct stat stbuf; if (fstat(fileno(fpi), &stbuf) == -1) { Logging::warning(scriptsource, "File could not be accessed. Disabling script: (%1) %2", errno, strerror(errno)); params.m_enablescript = false; } else { script_file.resize(static_cast(stbuf.st_size)); if (fread(&script_file[0], 1, static_cast(stbuf.st_size), fpi) != static_cast(stbuf.st_size)) { Logging::warning(scriptsource, "File could not be read. Disabling script: (%1) %2", errno, strerror(errno)); params.m_enablescript = false; } else { Logging::trace(scriptsource, "Read %1 bytes of script file.", script_file.size()); } } fclose(fpi); } } /** * @brief Convert file name from source to destination name. * @param[in] virtualpath - Name of source file, will be changed to destination name. * @param[in] origpath - Original path to file. May be empty string if filepath is already a full path. * @param[out] current_format - If format has been found points to format info, nullptr if not. * @return Returns true if format has been found and filename changed, false if not. */ static bool virtual_name(std::string * virtualpath, const std::string & origpath /*= ""*/, const FFmpegfs_Format **current_format /*= nullptr*/) { std::string ext; if (current_format != nullptr) { *current_format = nullptr; } if (!find_ext(&ext, *virtualpath) || is_passthrough(ext)) { return false; } if (!is_selected(ext)) { return false; } VIRTUALFILE newvirtualfile; newvirtualfile.m_origfile = origpath + *virtualpath; const FFmpegfs_Format *ffmpegfs_format = get_format(&newvirtualfile); if (ffmpegfs_format != nullptr) { if (params.m_oldnamescheme) { // Old filename scheme, creates duplicates replace_ext(virtualpath, ffmpegfs_format->fileext()); } else { // New name scheme append_ext(virtualpath, ffmpegfs_format->fileext()); } newvirtualfile.m_destfile = origpath + *virtualpath; newvirtualfile.m_virtfile = params.m_mountpath + *virtualpath; if (lstat(newvirtualfile.m_destfile.c_str(), &newvirtualfile.m_st) == 0) { if (params.m_recodesame == RECODESAME::NO) { newvirtualfile.m_flags |= VIRTUALFLAG_PASSTHROUGH; } if (ffmpegfs_format->is_multiformat()) { // Change file to directory for the frame set // Change file to virtual directory for the frame set. Keep permissions. stat_to_dir(&newvirtualfile.m_st); flags_to_dir(&newvirtualfile.m_flags); } } insert(newvirtualfile); if (current_format != nullptr) { *current_format = ffmpegfs_format; } return true; } return false; } /** * @brief Find mapped file by prefix. Normally used to find a path. * @param[in] map - File map with virtual files. * @param[in] search_for - Prefix (path) to search for. * @return If found, returns const_iterator to map entry. Returns map.cend() if not found. */ static FILENAME_MAP::const_iterator find_prefix(const FILENAME_MAP & map, const std::string & search_for) { FILENAME_MAP::const_iterator it = map.lower_bound(search_for); if (it != map.cend()) { const std::string & key = it->first; if (key.compare(0, search_for.size(), search_for) == 0) // Really a prefix? { return it; } } return map.cend(); } /** * @brief Insert virtualfile into list. * @param[in] virtualfile - VIRTUALFILE object to insert */ static void insert(const VIRTUALFILE & virtualfile) { filenames.emplace(virtualfile.m_destfile, virtualfile); rfilenames.emplace(virtualfile.m_origfile, virtualfile); } LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const struct stat * stbuf, int flags) { return insert_file(type, virtfile, virtfile, stbuf, flags); } LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const std::string & origfile, const struct stat * stbuf, int flags) { std::string sanitised_virtfile(sanitise_filepath(virtfile)); FILENAME_MAP::iterator it = filenames.find(sanitised_virtfile); if (it == filenames.cend()) { // Create new std::string sanitised_origfile(sanitise_filepath(origfile)); VIRTUALFILE virtualfile; std::memcpy(&virtualfile.m_st, stbuf, sizeof(struct stat)); virtualfile.m_type = type; virtualfile.m_flags = flags; virtualfile.m_format_idx = guess_format_idx(sanitised_origfile); // Make a guess, will be finalised later virtualfile.m_destfile = sanitised_virtfile; virtualfile.m_origfile = sanitised_origfile; virtualfile.m_virtfile = sanitised_origfile; //virtualfile.m_predicted_size = static_cast(stbuf->st_size); replace_start(&virtualfile.m_virtfile, params.m_basepath, params.m_mountpath); insert(virtualfile); it = filenames.find(sanitised_virtfile); } return &it->second; } /** * @brief Convert stbuf to directory * @param[inout] stbuf - Buffer to convert to directory */ static void stat_to_dir(struct stat *stbuf) { stbuf->st_mode &= ~static_cast(S_IFREG | S_IFLNK); stbuf->st_mode |= S_IFDIR; if (stbuf->st_mode & S_IRWXU) { stbuf->st_mode |= S_IXUSR; // Add user execute bit if user has read or write access } if (stbuf->st_mode & S_IRWXG) { stbuf->st_mode |= S_IXGRP; // Add group execute bit if group has read or write access } if (stbuf->st_mode & S_IRWXO) { stbuf->st_mode |= S_IXOTH; // Add other execute bit if other has read or write access } stbuf->st_nlink = 2; stbuf->st_size = stbuf->st_blksize; } /** * @brief Convert flags to directory * @param[inout] flags - Flags variable to change */ static void flags_to_dir(int *flags) { *flags |= VIRTUALFLAG_FILESET | VIRTUALFLAG_DIRECTORY; if (ffmpeg_format[FORMAT::VIDEO].is_frameset()) { *flags |= VIRTUALFLAG_FRAME; } else if (ffmpeg_format[FORMAT::VIDEO].is_hls()) { *flags |= VIRTUALFLAG_HLS; } } LPVIRTUALFILE insert_dir(VIRTUALTYPE type, const std::string & virtdir, const struct stat * stbuf, int flags) { struct stat stbufdir; std::memcpy(&stbufdir, stbuf, sizeof(stbufdir)); // Change file to directory for the frame set // Change file to virtual directory for the frame set. Keep permissions. stat_to_dir(&stbufdir); flags_to_dir(&flags); std::string path(virtdir); append_sep(&path); return insert_file(type, path, &stbufdir, flags); } LPVIRTUALFILE find_file(const std::string & virtfile) { FILENAME_MAP::iterator it = filenames.find(sanitise_filepath(virtfile)); errno = 0; return (it != filenames.end() ? &it->second : nullptr); } LPVIRTUALFILE find_file_from_orig(const std::string &origfile) { RFILENAME_MAP::iterator it = rfilenames.find(sanitise_filepath(origfile)); errno = 0; return (it != rfilenames.end() ? &it->second : nullptr); } bool check_path(const std::string & path) { FILENAME_MAP::const_iterator it = find_prefix(filenames, path); return (it != filenames.cend()); } int load_path(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler) { if (buf == nullptr) { // We can't add anything here if buf == nullptr return 0; } int title_count = 0; for (FILENAME_MAP::iterator it = filenames.lower_bound(path); it != filenames.end(); ++it) { std::string virtfilepath = it->first; LPVIRTUALFILE virtualfile = &it->second; if ( #ifdef USE_LIBVCD (virtualfile->m_type != VIRTUALTYPE::VCD) && #endif // USE_LIBVCD #ifdef USE_LIBDVD (virtualfile->m_type != VIRTUALTYPE::DVD) && #endif // USE_LIBDVD #ifdef USE_LIBBLURAY (virtualfile->m_type != VIRTUALTYPE::BLURAY) && #endif // USE_LIBBLURAY !(virtualfile->m_flags & VIRTUALFLAG_CUESHEET) ) { continue; } remove_filename(&virtfilepath); if (virtfilepath == path) // Really a prefix? { struct stat stbuf; std::string destfile(virtualfile->m_destfile); if (virtualfile->m_flags & VIRTUALFLAG_DIRECTORY) { // Is a directory, no need to translate the file name, just drop terminating separator remove_sep(&destfile); } remove_path(&destfile); title_count++; std::string cachefile; Buffer::make_cachefile_name(&cachefile, virtualfile->m_destfile, params.current_format(virtualfile)->fileext(), false); struct stat stbuf2; if (!lstat(cachefile.c_str(), &stbuf2)) { // Cache file exists, use cache file size here stat_set_size(&virtualfile->m_st, static_cast(stbuf2.st_size)); std::memcpy(&stbuf, &virtualfile->m_st, sizeof(struct stat)); } else { if (statbuf == nullptr) { std::memcpy(&stbuf, &virtualfile->m_st, sizeof(struct stat)); } else { std::memcpy(&stbuf, statbuf, sizeof(struct stat)); stat_set_size(&stbuf, static_cast(virtualfile->m_st.st_size)); } } if (add_fuse_entry(buf, filler, destfile, &stbuf, 0)) { // break; } } } return title_count; } /** * @brief Filter function used for scandir. * * Selects files only that can be processed with FFmpeg API. * * @param[in] de - dirent to check * @return Returns nonzero if dirent matches, 0 if not. */ static int selector(const struct dirent * de) { if (de->d_type & (DT_REG | DT_LNK)) { return (av_guess_format(nullptr, de->d_name, nullptr) != nullptr); } else { return 0; } } /** * @brief Scans the directory dirp * Works exactly like the scandir(3) function, the only * difference is that it returns the result in a std:vector. * @param[in] dirp - Directory to be searched. * @param[out] _namelist - Returns the list of files found * @param[in] selector - Entries for which selector() returns nonzero are stored in _namelist * @param[in] cmp - Entries are sorted using qsort(3) with the comparison function cmp(). May be nullptr for no sort. * @return Returns the number of directory entries selected. * On error, -1 is returned, with errno set to indicate * the error. */ static int scandir(const char *dirp, std::vector * _namelist, int (*selector) (const struct dirent *), int (*cmp) (const struct dirent **, const struct dirent **)) { struct dirent **namelist; int count = scandir(dirp, &namelist, selector, cmp); // cppcheck-suppress [nullPointer, ctunullpointer] _namelist->clear(); if (count != -1) { for (int n = 0; n < count; n++) { _namelist->push_back(*namelist[n]); free(namelist[n]); } free(namelist); } return count; } LPVIRTUALFILE find_original(const std::string & origpath) { std::string buffer(origpath); return find_original(&buffer); } LPVIRTUALFILE find_original(std::string * filepath) { sanitise_filepath(filepath); LPVIRTUALFILE virtualfile = find_file(*filepath); errno = 0; if (virtualfile != nullptr) { *filepath = virtualfile->m_origfile; return virtualfile; } else { // Fallback to old method (required if file accessed directly) std::string ext; if (!ffmpeg_format[FORMAT::VIDEO].is_hls() && find_ext(&ext, *filepath) && (strcasecmp(ext, ffmpeg_format[FORMAT::VIDEO].fileext()) == 0 || (params.smart_transcode() && strcasecmp(ext, ffmpeg_format[FORMAT::AUDIO].fileext()) == 0))) { std::string dir(*filepath); std::string searchexp(*filepath); std::string origfile; std::vector namelist; struct stat stbuf; int count; int found = 0; remove_filename(&dir); origfile = dir; // cppcheck-suppress nullPointer count = scandir(dir.c_str(), &namelist, selector, nullptr); if (count == -1) { if (errno != ENOTDIR) // If not a directory, simply ignore error { Logging::error(dir, "Error scanning directory: (%1) %2", errno, strerror(errno)); } return nullptr; } remove_path(&searchexp); remove_ext(&searchexp); for (size_t n = 0; n < static_cast(count); n++) { if (!strcmp(namelist[n].d_name, searchexp.c_str())) { append_filename(&origfile, namelist[n].d_name); sanitise_filepath(&origfile); found = 1; break; } } if (found && lstat(origfile.c_str(), &stbuf) == 0) { // The original file exists LPVIRTUALFILE virtualfile2; if (*filepath != origfile) { virtualfile2 = insert_file(VIRTUALTYPE::DISK, *filepath, origfile, &stbuf); ///<* @todo This probably won't work, need to redo "Fallback to old method" *filepath = origfile; } else { virtualfile2 = insert_file(VIRTUALTYPE::DISK, origfile, &stbuf, VIRTUALFLAG_PASSTHROUGH); } return virtualfile2; } else { // File does not exist; this is a virtual file, not an error errno = 0; } } } // Source file exists with no supported extension, keep path return nullptr; } LPVIRTUALFILE find_parent(const std::string & origpath) { std::string filepath(origpath); remove_filename(&filepath); remove_sep(&filepath); return find_original(&filepath); } /** * @brief Build a virtual HLS file set * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @param[in] origpath - The original file * @param[in] virtualfile - LPCVIRTUALFILE of file to create file set for * @return On success, returns 0. On error, returns -errno. */ static int make_hls_fileset(void * buf, fuse_fill_dir_t filler, const std::string & origpath, LPVIRTUALFILE virtualfile) { // Generate set of TS segment files and necessary M3U lists if (!virtualfile->get_segment_count()) { int res = get_source_properties(origpath, virtualfile); if (res < 0) { return res; } } if (virtualfile->get_segment_count()) { std::string master_contents; std::string index_0_av_contents; // Examples... //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1250,RESOLUTION=720x406,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n" //"index_0_av.m3u8\n"; //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2647000,RESOLUTION=1280x720,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n" //"index_1_av.m3u8\n" //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=922000,RESOLUTION=640x360,CODECS= \"avc1.77.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n" //"index_2_av.m3u8\n" //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=448000,RESOLUTION=384x216,CODECS= \"avc1.66.30, mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n" //"index_3_av.m3u8\n" //"#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=61000,CODECS= \"mp4a.40.2 \",CLOSED-CAPTIONS=NONE\n" //"index_3_a.m3u8\n"; master_contents = "#EXTM3U\n" "#EXT-X-STREAM-INF:PROGRAM-ID=1\n" "index_0_av.m3u8\n"; strsprintf(&index_0_av_contents, "#EXTM3U\n" "#EXT-X-TARGETDURATION:%i\n" "#EXT-X-ALLOW-CACHE:YES\n" "#EXT-X-PLAYLIST-TYPE:VOD\n" "#EXT-X-VERSION:3\n" "#EXT-X-MEDIA-SEQUENCE:1\n", static_cast(params.m_segment_duration / AV_TIME_BASE)); int64_t remaining_duration = virtualfile->m_duration % params.m_segment_duration; size_t segment_size = virtualfile->m_predicted_size / virtualfile->get_segment_count(); for (uint32_t file_no = 1; file_no <= virtualfile->get_segment_count(); file_no++) { std::string buffer; std::string segment_name = make_filename(file_no, params.current_format(virtualfile)->fileext()); struct stat stbuf; std::string cachefile; std::string _origpath(origpath); remove_sep(&_origpath); std::string filename(_origpath); filename.append("."); filename.append(segment_name); Buffer::make_cachefile_name(&cachefile, filename, params.current_format(virtualfile)->fileext(), false); if (!lstat(cachefile.c_str(), &stbuf)) { make_file(buf, filler, virtualfile->m_type, origpath, segment_name, static_cast(stbuf.st_size), virtualfile->m_st.st_ctime, VIRTUALFLAG_HLS); } else { make_file(buf, filler, virtualfile->m_type, origpath, segment_name, segment_size, virtualfile->m_st.st_ctime, VIRTUALFLAG_HLS); } if (file_no < virtualfile->get_segment_count()) { strsprintf(&buffer, "#EXTINF:%.3f,\n", static_cast(params.m_segment_duration) / AV_TIME_BASE); } else { strsprintf(&buffer, "#EXTINF:%.3f,\n", static_cast(remaining_duration) / AV_TIME_BASE); } index_0_av_contents += buffer; index_0_av_contents += segment_name; index_0_av_contents += "\n"; } index_0_av_contents += "#EXT-X-ENDLIST\n"; LPVIRTUALFILE child_file; child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "master.m3u8", master_contents.size(), virtualfile->m_st.st_ctime); std::copy(master_contents.begin(), master_contents.end(), std::back_inserter(child_file->m_file_contents)); child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "index_0_av.m3u8", index_0_av_contents.size(), virtualfile->m_st.st_ctime, VIRTUALFLAG_NONE); std::copy(index_0_av_contents.begin(), index_0_av_contents.end(), std::back_inserter(child_file->m_file_contents)); { // Demo code adapted from: https://github.com/video-dev/hls.js/ std::string hls_html; hls_html = "\n" "\n" "\n" " HLS Demo\n" " \n" " \n" "\n" "\n" "\n" "
\n" "

Hls.js demo - basic usage

\n" " \n" "
\n" " \n" "\n" "\n" "\n"; child_file = make_file(buf, filler, VIRTUALTYPE::SCRIPT, origpath, "hls.html", hls_html.size(), virtualfile->m_st.st_ctime, VIRTUALFLAG_NONE); std::copy(hls_html.begin(), hls_html.end(), std::back_inserter(child_file->m_file_contents)); } } return 0; } /** * @brief Give next song in cuesheet list a kick start * Starts transcoding of the next song on the cuesheet list * to ensure a somewhat gapless start when the current song * finishes. Next song can be played from cache and start * faster then. * @todo Will work only if transcoding finishes within timeout. * Probably remove or raise timeout here. * @param[in] virtualfile - VIRTUALFILE object of current song * @return On success, returns 0. On error, returns -errno. */ static int kick_next(LPVIRTUALFILE virtualfile) { if (virtualfile == nullptr) { // Not OK, should not happen return -EINVAL; } if (virtualfile->m_cuesheet_track.m_nextfile == nullptr) { // No next file return 0; } LPVIRTUALFILE nextvirtualfile = virtualfile->m_cuesheet_track.m_nextfile; Logging::debug(virtualfile->m_destfile, "Preparing next file: %1", nextvirtualfile->m_destfile.c_str()); Cache_Entry* cache_entry = transcoder_new(nextvirtualfile, true); /** @todo Disable timeout */ if (cache_entry == nullptr) { return -errno; } transcoder_delete(cache_entry); return 0; } /** * @brief Replacement SIGINT handler. * * FUSE handles SIGINT internally, but because there are extra threads running while transcoding this * mechanism does not properly work. We implement our own SIGINT handler to ensure proper shutdown of * all threads. Next we restore the original handler and dispatch the signal to it. * * @param[in] signum - Signal to handle. Must be SIGINT. */ static void sighandler(int signum) { if (signum == SIGINT) { Logging::warning(nullptr, "Caught SIGINT, shutting down now..."); // Make our threads terminate now transcoder_exit(); // Restore fuse's handler sigaction(SIGINT, &oldHandler, nullptr); // Dispatch to fuse's handler raise(SIGINT); } } /** * @brief Extract the number for a file name * @param[in] path - Path and filename of requested file * @param[out] value - Returns the number extracted * @return Returns the filename that was processed, without path. */ static std::string get_number(const char *path, uint32_t *value) { std::string filename(path); // Get frame number remove_path(&filename); *value = static_cast(std::stoi(filename)); // Extract frame or segment number. May be more fancy in the future. Currently just get number from filename part. return filename; } /** * @brief Try to guess the format index (audio or video) for a file. * @param[in] filepath - Name of the file, path my be included, but not required. * @return Index 0 or 1 */ static size_t guess_format_idx(const std::string & filepath) { const AVOutputFormat* oformat = ::av_guess_format(nullptr, filepath.c_str(), nullptr); if (oformat != nullptr) { if (!params.smart_transcode()) { // Not smart encoding: use first format (video file) return 0; } else { // Smart transcoding if (ffmpeg_format[FORMAT::VIDEO].video_codec() != AV_CODEC_ID_NONE && oformat->video_codec != AV_CODEC_ID_NONE && !is_album_art(oformat->video_codec)) { // Is a video: use first format (video file) return 0; } else if (ffmpeg_format[FORMAT::AUDIO].audio_codec() != AV_CODEC_ID_NONE && oformat->audio_codec != AV_CODEC_ID_NONE) { // For audio only, use second format (audio only file) return 1; } } } return 0; } /** * @brief Open file with FFmpeg API and parse for streams and cue sheet. * @param[inout] newvirtualfile - VIRTUALFILE object of file to parse * @return On success, returns 0. On error, returns a negative AVERROR value. */ static int parse_file(LPVIRTUALFILE newvirtualfile) { AVFormatContext *format_ctx = nullptr; int res; try { Logging::debug(newvirtualfile->m_origfile, "Creating a new format context and parsing the file."); res = avformat_open_input(&format_ctx, newvirtualfile->m_origfile.c_str(), nullptr, nullptr); if (res) { Logging::trace(newvirtualfile->m_origfile, "No parseable file: %1", ffmpeg_geterror(res).c_str()); throw res; } res = avformat_find_stream_info(format_ctx, nullptr); if (res < 0) { Logging::error(newvirtualfile->m_origfile, "Cannot find stream information: %1", ffmpeg_geterror(res).c_str()); throw res; } // Check for an embedded cue sheet AVDictionaryEntry *tag = av_dict_get(format_ctx->metadata, "CUESHEET", nullptr, AV_DICT_IGNORE_SUFFIX); if (tag != nullptr) { // Found cue sheet Logging::trace(newvirtualfile->m_origfile, "Found an embedded cue sheet."); newvirtualfile->m_cuesheet = tag->value; newvirtualfile->m_cuesheet += "\r\n"; // cue_parse_string() reports syntax error if string does not end with newline replace_all(&newvirtualfile->m_cuesheet, "\r\n", "\n"); // Convert all to unix } // Check for audio/video/subtitles int ret = get_audio_props(format_ctx, &newvirtualfile->m_channels, &newvirtualfile->m_sample_rate); if (ret < 0) { if (ret != AVERROR_STREAM_NOT_FOUND) // Not an error, no audio is OK { Logging::error(newvirtualfile->m_origfile, "Could not find audio stream in input file (error '%1').", ffmpeg_geterror(ret).c_str()); } } else { newvirtualfile->m_has_audio = true; } newvirtualfile->m_duration = format_ctx->duration; struct stat stbuf; if (lstat(newvirtualfile->m_origfile.c_str(), &stbuf) == 0) { std::memcpy(&newvirtualfile->m_st, &stbuf, sizeof(stbuf)); } for (unsigned int stream_idx = 0; stream_idx < format_ctx->nb_streams; stream_idx++) { switch (format_ctx->streams[stream_idx]->codecpar->codec_type) { case AVMEDIA_TYPE_VIDEO: { if (!is_album_art(format_ctx->streams[stream_idx]->codecpar->codec_id)) { newvirtualfile->m_has_video = true; } break; } case AVMEDIA_TYPE_SUBTITLE: { newvirtualfile->m_has_subtitle = true; break; } default: { break; } } } } catch (int _res) { res = _res; } if (format_ctx != nullptr) { avformat_close_input(&format_ctx); } return res; } /** * @brief Get FFmpegfs_Format for the file. * @param[inout] newvirtualfile - VIRTUALFILE object of file to parse * @return On success, returns true. On error, returns false. */ static const FFmpegfs_Format * get_format(LPVIRTUALFILE newvirtualfile) { LPVIRTUALFILE virtualfile = find_file_from_orig(newvirtualfile->m_origfile); if (virtualfile != nullptr) { // We already know the file! return params.current_format(virtualfile); } if (parse_file(newvirtualfile) < 0) { return nullptr; } Logging::trace(newvirtualfile->m_origfile, "Audio: %1 Video: %2 Subtitles: %3", newvirtualfile->m_has_audio, newvirtualfile->m_has_video, newvirtualfile->m_has_subtitle); if (!params.smart_transcode()) { // Not smart encoding: use first format (video file) newvirtualfile->m_format_idx = 0; return &ffmpeg_format[FORMAT::VIDEO]; } else { if (newvirtualfile->m_has_video) { newvirtualfile->m_format_idx = 0; return &ffmpeg_format[FORMAT::VIDEO]; } if (newvirtualfile->m_has_audio) { newvirtualfile->m_format_idx = 1; return &ffmpeg_format[FORMAT::AUDIO]; } } return nullptr; } int add_fuse_entry(void *buf, fuse_fill_dir_t filler, const std::string & name, const struct stat *stbuf, off_t /*off*/) { if (buf == nullptr || filler == nullptr) { return 0; } // Issue #173: FUSE_FILL_DIR_PLUS erstmal NICHT nutzen return filler(buf, name.c_str(), stbuf, 0, static_cast(0)); } int add_dotdot(void *buf, fuse_fill_dir_t filler, const struct stat *stbuf, off_t off) { struct stat *stbuf2 = nullptr; struct stat stbuf3; if (stbuf != nullptr) { stbuf2 = &stbuf3; init_stat(stbuf2, 0, stbuf->st_ctime, true); } add_fuse_entry(buf, filler, ".", stbuf2, off); add_fuse_entry(buf, filler, "..", stbuf2, off); return 0; } ffmpegfs-2.50/src/cache_maintenance.h0000664000175000017500000000410715177713600013276 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cache_maintenance.h * @brief %Cache maintenance * * Creates a POSIX timer that starts the cache maintenance in preset * intervals. To ensure that only one instance of FFmpegfs cleans up * the cache a shared memory area and a named semaphore is also created. * * The first FFmpegfs process acts as master, all subsequently started * instances will be clients. If the master process goes away one of * the clients will automatically take over as master. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef CACHE_MAINTENANCE_H #define CACHE_MAINTENANCE_H #pragma once #include /** * @brief Start cache maintenance timer. * @param[in] interval - Interval in seconds to run timer at. * @return On success, returns true. On error, returns false. Check errno for details. */ bool start_cache_maintenance(time_t interval); /** * @brief Stop cache maintenance timer. * @return On success, returns true. On error, returns false. Check errno for details. */ bool stop_cache_maintenance(); #endif // CACHE_MAINTENANCE_H ffmpegfs-2.50/src/ffmpeg_utils.cc0000664000175000017500000021316015177713600012514 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_utils.cc * @brief FFmpegfs utility set implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #include "libavutil/ffversion.h" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "id3v1tag.h" #include "ffmpegfs.h" #include "ffmpeg_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H // This causes problems because it includes defines that collide // with out config.h. #undef HAVE_CONFIG_H #endif // HAVE_CONFIG_H #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop #include #include #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif static int is_device(__attribute__((unused)) const AVClass *avclass); static std::string ffmpeg_libinfo(bool lib_exists, __attribute__((unused)) unsigned int version, __attribute__((unused)) const char *cfg, int version_minor, int version_major, int version_micro, const char * libname); #ifndef AV_ERROR_MAX_STRING_SIZE #define AV_ERROR_MAX_STRING_SIZE 128 /**< @brief Max. length of a FFmpeg error string */ #endif // AV_ERROR_MAX_STRING_SIZE typedef std::map FILETYPE_MAP; /**< @brief Map of file type. One entry per supported type. */ /** * List of supported file types */ static const FILETYPE_MAP filetype_map = { { "mp3", FILETYPE::MP3 }, { "mp4", FILETYPE::MP4 }, { "wav", FILETYPE::WAV }, { "ogg", FILETYPE::OGG }, { "webm", FILETYPE::WEBM }, { "mov", FILETYPE::MOV }, { "aiff", FILETYPE::AIFF }, { "opus", FILETYPE::OPUS }, { "prores", FILETYPE::PRORES }, { "alac", FILETYPE::ALAC }, { "png", FILETYPE::PNG }, { "jpg", FILETYPE::JPG }, { "bmp", FILETYPE::BMP }, { "ts", FILETYPE::TS }, { "hls", FILETYPE::HLS }, { "flac", FILETYPE::FLAC }, { "mkv", FILETYPE::MKV }, }; Format_Options::Format_Options() : m_format_map{ { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_NONE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE }}} , m_albumart_supported(false) { } Format_Options::Format_Options( std::string format_name, std::string fileext, FORMAT_MAP format, bool albumart_supported ) : m_format_name(std::move(format_name)) , m_fileext(std::move(fileext)) , m_format_map(std::move(format)) , m_albumart_supported(albumart_supported) { } AVCodecID Format_Options::video_codec() const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it == m_format_map.cend()) { // Output supports no video. End of story. return AV_CODEC_ID_NONE; } if (params.m_video_codec == AV_CODEC_ID_NONE) { return (it->second.m_video_codec[0]); // 1st array entry is the predefined codec } return params.m_video_codec; } bool Format_Options::is_video_codec_supported(AVCodecID codec_id) const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it != m_format_map.cend()) { for (const AVCodecID & video_codec_id : it->second.m_video_codec) { if (video_codec_id == codec_id) { return true; } } } return false; } std::string Format_Options::video_codec_list() const { std::string buffer; FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it != m_format_map.cend()) { for (const AVCodecID & video_codec_id : it->second.m_video_codec) { if (!buffer.empty()) { buffer += ", "; } buffer += get_video_codec_text(video_codec_id); } } return buffer; } AVCodecID Format_Options::audio_codec() const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it == m_format_map.cend()) { // Output supports no audio??? Well then, end of story. return AV_CODEC_ID_NONE; } if (params.m_audio_codec == AV_CODEC_ID_NONE) { return (it->second.m_audio_codec[0]); // 1st array entry is the predefined codec } return params.m_audio_codec; } bool Format_Options::is_audio_codec_supported(AVCodecID codec_id) const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it != m_format_map.cend()) { for (const AVCodecID & audio_codec_id : it->second.m_audio_codec) { if (audio_codec_id == codec_id) { return true; } } } return false; } std::string Format_Options::audio_codec_list() const { std::string buffer; FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it != m_format_map.cend()) { for (const AVCodecID & audio_codec_id : it->second.m_audio_codec) { if (!buffer.empty()) { buffer += ", "; } buffer += get_audio_codec_text(audio_codec_id); } } return buffer; } AVSampleFormat Format_Options::sample_format() const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it == m_format_map.cend()) { return AV_SAMPLE_FMT_NONE; } return (it->second.m_sample_format); } bool Format_Options::is_sample_fmt_supported() const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); return (it != m_format_map.cend()); } std::string Format_Options::sample_fmt_list() const { std::string buffer; for (typename FORMAT_MAP::const_iterator it = m_format_map.cbegin(); it != m_format_map.cend();) { buffer += get_sampleformat_text(it->first); if (++it != m_format_map.cend()) { buffer += ", "; } } return buffer; } AVCodecID Format_Options::subtitle_codec(AVCodecID codec_id) const { FORMAT_MAP::const_iterator it = m_format_map.find(params.m_sample_fmt); if (it == m_format_map.cend()) { // Output supports no subtitles. End of story. return AV_CODEC_ID_NONE; } // Try to find direct match, prefer same as input stream for (const AVCodecID & subtitle_codec_id : it->second.m_subtitle_codec) { // Also match AV_CODEC_ID_DVD_SUBTITLE to AV_CODEC_ID_DVB_SUBTITLE if (subtitle_codec_id == codec_id || (codec_id == AV_CODEC_ID_DVD_SUBTITLE && subtitle_codec_id == AV_CODEC_ID_DVB_SUBTITLE)) { return subtitle_codec_id; } } // No direct match, try to find a text/text or bitmap/bitmap pair if (is_text_codec(codec_id)) { // Find a text based codec in the list for (const AVCodecID & subtitle_codec_id : it->second.m_subtitle_codec) { if (is_text_codec(subtitle_codec_id)) { return subtitle_codec_id; } } } else { // Find a bitmap based codec in the list for (const AVCodecID & subtitle_codec_id : it->second.m_subtitle_codec) { if (!is_text_codec(subtitle_codec_id)) { return subtitle_codec_id; } } } // No matching codec support return AV_CODEC_ID_NONE; } const FFmpegfs_Format::OPTIONS_MAP FFmpegfs_Format::m_options_map = { //{ // Descriptive name of the format. // File extension: Mostly, but not always, same as format. // { // { // SAMPLE_FMT enum, or SAMPLE_FMT::FMT_DONTCARE if source format decides // { // List of video codecs // List of audio codec(s) // List of subtitle codec(s) // AVSampleFormat to be used in encoding, if AV_SAMPLE_FMT_NONE will be determined by source // } // } // }, // If album arts are supported, true; false if no album arts are supported //} // ----------------------------------------------------------------------------------------------------------------------- // MP3 // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::MP3, { "mp3", "mp3", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_MP3 }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, true } }, // ----------------------------------------------------------------------------------------------------------------------- // MP4 // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::MP4, { "mp4", "mp4", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO }, { AV_CODEC_ID_AAC, AV_CODEC_ID_MP3 }, { AV_CODEC_ID_MOV_TEXT }, // MOV Text (Apple Text Media Handler): should be AV_CODEC_ID_WEBVTT, but we get "codec not currently supported in container" AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // WAV // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::WAV, { "wav", "wav", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S16LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_8, // 8 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_U8 }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_16, // 32 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S16LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_24, // 24 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S24LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_32, // 32 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S32LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_64, // 64 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S64LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_F16, // 16 bit float { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_F16LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_F24, // 24 bit float { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_F24LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_F32, // 32 bit float { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_F32LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_F64, // 64 bit float { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_F64LE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // OGG // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::OGG, { "ogg", "ogg", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_THEORA }, { AV_CODEC_ID_VORBIS }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // WebM // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::WEBM, { "webm", "webm", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_VP9, AV_CODEC_ID_VP8, AV_CODEC_ID_AV1 }, { AV_CODEC_ID_OPUS, AV_CODEC_ID_VORBIS }, { AV_CODEC_ID_WEBVTT }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // MOV // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::MOV, { "mov", "mov", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO }, { AV_CODEC_ID_AAC, AV_CODEC_ID_AC3, AV_CODEC_ID_MP3 }, { AV_CODEC_ID_MOV_TEXT }, // MOV Text (Apple Text Media Handler): should be AV_CODEC_ID_WEBVTT, but we get "codec not currently supported in container" AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // AIFF // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::AIFF, { "aiff", "aiff", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S16BE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_16, // 16 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S16BE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_S16, // 16 bit } }, { SAMPLE_FMT::FMT_32, // 32 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_PCM_S32BE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_S32, // 32 bit } }, }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // Opus // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::OPUS, { "opus", "opus", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_OPUS }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // Opus // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::PRORES, { "mov", "mov", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_PRORES }, { AV_CODEC_ID_PCM_S16LE }, { AV_CODEC_ID_MOV_TEXT }, // MOV Text (Apple Text Media Handler): should be AV_CODEC_ID_WEBVTT, but we get "codec not currently supported in container" AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // ALAC // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::ALAC, { "m4a", "m4a", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_ALAC }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_16, // 16 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_ALAC }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_S16P, // 16 bit planar } }, { SAMPLE_FMT::FMT_24, // 24 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_ALAC }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_S32P, // 32 bit planar, creates 24 bit ALAC } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // PNG // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::PNG, { "png", "png", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_PNG }, { AV_CODEC_ID_NONE }, // Audio codec(s) { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // JPG // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::JPG, { "jpg", "jpg", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_MJPEG }, { AV_CODEC_ID_NONE }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // BMP // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::BMP, { "bmp", "bmp", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_BMP }, { AV_CODEC_ID_NONE }, // Audio codec(s) { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // TS // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::TS, { "mpegts", "ts", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO }, { AV_CODEC_ID_AAC, AV_CODEC_ID_AC3, AV_CODEC_ID_MP3 }, { AV_CODEC_ID_DVB_SUBTITLE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // HLS, same as TS // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::HLS, { "mpegts", "ts", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO }, { AV_CODEC_ID_AAC, AV_CODEC_ID_AC3, AV_CODEC_ID_MP3 }, { AV_CODEC_ID_DVB_SUBTITLE }, AV_SAMPLE_FMT_NONE, } } }, false } }, // ----------------------------------------------------------------------------------------------------------------------- // FLAC // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::FLAC, { "flac", "flac", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_FLAC }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_NONE, } }, { SAMPLE_FMT::FMT_16, // 16 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_FLAC }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_S16, // Use 16 bit samples } }, { SAMPLE_FMT::FMT_24, // 24 bit { { AV_CODEC_ID_NONE }, { AV_CODEC_ID_FLAC }, { AV_CODEC_ID_NONE }, AV_SAMPLE_FMT_S32, // Use 24 bit samples (yes, S32 creates 24 bit samples) } } }, true } }, // ----------------------------------------------------------------------------------------------------------------------- // MKV // ----------------------------------------------------------------------------------------------------------------------- { FILETYPE::MKV, { "matroska", "mkv", { { SAMPLE_FMT::FMT_DONTCARE, { { AV_CODEC_ID_H264, AV_CODEC_ID_H265, AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO }, { AV_CODEC_ID_AAC, AV_CODEC_ID_AC3, AV_CODEC_ID_MP3 }, { AV_CODEC_ID_ASS, AV_CODEC_ID_SUBRIP, AV_CODEC_ID_WEBVTT, AV_CODEC_ID_DVB_SUBTITLE }, AV_SAMPLE_FMT_NONE, } } }, false } }, }; FFmpegfs_Format::FFmpegfs_Format() : m_cur_opts(&m_empty_options), m_filetype(FILETYPE::UNKNOWN) { } bool FFmpegfs_Format::init(const std::string & desttype) { OPTIONS_MAP::const_iterator it = m_options_map.find(get_filetype(desttype)); if (it == m_options_map.cend()) { // Not found/invalid desttype m_desttype.clear(); m_filetype = FILETYPE::UNKNOWN; m_cur_opts = &m_empty_options; return false; } else { // OK m_desttype = desttype; m_filetype = it->first; m_cur_opts = &it->second; return true; } } const std::string & FFmpegfs_Format::desttype() const { return m_desttype; } const std::string & FFmpegfs_Format::format_name() const { return m_cur_opts->m_format_name; } const std::string & FFmpegfs_Format::fileext() const { return m_cur_opts->m_fileext; } FILETYPE FFmpegfs_Format::filetype() const { return m_filetype; } bool FFmpegfs_Format::is_multiformat() const { return (is_frameset() || is_hls()); } bool FFmpegfs_Format::is_frameset() const { return (m_filetype == FILETYPE::JPG || m_filetype == FILETYPE::PNG || m_filetype == FILETYPE::BMP); } bool FFmpegfs_Format::is_hls() const { return (m_filetype == FILETYPE::HLS); } bool FFmpegfs_Format::albumart_supported() const { return m_cur_opts->m_albumart_supported; } AVCodecID FFmpegfs_Format::video_codec() const { return m_cur_opts->video_codec(); } bool FFmpegfs_Format::is_video_codec_supported(AVCodecID codec_id) const { return m_cur_opts->is_video_codec_supported(codec_id); } std::string FFmpegfs_Format::video_codec_list() const { return m_cur_opts->video_codec_list(); } AVCodecID FFmpegfs_Format::audio_codec() const { return m_cur_opts->audio_codec(); } bool FFmpegfs_Format::is_audio_codec_supported(AVCodecID codec_id) const { return m_cur_opts->is_audio_codec_supported(codec_id); } std::string FFmpegfs_Format::audio_codec_list() const { return m_cur_opts->audio_codec_list(); } AVSampleFormat FFmpegfs_Format::sample_format() const { return m_cur_opts->sample_format(); } bool FFmpegfs_Format::is_sample_fmt_supported() const { return m_cur_opts->is_sample_fmt_supported(); } std::string FFmpegfs_Format::sample_fmt_list() const { return m_cur_opts->sample_fmt_list(); } AVCodecID FFmpegfs_Format::subtitle_codec(AVCodecID codec_id) const { return m_cur_opts->subtitle_codec(codec_id); } const std::string & append_sep(std::string * path) { if (path->back() != '/') { *path += '/'; } return *path; } const std::string & append_filename(std::string * path, const std::string & filename) { append_sep(path); *path += filename; return *path; } const std::string & remove_sep(std::string * path) { if (path->back() == '/') { (*path).pop_back(); } return *path; } const std::string & remove_filename(std::string * filepath) { std::shared_ptr p = new_strdup(*filepath); if (p == nullptr) { errno = ENOMEM; return *filepath; } *filepath = dirname(p.get()); append_sep(filepath); return *filepath; } const std::string & remove_path(std::string *filepath) { std::shared_ptr p = new_strdup(*filepath); if (p == nullptr) { errno = ENOMEM; return *filepath; } *filepath = basename(p.get()); return *filepath; } const std::string & remove_ext(std::string *filepath) { size_t found; found = filepath->rfind('.'); if (found != std::string::npos) { // Have extension filepath->resize(found); } return *filepath; } bool find_ext(std::string * ext, const std::string & filename) { size_t found; found = filename.rfind('.'); if (found == std::string::npos) { // No extension ext->clear(); return false; } else { // Have extension *ext = filename.substr(found + 1); return true; } } bool check_ext(const std::string & ext, const std::string & filename) { std::string ext1; return (find_ext(&ext1, filename) && ext1 == ext); } const std::string & replace_ext(std::string * filepath, const std::string & ext) { size_t found; found = filepath->rfind('.'); if (found == std::string::npos) { // No extension, just add *filepath += '.'; } else { // Have extension, so replace filepath->resize(found + 1); } *filepath += ext; return *filepath; } const std::string & append_ext(std::string * filepath, const std::string & ext) { size_t found; found = filepath->rfind('.'); if (found == std::string::npos || strcasecmp(filepath->substr(found + 1), ext) != 0) { // No extension or different extension *filepath += '.' + ext; } return *filepath; } std::shared_ptr new_strdup(const std::string & str) { size_t n = str.size() + 1; std::shared_ptr p(new (std::nothrow) char[n]); if (p == nullptr) { errno = ENOMEM; return nullptr; } memcpy(p.get(), str.data(), n - 1); p.get()[n - 1] = '\0'; return p; } std::string ffmpeg_geterror(int errnum) { if (errnum < 0) { std::array error; av_strerror(errnum, error.data(), error.size() - 1); return error.data(); } else { // Prefer thread-safe strerror_r where available #if defined(__GLIBC__) || defined(_POSIX_C_SOURCE) char buf[256]; buf[0] = '\0'; # if ((_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE) if (strerror_r(errnum, buf, sizeof(buf)) == 0) { return std::string(buf); } else { return std::string("Unknown error: ") + std::to_string(errnum); } # else // GNU-specific strerror_r returns char* char *msg = strerror_r(errnum, buf, sizeof(buf)); if (msg) return std::string(msg); return std::string("Unknown error: ") + std::to_string(errnum); # endif #else // Fallback (may not be thread-safe on some platforms) return std::string(strerror(errnum)); #endif } } int64_t ffmpeg_rescale_q(int64_t ts, const AVRational & timebase_in, const AVRational &timebase_out) { if (ts == AV_NOPTS_VALUE) { return AV_NOPTS_VALUE; } if (ts == 0) { return 0; } return av_rescale_q(ts, timebase_in, timebase_out); } int64_t ffmpeg_rescale_q_rnd(int64_t ts, const AVRational & timebase_in, const AVRational &timebase_out) { if (ts == AV_NOPTS_VALUE) { return AV_NOPTS_VALUE; } if (ts == 0) { return 0; } return av_rescale_q_rnd(ts, timebase_in, timebase_out, static_cast(AV_ROUND_UP | AV_ROUND_PASS_MINMAX)); } #if !HAVE_MEDIA_TYPE_STRING const char *get_media_type_string(enum AVMediaType media_type) { switch (media_type) { case AVMEDIA_TYPE_VIDEO: return "video"; case AVMEDIA_TYPE_AUDIO: return "audio"; case AVMEDIA_TYPE_DATA: return "data"; case AVMEDIA_TYPE_SUBTITLE: return "subtitle"; case AVMEDIA_TYPE_ATTACHMENT: return "attachment"; default: return "unknown"; } } #endif /** * @brief Get FFmpeg library info. * @param[in] lib_exists - Set to true if library exists. * @param[in] version - Library version number. * @param[in] cfg - Library configuration. * @param[in] version_minor - Library version minor. * @param[in] version_major - Library version major. * @param[in] version_micro - Library version micro. * @param[in] libname - Name of the library. * @return Formatted library information. */ static std::string ffmpeg_libinfo(bool lib_exists, __attribute__((unused)) unsigned int version, __attribute__((unused)) const char *cfg, int version_minor, int version_major, int version_micro, const char * libname) { std::string info; if (lib_exists) { strsprintf(&info, "lib%-17s: %d.%d.%d\n", libname, version_minor, version_major, version_micro); } return info; } #define PRINT_LIB_INFO(libname, LIBNAME) \ ffmpeg_libinfo(true, libname##_version(), libname##_configuration(), \ LIB##LIBNAME##_VERSION_MAJOR, LIB##LIBNAME##_VERSION_MINOR, LIB##LIBNAME##_VERSION_MICRO, #libname) /**< @brief Print info about a FFmpeg library */ std::string ffmpeg_libinfo() { std::string info; info = "FFmpeg Version : " FFMPEG_VERSION "\n"; // cppcheck-suppress ConfigurationNotChecked info += PRINT_LIB_INFO(avutil, AVUTIL); info += PRINT_LIB_INFO(avcodec, AVCODEC); info += PRINT_LIB_INFO(avformat, AVFORMAT); // info += PRINT_LIB_INFO(avdevice, AVDEVICE); // info += PRINT_LIB_INFO(avfilter, AVFILTER); info += PRINT_LIB_INFO(swresample, SWRESAMPLE); info += PRINT_LIB_INFO(swscale, SWSCALE); // info += PRINT_LIB_INFO(postproc, POSTPROC); return info; } /** * @brief Check if class is a FMmpeg device * @todo Currently always returns 0. Must implement real check. * @param[in] avclass - Private class object * @return Returns 1 if object is a device, 0 if not. */ static int is_device(__attribute__((unused)) const AVClass *avclass) { //if (avclass == nullptr) // return 0; return 0; //return AV_IS_INPUT_DEVICE(avclass->category) || AV_IS_OUTPUT_DEVICE(avclass->category); } int show_caps(int device_only) { const AVInputFormat *ifmt = nullptr; const AVOutputFormat *ofmt = nullptr; const char *last_name; int is_dev; std::printf("%s\n" " D. = Demuxing supported\n" " .E = Muxing supported\n" " --\n", device_only ? "Devices:" : "File formats:"); last_name = "000"; for (;;) { int decode = 0; int encode = 0; const char *name = nullptr; const char *long_name = nullptr; const char *extensions = nullptr; void *ofmt_opaque = nullptr; ofmt_opaque = nullptr; while ((ofmt = av_muxer_iterate(&ofmt_opaque))) { is_dev = is_device(ofmt->priv_class); if (!is_dev && device_only) { continue; } if ((!name || strcmp(ofmt->name, name) < 0) && strcmp(ofmt->name, last_name) > 0) { name = ofmt->name; long_name = ofmt->long_name; encode = 1; } } void *ifmt_opaque = nullptr; ifmt_opaque = nullptr; while ((ifmt = av_demuxer_iterate(&ifmt_opaque)) != nullptr) { is_dev = is_device(ifmt->priv_class); if (!is_dev && device_only) { continue; } if ((!name || strcmp(ifmt->name, name) < 0) && strcmp(ifmt->name, last_name) > 0) { name = ifmt->name; long_name = ifmt->long_name; extensions = ifmt->extensions; encode = 0; } if (name && strcmp(ifmt->name, name) == 0) { decode = 1; } } if (name == nullptr) { break; } last_name = name; if (extensions == nullptr) { continue; } std::printf(" %s%s %-15s %-15s %s\n", decode ? "D" : " ", encode ? "E" : " ", extensions, name, (long_name != nullptr) ? long_name : " "); } return 0; } const char * get_codec_name(AVCodecID codec_id, bool long_name) { const AVCodecDescriptor * pCodecDescriptor; const char * psz = "unknown"; pCodecDescriptor = avcodec_descriptor_get(codec_id); if (pCodecDescriptor != nullptr) { if (pCodecDescriptor->long_name != nullptr && long_name) { psz = pCodecDescriptor->long_name; } else { psz = pCodecDescriptor->name; } } return psz; } int mktree(const std::string & path, mode_t mode) { std::shared_ptr buffer = new_strdup(path); if (buffer == nullptr) { return ENOMEM; } std::string dir; char *saveptr; char *p = strtok_r(buffer.get(), "/", &saveptr); int status = 0; while (p != nullptr) { int newstat; dir += "/"; dir += p; errno = 0; newstat = mkdir(dir.c_str(), mode); if (!status && newstat && errno != EEXIST) { status = -1; break; } status = newstat; p = strtok_r(nullptr, "/", &saveptr); } return status; } void tempdir(std::string & path) { const char *temp = getenv("TMPDIR"); if (temp != nullptr) { path = temp; return; } path = P_tmpdir; if (!path.empty()) { return; } path = "/tmp"; } int supports_albumart(FILETYPE filetype) { // Could also allow OGG but the format requires special handling for album arts return (filetype == FILETYPE::MP3 || filetype == FILETYPE::MP4); } FILETYPE get_filetype(const std::string & desttype) { try { return (filetype_map.at(desttype.c_str())); } catch (const std::out_of_range& /*oor*/) { //std::cerr << "Out of Range error: " << oor.what() << std::endl; return FILETYPE::UNKNOWN; } } std::string get_filetype_text(FILETYPE filetype) { FILETYPE_MAP::const_iterator it = search_by_value(filetype_map, filetype); if (it != filetype_map.cend()) { return it->first; } return "INVALID"; } FILETYPE get_filetype_from_list(const std::string & desttypelist) { std::vector desttype = split(desttypelist, ","); FILETYPE filetype = FILETYPE::UNKNOWN; // Find first matching entry for (size_t n = 0; n < desttype.size() && filetype != FILETYPE::UNKNOWN; n++) { filetype = get_filetype(desttype[n]); } return filetype; } void init_id3v1(ID3v1 *id3v1) { // Initialise ID3v1.1 tag structure std::memset(id3v1, ' ', sizeof(ID3v1)); std::memcpy(&id3v1->m_tag, "TAG", 3); id3v1->m_padding = '\0'; id3v1->m_title_no = 0; id3v1->m_genre = 0; } std::string format_number(int64_t value) { if (!value) { return "unlimited"; } if (value == AV_NOPTS_VALUE) { return "unset"; } std::string buffer; return strsprintf(&buffer, "%" PRId64, value); } std::string format_bitrate(BITRATE value) { if (value == static_cast(AV_NOPTS_VALUE)) { return "unset"; } if (value > 1000000) { std::string buffer; return strsprintf(&buffer, "%.2f Mbps", static_cast(value) / 1000000); } else if (value > 1000) { std::string buffer; return strsprintf(&buffer, "%.1f kbps", static_cast(value) / 1000); } else { std::string buffer; return strsprintf(&buffer, "%" PRId64 " bps", value); } } std::string format_samplerate(int value) { if (value == static_cast(AV_NOPTS_VALUE)) { return "unset"; } if (value < 1000) { std::string buffer; return strsprintf(&buffer, "%u Hz", value); } else { std::string buffer; return strsprintf(&buffer, "%.3f kHz", static_cast(value) / 1000); } } #define STR_VALUE(arg) #arg /**< @brief Convert macro to string */ #define X(name) STR_VALUE(name) /**< @brief Convert macro to string */ std::string format_duration(int64_t value, uint32_t fracs /*= 3*/) { if (value == AV_NOPTS_VALUE) { return "unset"; } std::string buffer; std::string duration; unsigned hours = static_cast((value / AV_TIME_BASE) / (3600)); unsigned mins = static_cast(((value / AV_TIME_BASE) % 3600) / 60); unsigned secs = static_cast((value / AV_TIME_BASE) % 60); if (hours) { duration = strsprintf(&buffer, "%02u:", hours); } duration += strsprintf(&buffer, "%02u:%02u", mins, secs); if (fracs) { unsigned decimals = static_cast(value % AV_TIME_BASE); duration += strsprintf(&buffer, ".%0*u", sizeof(X(AV_TIME_BASE)) - 2, decimals).substr(0, fracs + 1); } return duration; } std::string format_time(time_t value) { if (!value) { return "unlimited"; } if (value == static_cast(AV_NOPTS_VALUE)) { return "unset"; } std::string buffer; std::string time; int weeks; int days; int hours; int mins; int secs; weeks = static_cast(value / (60*60*24*7)); value -= weeks * (60*60*24*7); days = static_cast(value / (60*60*24)); value -= days * (60*60*24); hours = static_cast(value / (60*60)); value -= hours * (60*60); mins = static_cast(value / (60)); value -= mins * (60); secs = static_cast(value); if (weeks) { time = strsprintf(&buffer, "%iw ", weeks); } if (days) { time += strsprintf(&buffer, "%id ", days); } if (hours) { time += strsprintf(&buffer, "%ih ", hours); } if (mins) { time += strsprintf(&buffer, "%im ", mins); } if (secs) { time += strsprintf(&buffer, "%is ", secs); } return time; } std::string format_size(uint64_t value) { if (!value) { return "unlimited"; } if (value == static_cast(AV_NOPTS_VALUE)) { return "unset"; } if (value > 1024*1024*1024*1024LL) { std::string buffer; return strsprintf(&buffer, "%.3f TB", static_cast(value) / (1024*1024*1024*1024LL)); } else if (value > 1024*1024*1024) { std::string buffer; return strsprintf(&buffer, "%.2f GB", static_cast(value) / (1024*1024*1024)); } else if (value > 1024*1024) { std::string buffer; return strsprintf(&buffer, "%.1f MB", static_cast(value) / (1024*1024)); } else if (value > 1024) { std::string buffer; return strsprintf(&buffer, "%.1f KB", static_cast(value) / (1024)); } else { std::string buffer; return strsprintf(&buffer, "%" PRIu64 " bytes", value); } } std::string format_size_ex(uint64_t value) { std::string buffer; return format_size(value) + strsprintf(&buffer, " (%" PRIu64 " bytes)", value); } std::string format_result_size(size_t size_resulting, size_t size_predicted) { if (size_resulting >= size_predicted) { size_t value = size_resulting - size_predicted; return format_size(value); } else { size_t value = size_predicted - size_resulting; return "-" + format_size(value); } } std::string format_result_size_ex(size_t size_resulting, size_t size_predicted) { if (size_resulting >= size_predicted) { std::string buffer; size_t value = size_resulting - size_predicted; return format_size(value) + strsprintf(&buffer, " (%zu bytes)", value); } else { std::string buffer; size_t value = size_predicted - size_resulting; return "-" + format_size(value) + strsprintf(&buffer, " (-%zu bytes)", value); } } /** * @brief Print frames per second. * @param[in] d - Frames per second. * @param[in] postfix - Postfix text. */ static void print_fps(double d, const char *postfix) { long v = lrint(d * 100); if (!v) { std::printf("%1.4f %s\n", d, postfix); } else if (v % 100) { std::printf("%3.2f %s\n", d, postfix); } else if (v % (100 * 1000)) { std::printf("%1.0f %s\n", d, postfix); } else { std::printf("%1.0fk %s\n", d / 1000, postfix); } } int print_stream_info(const AVStream* stream) { int ret = 0; AVCodecContext *avctx = avcodec_alloc_context3(nullptr); if (avctx == nullptr) { return AVERROR(ENOMEM); } ret = avcodec_parameters_to_context(avctx, stream->codecpar); if (ret < 0) { avcodec_free_context(&avctx); return ret; } // Fields which are missing from AVCodecParameters need to be taken from the AVCodecContext // avctx->properties = output_stream->codec->properties; // avctx->codec = output_stream->codec->codec; // avctx->qmin = output_stream->codec->qmin; // avctx->qmax = output_stream->codec->qmax; // avctx->coded_width = output_stream->codec->coded_width; // avctx->coded_height = output_stream->codec->coded_height; int fps = stream->avg_frame_rate.den && stream->avg_frame_rate.num; int tbr = stream->r_frame_rate.den && stream->r_frame_rate.num; int tbn = stream->time_base.den && stream->time_base.num; int tbc = avctx->time_base.den && avctx->time_base.num; // Even the currently latest (lavf 58.10.100) refers to AVStream codec->time_base member... (See dump.c dump_stream_format) if (fps) print_fps(av_q2d(stream->avg_frame_rate), "avg fps"); if (tbr) print_fps(av_q2d(stream->r_frame_rate), "Real base framerate (tbr)"); if (tbn) print_fps(1 / av_q2d(stream->time_base), "stream timebase (tbn)"); if (tbc) print_fps(1 / av_q2d(avctx->time_base), "codec timebase (tbc)"); avcodec_free_context(&avctx); return ret; } std::string fourcc_make_string(std::string * buf, uint32_t fourcc) { std::string fourcc2str(AV_FOURCC_MAX_STRING_SIZE, '\0'); av_fourcc_make_string(&fourcc2str[0], fourcc); fourcc2str.resize(std::strlen(fourcc2str.c_str())); *buf = fourcc2str; return *buf; } void exepath(std::string * path) { std::array result; ssize_t count = readlink("/proc/self/exe", result.data(), result.size() - 1); if (count != -1) { *path = dirname(result.data()); append_sep(path); } else { path->clear(); } } std::string <rim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not_fn(std::function(isspace)))); return s; } std::string &rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not_fn(std::function(isspace))).base(), s.end()); return s; } std::string &trim(std::string &s) { return ltrim(rtrim(s)); } std::string replace_all(std::string str, const std::string& from, const std::string& to) { return replace_all(&str, from, to); } std::string replace_all(std::string *str, const std::string& from, const std::string& to) { size_t start_pos = 0; while ((start_pos = str->find(from, start_pos)) != std::string::npos) { str->replace(start_pos, from.length(), to); start_pos += to.length(); // Handles case where 'to' is a substring of 'from' } return *str; } bool replace_start(std::string *str, const std::string& from, const std::string& to) { #if __cplusplus >= 202002L // C++20 (and later) code if (str->starts_with(from) == 0) #else if (str->find(from, 0) == 0) #endif { str->replace(0, from.length(), to); return true; } return false; } int strcasecmp(const std::string & s1, const std::string & s2) { return ::strcasecmp(s1.c_str(), s2.c_str()); } int reg_compare(const std::string & value, const std::string & pattern, std::regex::flag_type flag) { int reti; try { std::regex rgx(pattern, flag); reti = (std::regex_search(value, rgx) == true) ? 0 : 1; } catch(const std::regex_error& e) { std::cerr << "regex_error caught: " << e.what() << std::endl; if(e.code() == std::regex_constants::error_brack) std::cerr << "The code was error_brack" << std::endl; reti = -1; } return reti; } const std::string & expand_path(std::string *tgt, const std::string & src) { wordexp_t exp_result; if (!wordexp(replace_all(src, " ", "\\ ").c_str(), &exp_result, 0)) { *tgt = exp_result.we_wordv[0]; wordfree(&exp_result); } else { *tgt = src; } return *tgt; } int is_mount(const std::string & path) { int ret = 0; try { std::shared_ptr orig_name; struct stat file_stat; struct stat parent_stat; char * parent_name = nullptr; orig_name = new_strdup(path); if (orig_name == nullptr) { std::fprintf(stderr, "is_mount(): Out of memory\n"); errno = ENOMEM; throw -1; } // get the parent directory of the file parent_name = dirname(orig_name.get()); // get the file's stat info if (-1 == stat(path.c_str(), &file_stat)) { std::fprintf(stderr, "is_mount(): (%i) %s\n", errno, strerror(errno)); throw -1; } //determine whether the supplied file is a directory // if it isn't, then it can't be a mountpoint. if (!(file_stat.st_mode & S_IFDIR)) { std::fprintf(stderr, "is_mount(): %s is not a directory.\n", path.c_str()); throw -1; } // get the parent's stat info if (-1 == stat(parent_name, &parent_stat)) { std::fprintf(stderr, "is_mount(): (%i) %s\n", errno, strerror(errno)); throw -1; } // if file and parent have different device ids, // then the file is a mount point // or, if they refer to the same file, // then it's probably the root directory // and therefore a mountpoint // style: Redundant condition: file_stat.st_dev==parent_stat.st_dev. // 'A || (!A && B)' is equivalent to 'A || B' [redundantCondition] //if (file_stat.st_dev != parent_stat.st_dev || // (file_stat.st_dev == parent_stat.st_dev && // file_stat.st_ino == parent_stat.st_ino)) if (file_stat.st_dev != parent_stat.st_dev || file_stat.st_ino == parent_stat.st_ino) { // IS a mountpoint ret = 1; } else { // is NOT a mountpoint ret = 0; } } catch (int _ret) { ret = _ret; } return ret; } std::vector split(const std::string& input, const std::string & regex) { // passing -1 as the submatch index parameter performs splitting std::regex re(regex); std::sregex_token_iterator first{input.cbegin(), input.cend(), re, -1}, last; return {first, last}; } std::string sanitise_filepath(std::string * filepath) { std::array resolved_name; if (realpath(filepath->c_str(), resolved_name.data()) != nullptr) { *filepath = resolved_name.data(); return *filepath; } // realpath has the strange feature to remove a trailing slash if there. // To mimick its behaviour, if realpath fails, at least remove it. std::string _filepath(*filepath); remove_sep(&_filepath); return _filepath; } std::string sanitise_filepath(const std::string & filepath) { std::string buffer(filepath); return sanitise_filepath(&buffer); } void append_basepath(std::string *origpath, const char* path) { *origpath = params.m_basepath; if (*path == '/') { ++path; } *origpath += path; sanitise_filepath(origpath); } bool is_album_art(AVCodecID codec_id, const AVRational * frame_rate) { if (codec_id == AV_CODEC_ID_PNG || codec_id == AV_CODEC_ID_BMP) { // PNG or BMP: must be an album art stream return true; } if (codec_id != AV_CODEC_ID_MJPEG) { // Anything else than MJPEG is never an album art stream return false; } if (frame_rate != nullptr && frame_rate->den) { double dbFrameRate = static_cast(frame_rate->num) / frame_rate->den; // If frame rate is < 300 fps this most likely is a video if (dbFrameRate < 300) { // This is a video return false; } } return true; } int nocasecompare(const std::string & lhs, const std::string &rhs) { return (strcasecmp(lhs, rhs)); } size_t get_disk_free(std::string & path) { struct statvfs buf; if (statvfs(path.c_str(), &buf)) { return 0; } return static_cast(buf.f_bfree * buf.f_bsize); } bool check_ignore(size_t size, size_t offset) { std::array blocksize_arr = { 0x2000, 0x8000, 0x10000 }; bool ignore = false; for (const size_t & blocksize: blocksize_arr) { size_t rest; bool match; match = !(offset % blocksize); // Must be multiple of block size if (!match) { continue; } rest = size % offset; // Calculate rest. ignore = match && (rest < blocksize); // Ignore of rest is less than block size if (ignore) { break; } } return ignore; } std::string make_filename(uint32_t file_no, const std::string & fileext) { std::string buffer; return strsprintf(&buffer, "%06u.%s", file_no, fileext.c_str()); } bool file_exists(const std::string & filename) { return (access(filename.c_str(), F_OK) != -1); } void make_upper(std::string * input) { std::for_each(std::begin(*input), std::end(*input), [](char& c) { c = static_cast(std::toupper(static_cast(c))); }); } void make_lower(std::string * input) { std::for_each(std::begin(*input), std::end(*input), [](char& c) { c = static_cast(std::tolower(static_cast(c))); }); } const char * hwdevice_get_type_name(AVHWDeviceType dev_type) { const char *type_name = av_hwdevice_get_type_name(dev_type); return (type_name != nullptr ? type_name : "unknown"); } int to_utf8(std::string & text, const std::string & encoding) { iconv_t conv = iconv_open("UTF-8", encoding.c_str()); if (conv == (iconv_t) -1) { int open_err = errno; // Some platforms (e.g. BSD/macOS) use UCS-* names instead of UTF-* const char *fallback = nullptr; if (open_err == EINVAL) { if (encoding == "UTF-32LE") fallback = "UCS-4LE"; else if (encoding == "UTF-32BE") fallback = "UCS-4BE"; else if (encoding == "UTF-16LE") fallback = "UCS-2LE"; else if (encoding == "UTF-16BE") fallback = "UCS-2BE"; } if (fallback != nullptr) { conv = iconv_open("UTF-8", fallback); if (conv == (iconv_t) -1) { // Fallback also failed return errno; } } else { // Error in iconv_open, errno in return code. return open_err; } } { // Error in iconv_open, errno in return code. return errno; } std::vector src; std::vector dst; size_t srclen = text.size(); size_t dstlen = 2 * srclen; src.resize(srclen + 1); dst.resize(dstlen + 2); char * pIn = src.data(); char * pOut = dst.data(); memcpy(pIn, text.data(), srclen); pIn[srclen] = '\0'; size_t len = iconv(conv, &pIn, &srclen, &pOut, &dstlen); if (len != (size_t) -1) { *pOut = '\0'; iconv_close(conv); text = dst.data(); return 0; // Conversion OK } else { int orgerrno = errno; iconv_close(conv); // Error in iconv, errno in return code. return orgerrno; } } int get_encoding (const char * str, std::string & encoding) { DetectObj *obj = detect_obj_init(); if (obj == nullptr) { // Memory Allocation failed return ENOMEM; // CHARDET_MEM_ALLOCATED_FAIL; } #ifndef CHARDET_BINARY_SAFE // before 1.0.5. This API is deprecated on 1.0.5 switch (detect (str, &obj)) #else // from 1.0.5 switch (detect_r (str, strlen (str), &obj)) #endif { case CHARDET_OUT_OF_MEMORY : // Out of memory on handle processing detect_obj_free (&obj); return ENOMEM; // CHARDET_OUT_OF_MEMORY; case CHARDET_NULL_OBJECT : // 1st argument of chardet() must be allocated with detect_obj_init API return EINVAL; // CHARDET_NULL_OBJECT; } //#ifndef CHARDET_BOM_CHECK // printf ("encoding: %s, confidence: %f\n", obj->encoding, obj->confidence); //#else // // from 1.0.6 support return whether exists BOM // printf ( // "encoding: %s, confidence: %f, exist BOM: %d\n", // obj->encoding, obj->confidence, obj->bom // ); //#endif encoding = obj->encoding; detect_obj_free (&obj); return 0; } int read_file(const std::string & path, std::string & result) { constexpr std::array UTF_8_BOM = { '\xEF', '\xBB', '\xBF' }; constexpr std::array UTF_16_BE_BOM = { '\xFE', '\xFF' }; constexpr std::array UTF_16_LE_BOM = { '\xFF', '\xFE' }; constexpr std::array UTF_32_BE_BOM = { '\x00', '\x00', '\xFE', '\xFF' }; constexpr std::array UTF_32_LE_BOM = { '\xFF', '\xFE', '\x00', '\x00' }; std::ifstream ifs; ENCODING encoding = ENCODING::ASCII; int res = 0; try { ifs.open(path, std::ios::binary); if (!ifs.is_open()) { // Unable to read file result.clear(); throw errno; } if (ifs.eof()) { // Empty file result.clear(); throw ENCODING::ASCII; } // Read the bottom mark std::array BOM; ifs.read(BOM.data(), BOM.size()); // If you feel tempted to reorder these checks please note // that UTF_32_LE_BOM must be done before UTF_16_LE_BOM to // avoid misdetection :) if (!memcmp(BOM.data(), UTF_32_LE_BOM.data(), UTF_32_LE_BOM.size())) { // The file contains UTF-32LE BOM encoding = ENCODING::UTF32LE_BOM; ifs.seekg(UTF_32_LE_BOM.size()); } else if (!memcmp(BOM.data(), UTF_32_BE_BOM.data(), UTF_32_BE_BOM.size())) { // The file contains UTF-32BE BOM encoding = ENCODING::UTF32BE_BOM; ifs.seekg(UTF_32_BE_BOM.size()); } else if (!memcmp(BOM.data(), UTF_16_LE_BOM.data(), UTF_16_LE_BOM.size())) { // The file contains UTF-16LE BOM encoding = ENCODING::UTF16LE_BOM; ifs.seekg(UTF_16_LE_BOM.size()); } else if (!memcmp(BOM.data(), UTF_16_BE_BOM.data(), UTF_16_BE_BOM.size())) { // The file contains UTF-16BE BOM encoding = ENCODING::UTF16BE_BOM; ifs.seekg(UTF_16_BE_BOM.size()); } else if (!memcmp(BOM.data(), UTF_8_BOM.data(), UTF_8_BOM.size())) { // The file contains UTF-8 BOM encoding = ENCODING::UTF8_BOM; ifs.seekg(UTF_8_BOM.size()); } else { // The file does not have BOM encoding = ENCODING::ASCII; ifs.seekg(0); } switch (encoding) { case ENCODING::UTF16LE_BOM: { std::stringstream ss; ss << ifs.rdbuf(); result = ss.str(); // raw UTF-16LE bytes (without BOM) res = to_utf8(result, "UTF-16LE"); if (res) { throw res; } break; } case ENCODING::UTF16BE_BOM: { std::stringstream ss; ss << ifs.rdbuf(); result = ss.str(); // raw UTF-16BE bytes (without BOM) res = to_utf8(result, "UTF-16BE"); if (res) { throw res; } break; } case ENCODING::UTF32LE_BOM: { std::stringstream ss; ss << ifs.rdbuf(); result = ss.str(); // raw UTF-32LE bytes (without BOM) res = to_utf8(result, "UTF-32LE"); if (res) { throw res; } break; } case ENCODING::UTF32BE_BOM: { std::stringstream ss; ss << ifs.rdbuf(); result = ss.str(); // raw UTF-32BE bytes (without BOM) res = to_utf8(result, "UTF-32BE"); if (res) { throw res; } break; } case ENCODING::UTF8_BOM: { // Already UTF-8, nothing to do std::stringstream ss; ss << ifs.rdbuf(); result = ss.str(); break; } default: // ENCODING::ASCII { // This is a bit tricky, we have to try to determine the actual encoding. std::stringstream ss; ss << ifs.rdbuf(); result = ss.str(); // Using libchardet to guess the encoding std::string encoding_name; res = get_encoding(result.c_str(), encoding_name); if (res) { throw res; } if (encoding_name != "UTF-8") { // If not UTF-8, do the actual conversion res = to_utf8(result, encoding_name); if (res) { throw res; } } break; } } res = static_cast(encoding); } catch (const std::system_error& e) { res = errno; } catch (int _res) { res = _res; } return res; } void stat_set_size(struct stat *st, size_t size) { #if defined __x86_64__ || !defined __USE_FILE_OFFSET64 st->st_size = static_cast<__off_t>(size); #else st->st_size = static_cast<__off64_t>(size); #endif st->st_blocks = (st->st_size + 512 - 1) / 512; } bool detect_docker() { try { std::ifstream const in_stream("/proc/self/cgroup"); std::stringstream buffer; buffer << in_stream.rdbuf(); auto const& content_as_string = buffer.str(); return std::string::npos != content_as_string.find("/docker"); } catch (std::exception const& ex) { std::fprintf(stderr, "detect_docker(): Unable check if running in docker or not, exception: %s.", ex.what()); return false; } } bool is_text_codec(AVCodecID codec_id) { //AV_CODEC_ID_DVD_SUBTITLE = 0x17000, //AV_CODEC_ID_DVB_SUBTITLE, //AV_CODEC_ID_TEXT, ///< raw UTF-8 text //AV_CODEC_ID_XSUB, //AV_CODEC_ID_SSA, //AV_CODEC_ID_MOV_TEXT, //AV_CODEC_ID_HDMV_PGS_SUBTITLE, //AV_CODEC_ID_DVB_TELETEXT, //AV_CODEC_ID_SRT, //AV_CODEC_ID_MICRODVD, //AV_CODEC_ID_EIA_608, //AV_CODEC_ID_JACOSUB, //AV_CODEC_ID_SAMI, //AV_CODEC_ID_REALTEXT, //AV_CODEC_ID_STL, //AV_CODEC_ID_SUBVIEWER1, //AV_CODEC_ID_SUBVIEWER, //AV_CODEC_ID_SUBRIP, //AV_CODEC_ID_WEBVTT, //AV_CODEC_ID_MPL2, //AV_CODEC_ID_VPLAYER, //AV_CODEC_ID_PJS, //AV_CODEC_ID_ASS, //AV_CODEC_ID_HDMV_TEXT_SUBTITLE, //AV_CODEC_ID_TTML, //AV_CODEC_ID_ARIB_CAPTION, return (codec_id != AV_CODEC_ID_DVD_SUBTITLE && codec_id != AV_CODEC_ID_DVB_SUBTITLE && codec_id != AV_CODEC_ID_HDMV_PGS_SUBTITLE); } int get_audio_props(AVFormatContext *format_ctx, int *channels, int *samplerate) { int ret; ret = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, INVALID_STREAM, INVALID_STREAM, nullptr, 0); if (ret >= 0) { #if LAVU_DEP_OLD_CHANNEL_LAYOUT *channels = format_ctx->streams[ret]->codecpar->ch_layout.nb_channels; #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT *channels = format_ctx->streams[ret]->codecpar->channels; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT *samplerate = format_ctx->streams[ret]->codecpar->sample_rate; } return ret; } const std::string & regex_escape(std::string * str) { // Escape characters that are meaningful to regexp. // Note that "\\" must be first so we do not escape our own escapes... const std::vector charlist {"\\", "+", "*", "?", "^", "$", "(", ")", "[", "]", "{", "}", "|"}; for (const std::string & ch : charlist) { replace_all(str, ch, "\\" + ch); } replace_all(str, ".", "[.]"); return *str; } bool is_selected(const std::string & ext) { if (params.m_include_extensions->empty()) { // If set is empty, allow all extensions return true; } auto is_match = [ext](const std::string & regex_string) { return (fnmatch(regex_string.c_str(), ext.c_str(), 0) == 0); }; return (find_if(begin(*params.m_include_extensions), end(*params.m_include_extensions), is_match) != end(*params.m_include_extensions)); } bool is_blocked(const std::string & filename) { std::string ext; if (!find_ext(&ext, filename)) { return false; // no extension } // These are blocked by default, they confuse players like VLC or mpv which // auto load them. As they get incorporated as subtitle tracks by FFmpegfs // they would end up as duplicates. if (!strcasecmp(ext, "srt") || !strcasecmp(ext, "vtt")) { return true; } auto is_match = [ext](const std::string & regex_string) { return (fnmatch(regex_string.c_str(), ext.c_str(), 0) == 0); }; // Check block list return (find_if(begin(*params.m_hide_extensions), end(*params.m_hide_extensions), is_match) != end(*params.m_hide_extensions)); } void save_free(void **p) { void * tmp = __atomic_exchange_n(p, nullptr, __ATOMIC_RELEASE); if (tmp != nullptr) { free(tmp); } } void mssleep(int milliseconds) { std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } void ussleep(int microseconds) { std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); } void nssleep(int nanoseconds) { std::this_thread::sleep_for(std::chrono::nanoseconds(nanoseconds)); } ffmpegfs-2.50/src/ffmpeg_swrcontext.h0000664000175000017500000000634115200152616013426 /* * This file is part of FFmpegfs. * * FFmpegfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_swrcontext.h * @brief FFmpeg SwrContext RAII wrapper. * * @ingroup ffmpegfs */ #ifndef FFMPEG_SWRCONTEXT_H #define FFMPEG_SWRCONTEXT_H struct SwrContext; /** * @brief RAII wrapper for SwrContext. * * Owns a libswresample context and releases it with swr_free(). The wrapper is * movable but not copyable. */ class FFmpeg_SwrContext { public: /** * @brief Construct an empty resampler-context wrapper. */ FFmpeg_SwrContext(); /** * @brief Construct a wrapper taking ownership of an existing context. * @param[in] ctx Context pointer to take ownership of, or nullptr. */ explicit FFmpeg_SwrContext(SwrContext *ctx); /** * @brief Release the owned resampler context, if any. */ ~FFmpeg_SwrContext(); FFmpeg_SwrContext(const FFmpeg_SwrContext&) = delete; FFmpeg_SwrContext& operator=(const FFmpeg_SwrContext&) = delete; /** * @brief Move-construct a resampler-context wrapper. * @param[in,out] ctx Source wrapper whose context ownership is transferred. */ FFmpeg_SwrContext(FFmpeg_SwrContext&& ctx) noexcept; /** * @brief Move-assign a resampler-context wrapper. * @param[in,out] ctx Source wrapper whose context ownership is transferred. * @return Reference to this wrapper. */ FFmpeg_SwrContext& operator=(FFmpeg_SwrContext&& ctx) noexcept; /** * @brief Get the owned FFmpeg resampler context pointer. * @return SwrContext pointer, or nullptr if empty. */ SwrContext *get() const; /** * @brief Get a writable pointer-to-pointer for FFmpeg allocation APIs. * * Any currently owned context is freed first so that FFmpeg can write a new * context pointer without leaking the previous one. * * @return Address of the managed context pointer. */ SwrContext **address(); /** * @brief Release ownership without freeing the resampler context. * @return Previously owned SwrContext pointer, or nullptr if empty. */ SwrContext *release(); /** * @brief Replace the owned context. * @param[in] ctx New context pointer to own, or nullptr to only reset. * @return true if a previous context was freed, false otherwise. */ bool reset(SwrContext *ctx = nullptr); /** * @brief Check whether the wrapper currently owns a resampler context. * @return true if no context is owned, false otherwise. */ bool empty() const; /** * @brief Check whether the wrapper owns a valid context. */ explicit operator bool() const; /** * @brief Convert to the underlying resampler context pointer. */ operator SwrContext *() const; /** * @brief Access members of the underlying resampler context. * @return SwrContext pointer. */ SwrContext *operator->() const; private: SwrContext *m_ctx; /**< @brief Pointer to underlying SwrContext. */ }; #endif // FFMPEG_SWRCONTEXT_H ffmpegfs-2.50/src/cache_maintenance.cc0000664000175000017500000002412515177713600013436 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cache_maintenance.cc * @brief #Cache maintenance implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "cache_maintenance.h" #include "ffmpegfs.h" #include "logging.h" #include #include #include /* shmat(), IPC_RMID */ #include /* sem_open(), sem_destroy(), sem_wait().. */ #define CLOCKID CLOCK_REALTIME /**< @brief Use real time clock here. */ #define SIGMAINT SIGRTMIN /**< @brief Map maintenance signal. */ #define SEM_OPEN_FILE "/" PACKAGE_NAME "_04806785-b5fb-4615-ba56-b30a2946e80b" /**< @brief Shared semaphore name, should be unique system wide. */ static sigset_t mask; /**< @brief Process mask for timer */ static timer_t timerid; /**< @brief Timer id */ static sem_t * sem; /**< @brief Semaphore used to synchronise between master and slave processes */ static int shmid; /**< @brief Shared memory segment ID */ static pid_t * pid_master; /**< @brief PID of master process */ static bool master; /**< @brief If true, we are master */ static void maintenance_handler(int sig, __attribute__((unused)) siginfo_t *si, __attribute__((unused)) void *uc); static bool start_timer(time_t interval); static bool stop_timer(); static bool link_up(); static void master_check(); static bool link_down(); /** * @brief Run maintenance handler * @param[in] sig - Signal, must be SIGMAINT. * @param[in] si * @param[in] uc */ static void maintenance_handler(int sig, __attribute__((unused)) siginfo_t *si, __attribute__((unused)) void *uc) { if (sig != SIGMAINT) { // Wrong signal. Should never happen. return; } master_check(); if (master) { Logging::info(nullptr, "Running periodic cache maintenance."); transcoder_cache_maintenance(); } } /** * @brief Start the maintenance timer at predefined interval. * @param[in] interval - Timer interval in seconds. * @return On success, returns true. On error, returns false. Check errno for details. */ static bool start_timer(time_t interval) { struct sigevent sev; struct itimerspec its; long long freq_nanosecs; struct sigaction sa; freq_nanosecs = interval * 1000000000LL; Logging::trace(nullptr, "Starting maintenance timer with %1period.", format_time(interval).c_str()); // Establish maintenance_handler for timer signal sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = maintenance_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGMAINT, &sa, nullptr) == -1) { Logging::error(nullptr, "start_timer(): sigaction failed: (%1) %2", errno, strerror(errno)); return false; } // Block timer signal temporarily sigemptyset(&mask); sigaddset(&mask, SIGMAINT); if (sigprocmask(SIG_SETMASK, &mask, nullptr) == -1) { Logging::error(nullptr, "start_timer(): sigprocmask(SIG_SETMASK) failed: (%1) %2", errno, strerror(errno)); return false; } // Create the timer sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGMAINT; sev.sigev_value.sival_ptr = &timerid; if (timer_create(CLOCKID, &sev, &timerid) == -1) { Logging::error(nullptr, "start_timer(): timer_create failed: (%1) %2", errno, strerror(errno)); return false; } // Start the timer its.it_value.tv_sec = static_cast(freq_nanosecs / 1000000000); its.it_value.tv_nsec = static_cast(freq_nanosecs % 1000000000); its.it_interval.tv_sec = its.it_value.tv_sec; its.it_interval.tv_nsec = its.it_value.tv_nsec; if (timer_settime(timerid, 0, &its, nullptr) == -1) { Logging::error(nullptr, "start_timer(): timer_settime failed: (%1) %2", errno, strerror(errno)); return false; } if (sigprocmask(SIG_UNBLOCK, &mask, nullptr) == -1) { Logging::error(nullptr, "start_timer(): sigprocmask(SIG_UNBLOCK) failed: (%1) %2", errno, strerror(errno)); } Logging::trace(nullptr, "The maintenance timer started successfully."); return true; } /** * @brief Stop the maintenance timer. * @return On success, returns true. On error, returns false. Check errno for details. */ static bool stop_timer() { Logging::info(nullptr, "Stopping the maintenance timer."); if (timer_delete(timerid) == -1 && errno) { Logging::error(nullptr, "stop_timer(): timer_delete failed: (%1) %2", errno, strerror(errno)); return false; } return true; } /** * @brief Set system wide inter process link up. * @return On success, returns true. On error, returns false. Check errno for details. */ static bool link_up() { key_t shmkey; Logging::debug(nullptr, "Activating " PACKAGE " inter-process link."); // initialise a shared variable in shared memory shmkey = ftok ("/dev/null", 5); // valid directory name and a number if (shmkey == -1) { Logging::error(nullptr, "link_up(): ftok error (%1) %2", errno, strerror(errno)); return false; } // First try to open existing memory. shmid = shmget (shmkey, sizeof (pid_t), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); if (shmid != -1) { // Shared memory already exists, seems we are client. master = false; } else { // Ignore error at first, try to create memory. shmid = shmget (shmkey, sizeof (pid_t), IPC_CREAT | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); if (shmid != -1) { // Shared memory freshly created, seems we are master. master = true; } else { Logging::error(nullptr, "link_up(): shmget error (%1) %2", errno, strerror(errno)); return false; } } pid_master = static_cast(shmat (shmid, nullptr, 0)); // attach pid_master to shared memory if (master) { *pid_master = getpid(); Logging::info(nullptr, "The process with PID %1 is now master.", *pid_master); } else { Logging::info(nullptr, "The process with PID %1 is now a client, and PID %2 is the master.", getpid(), *pid_master); } // Also create inter-process semaphore. sem = sem_open(const_cast(SEM_OPEN_FILE), O_CREAT, 0777, 1); if (sem == SEM_FAILED) { Logging::error(nullptr, "link_up(): sem_open error (%1) %2", errno, strerror(errno)); link_down(); return false; } return true; } /** * @brief Check if a master is already running. We become master if not. */ static void master_check() { pid_t pid_self = getpid(); if (*pid_master == pid_self) { Logging::trace(nullptr, "The process with PID %1 is already master.", pid_self); return; } sem_wait(sem); // Check if master process still exists int master_running = (getpgid(*pid_master) >= 0); Logging::trace(nullptr, "Master with PID %1 is %2 running.", *pid_master, master_running ? "still" : "NOT"); if (!master_running) { Logging::info(nullptr, "Master with PID %1 is gone. PID %2 taking over as new master.", *pid_master, pid_self); // Register us as master *pid_master = pid_self; master = true; } sem_post(sem); } /** * @brief Set system wide inter process link down. * @return On success, returns true. On error, returns false. Check errno for details. */ static bool link_down() { struct shmid_ds buf; bool success = true; Logging::info(nullptr, "Shutting " PACKAGE " inter-process link down."); if (sem != nullptr && sem_close(sem)) { Logging::error(nullptr, "link_down(): sem_close error (%1) %2", errno, strerror(errno)); success = false; } // shared memory detach if (shmdt (pid_master)) { Logging::error(nullptr, "link_down(): shmdt error (%1) %2", errno, strerror(errno)); success = false; } if (shmctl(shmid, IPC_STAT, &buf)) { Logging::error(nullptr, "link_down(): shmctl error (%1) %2", errno, strerror(errno)); success = false; } else { if (!buf.shm_nattch) { if (shmctl (shmid, IPC_RMID, nullptr)) { Logging::error(nullptr, "link_down(): shmctl error (%1) %2", errno, strerror(errno)); success = false; } // unlink prevents the semaphore existing forever // if a crash occurs during the execution if (sem_unlink(SEM_OPEN_FILE)) { Logging::error(nullptr, "link_down(): sem_unlink error (%1) %2", errno, strerror(errno)); success = false; } } } return success; } bool start_cache_maintenance(time_t interval) { // Start link if (!link_up()) { return false; } // Now start timer return start_timer(interval); } bool stop_cache_maintenance() { bool success = true; // Stop timer first if (!stop_timer()) { success = false; } // Now shut down link if (!link_down()) { success = false; } return success; } ffmpegfs-2.50/src/ffmpeg_frame.h0000664000175000017500000000776615177713600012325 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_frame.h * @brief FFmpeg AVFrame extension * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_FRAME_H #define FFMPEG_FRAME_H #pragma once struct AVFrame; /** * @brief The #FFmpeg_Frame class */ class FFmpeg_Frame { public: /** * @brief Construct FFmpeg_Frame object. * @param[in] stream_index - Index of stream */ explicit FFmpeg_Frame(int stream_index = INVALID_STREAM); /** * @brief Copy construct from FFmpeg_Frame object. * @param[in] frame - Pointer to source FFmpeg_Frame object. * @note Do not declare explicit, breaks use in std::variant */ FFmpeg_Frame(const FFmpeg_Frame & frame); /** * @brief Copy construct from AVFrame struct. * @param[in] frame - Pointer to source AVFrame struct. * @note Do not declare explicit, breaks use in std::variant */ FFmpeg_Frame(const AVFrame * frame); // cppcheck-suppress noExplicitConstructor /** * @brief Destruct FFmpeg_Frame object. */ virtual ~FFmpeg_Frame(); /** * @brief Get result of last operation * @return Returns 0 if last operation was successful, or negative AVERROR value. */ int res() const; /** * @brief Clone frame to a new AVFrame * struct. Needs to be freed by a av_frame_free() call. * @return Returns cloned AVFrame * struct, or nullptr on error. */ AVFrame* clone(); /** * @brief Unreference underlying frame */ void unref(); /** * @brief Free underlying frame */ void free(); /** * @brief Access the underlying frame * @return Returns the unerlying AVFrame * struct, or nullptr not valid. */ AVFrame* get(); /** * @brief operator AVFrame *: Do as if we were a pointer to AVFrame */ operator AVFrame*(); /** * @brief operator const AVFrame *: Do as if we were a const pointer to AVFrame */ operator const AVFrame*() const; /** * @brief operator ->: Do as if we were a pointer to AVFrame * @return Pointer to AVFrame struct. */ AVFrame* operator->(); /** * @brief Make copy from other FFmpeg_Frame object. * @param[in] frame - Reference to source FFmpeg_Frame object. * @return Reference to new FFmpeg_Frame object. */ FFmpeg_Frame& operator=(const FFmpeg_Frame & frame) noexcept; /** * @brief Make copy from AVFrame structure. * @param[in] frame - Pointer to source AVFrame structure. * @return Reference to new FFmpeg_Frame object. */ FFmpeg_Frame& operator=(const AVFrame * frame) noexcept; protected: AVFrame * m_frame; /**< @brief Pointer to underlying AVFrame struct */ int m_res; /**< @brief 0 if last operation was successful, or negative AVERROR value */ public: int m_stream_idx; /**< @brief Stream index frame belongs to, or -1 (INVALID_STREAM) */ }; #endif // FFMPEG_FRAME_H ffmpegfs-2.50/src/ffmpeg_transcoder.h0000664000175000017500000023210215215723104013350 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_transcoder.h * @brief FFmpeg transcoder * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_TRANSCODER_H #define FFMPEG_TRANSCODER_H #pragma once #include "ffmpeg_base.h" #include "ffmpeg_audiofifo.h" #include "ffmpeg_frame.h" #include "ffmpeg_formatcontext.h" #include "ffmpeg_swrcontext.h" #include "ffmpeg_swscontext.h" #include "ffmpeg_packet.h" #include "ffmpeg_subtitle.h" #include "id3v1tag.h" #include "fileio.h" #include "ffmpeg_profiles.h" #include #include #include #include #include #include #include class Buffer; class FFmpeg_Dictionary; struct AVFilterContext; struct AVFilterGraph; struct AVCodecContext; struct AVSubtitle; /** * @brief Decoder status codes. Can be error, success or end of file. */ enum class DECODER_STATUS { DEC_ERROR = -1, /**< @brief Decoder error, see return code */ DEC_SUCCESS = 0, /**< @brief Frame decoded successfully */ DEC_EOF = 1 /**< @brief Read to end of file */ }; typedef DECODER_STATUS *LPDECODER_STATUS; /**< @brief Pointer version of DECODER_STATUS */ typedef DECODER_STATUS const * LPCDECODER_STATUS; /**< @brief Pointer to const version of DECODER_STATUS */ /** * @brief The #FFmpeg_Transcoder class */ class FFmpeg_Transcoder : public FFmpeg_Base, FFmpeg_Profiles { protected: /** * @brief Buffer structure, used in FFmpeg_Transcoder::read_packet */ typedef struct BUFFER_DATA { uint8_t * ptr; ///< Pointer to buffer size_t size; ///< Size left in the buffer } BUFFER_DATA; #define MAX_PRORES_FRAMERATE 2 /**< @brief Number of selectable fram rates */ /** * @brief Predicted bitrates for Apple Prores, see https://www.apple.com/final-cut-pro/docs/Apple_ProRes_White_Paper.pdf */ typedef struct PRORES_BITRATE /**< @brief List of ProRes bit rates */ { int m_width; /**< @brief Resolution: width */ int m_height; /**< @brief Resolution: height */ struct PRORES_FRAMERATE /**< @brief List of ProRes frame rates */ { int m_framerate; /**< @brief Frame rate */ bool m_interleaved; /**< @brief Format is interleaved */ } m_framerate[MAX_PRORES_FRAMERATE]; /**< @brief Array of frame rates */ /** * Bitrates in MB/s * 0: ProRes 422 Proxy * 1: ProRes 422 LT * 2: ProRes 422 standard * 3: ProRes 422 HQ * 4: ProRes 4444 (no alpha) * 5: ProRes 4444 XQ (no alpha) */ std::array m_bitrate; /**< @brief Bitrates for this format */ } PRORES_BITRATE, *LPPRORES_BITRATE; /**< @brief Pointer version of PRORES_BITRATE */ typedef PRORES_BITRATE const * LPCPRORES_BITRATE; /**< @brief Pointer to const version of PRORES_BITRATE */ class StreamRef /**< @brief In/output stream reference data */ { public: StreamRef(); virtual ~StreamRef(); /** * @brief Set the AVCodecContext pointer. Will be shared and deleted after the last consumer freed it. * @param[in] codec_ctx - AVCodecContext pointer to store */ void set_codec_ctx(AVCodecContext *codec_ctx); /** * @brief Close (reset) AVCodecContext pointer */ void reset(); public: std::shared_ptr m_codec_ctx; /**< @brief AVCodecContext for this encoder stream */ AVStream * m_stream; /**< @brief AVStream for this encoder stream */ int m_stream_idx; /**< @brief Stream index in AVFormatContext */ int64_t m_start_time; /**< @brief Start time of the stream in stream time base units, may be 0 */ }; typedef std::map StreamRef_map; /**< @brief Map stream index to StreamRef */ struct INPUTFILE /**< @brief Input file definition */ { INPUTFILE() : m_filetype(FILETYPE::UNKNOWN), m_format_ctx(FFmpeg_FormatContext::TYPE::INPUT), m_pix_fmt(AV_PIX_FMT_NONE) {} FILETYPE m_filetype; /**< @brief File type, MP3, MP4, OPUS etc. */ FFmpeg_FormatContext m_format_ctx; /**< @brief Format context */ StreamRef m_audio; /**< @brief Audio stream information */ StreamRef m_video; /**< @brief Video stream information */ AVPixelFormat m_pix_fmt; /**< @brief Video stream pixel format */ StreamRef_map m_subtitle; /**< @brief Subtitle stream information */ std::vector m_album_art; /**< @brief Album art stream */ }; // Output file struct OUTPUTFILE : public INPUTFILE /**< @brief Output file definition */ { OUTPUTFILE() : m_audio_pts(0), m_video_pts(0), m_last_mux_dts(AV_NOPTS_VALUE) { std::memset(&m_id3v1, 0, sizeof(m_id3v1)); } int64_t m_audio_pts; /**< @brief Global timestamp for the audio frames in output audio stream time base units */ int64_t m_video_pts; /**< @brief Global timestamp for the video frames in output video stream time base units */ int64_t m_last_mux_dts; /**< @brief Last muxed DTS */ ID3v1 m_id3v1; /**< @brief mp3 only, can be referenced at any time */ }; typedef std::map DEVICETYPE_MAP; /**< @brief Map device types to pixel formats */ enum class HWACCELMODE /**< @brief Currently active hardware acceleration mode */ { NONE, /**< @brief Hardware acceleration not active */ ENABLED, /**< @brief Hardware acceleration is active */ FALLBACK /**< @brief Hardware acceleration selected, but fell back to software */ }; typedef std::variant MULTIFRAME; /**< @brief Combined audio/videoframe and subtitle */ typedef std::multimap MULTIFRAME_MAP; /**< @brief Audio frame/video frame/subtitle buffer */ typedef std::map STREAM_MAP; /**< @brief Map input subtitle stream to output stream */ public: /** * Construct FFmpeg_Transcoder object */ explicit FFmpeg_Transcoder(); /** * Destroy FFMPEG_Transcoder object * Close and free all internal structures. */ virtual ~FFmpeg_Transcoder(); /** * Check if input file is already open. * * @return true if open; false if closed */ bool is_open() const; /** * Open the given FFmpeg file and prepare for decoding. * Collect information for the file (duration, bitrate, etc.). * After this function, the other methods can be used to process the file. * * @param[in,out] virtualfile - Virtualfile object for desired file. May be a physical file, a DVD, Blu-ray or video CD * @param[in,out] fio - Pass an already open fileio object. Normally the file is opened, but if this parameter is not nullptr the already existing object is used. * * @return On success, returns 0; on error, a negative AVERROR value. */ int open_input_file(LPVIRTUALFILE virtualfile, std::shared_ptr fio = nullptr); /** * @brief Open output file. Data will actually be written to buffer and copied by FUSE when accessed. * @param[in] buffer - Cache buffer to be written. * @return On success, returns 0; on error, a negative AVERROR value. */ int open_output_file(Buffer* buffer); /** * Process a single frame of audio data. The encode_pcm_data() method * of the Encoder will be used to process the resulting audio data, with the * result going into the given Buffer. * @param[out] status - On success, returns DECODER_SUCCESS; if at EOF, returns DECODER_EOF; on error, returns DECODER_ERROR * @return On success, returns 0; on error, a negative AVERROR value. If EOF is reached, it returns 1. */ int process_single_fr(DECODER_STATUS *status); /** * Encode any remaining PCM data to the given buffer. This should be called * after all input data has already been passed to encode_pcm_data(). * @return On success, returns 0; on error, a negative AVERROR value. */ int encode_finish(); /** * @brief Close transcoder, free all ressources. */ void closeio(); /** * @brief Get last modification time of file. * @return Modification time (seconds since epoch) */ time_t mtime() const; /** * @brief Get the file duration. * @return Returns the duration of the file in AV_TIME_BASE units. */ int64_t duration() const; /** * @brief Try to predict the recoded file size. This may (better will surely) be inaccurate. * @return Predicted file size in bytes. */ size_t predicted_filesize() const; /** * @brief Get the number of video frames in file. * @return On success, returns the number of frames; on error, returns 0 (calculation failed or no video source file). */ uint32_t video_frame_count() const; /** * @brief Get the number of HLS segments of file. * @return On success, returns the number of segments; on error, returns 0 (calculation failed). */ uint32_t segment_count() const; /** * @brief Assemble an ID3v1 file tag * @return Returns an ID3v1 file tag. */ const ID3v1 * id3v1tag() const; /** * @brief Return source filename. * @return Returns filename. */ virtual const char * filename() const override; /** * @brief Return destination filename. * @return Returns filename. */ virtual const char * destname() const override; /** * @brief Return virtual filename. Same as destination filename, but with virtual (mount) path.. * @return Returns filename. */ virtual const char * virtname() const override; /** * @brief Predict audio file size. This may (better will surely) be inaccurate. * @param[out] filesize - Predicted file size in bytes, including audio stream size. * @param[in] codec_id - Target codec ID. * @param[in] bit_rate - Target bit rate. * @param[in] duration - File duration. * @param[in] channels - Number of channels in target file. * @param[in] sample_rate - Sample rate of target file. * @param[in] sample_format - Selected sample format * @return On success, returns true; on failure, returns false. */ static bool audio_size(size_t *filesize, AVCodecID codec_id, BITRATE bit_rate, int64_t duration, int channels, int sample_rate, AVSampleFormat sample_format); /** * @brief Predict video file size. This may (better will surely) be inaccurate. * @param[out] filesize - Predicted file size in bytes, including video stream size. * @param[in] codec_id - Target codec ID. * @param[in] bit_rate - Target bit rate. * @param[in] duration - File duration. * @param[in] width - Target video width. * @param[in] height- Target video height. * @param[in] interleaved - True if target video is interleaved, false if not. * @param[in] framerate - Frame rate of target video. * @return On success, returns true; on failure, returns false. */ static bool video_size(size_t *filesize, AVCodecID codec_id, BITRATE bit_rate, int64_t duration, int width, int height, bool interleaved, const AVRational & framerate); /** * @brief Predict overhead in file size. This may (better will surely) be inaccurate. * @param[out] filesize - Predicted file size in bytes, including overhead. * @param[in] filetype - File type: MP3, TS etc. * @return On success, returns true; on failure, returns false. */ static bool total_overhead(size_t *filesize, FILETYPE filetype); /** * @brief Closes the output file if open and reports lost packets. Can safely be called again after the file was already closed or if the file was never open. * @return Returns true if anything has been closed; false if not. */ bool close_output_file(); /** * @brief Closes the input file if open. Can safely be called again after the file was already closed or if the file was never open. * @return Returns true if anything has been closed; false if not. */ bool close_input_file(); /** * @brief Seek to a specific frame. Does not actually perform the seek, this is done asynchronously by the transcoder thread. * @param[in] frame_no - Frame number to seek 1...n * @return On success, returns 0; On error, negative AVERROR value and sets errno to EINVAL. */ int stack_seek_frame(uint32_t frame_no); /** * @brief Seek to a specific HLS segment. Does not actually perform the seek, this is done asynchronously by the transcoder thread. * @param[in] segment_no - Segment number to seek 1...n * @return On success, returns 0; On error, negative AVERROR value and sets errno to EINVAL. */ int stack_seek_segment(uint32_t segment_no); /** * @brief Check for an export frame format * @return Returns true for formats that export all frames as images. */ bool is_multiformat() const; /** * @brief Check for an export frame format * @return Returns true for formats that export all frames as images. */ bool is_frameset() const; /** * @brief Check for HLS format * @return Returns true for formats that create an HLS set including the m3u file. */ bool is_hls() const; /** * @brief Check if we made a seek operation * @return Returns true if a seek was done, false if not. */ bool have_seeked() const; /** * @brief Flush FFmpeg's input buffers */ void flush_buffers(); /** * @brief Flush delayed audio packets, if there are any */ int flush_delayed_audio(); /** * @brief Flush delayed video packets, if there are any */ int flush_delayed_video(); /** * @brief Flush delayed subtitle packets, if there are any */ int flush_delayed_subtitles(); /** * @brief Get PTS (presentation time stamp) of decoded audio/video so far. * @return Returns the PTS (presentation time stamp) of decoded audio/video so far in AV_TIME_BASE units. */ int64_t pts() const; /** * @brief Current seek frame if available. * @return Returns the current seek frame; 0 if none available. */ uint32_t last_seek_frame_no() const; protected: /** * @brief Copy data from audio FIFO to frame buffer. * Divides WAV data into proper chunks to be fed into the * encoder. * @return On success, returns 0; on error, a negative AVERROR value. */ int copy_audio_to_frame_buffer(int *finished); /** * @brief Find best match stream and open codec context for it. * @param[in] format_ctx - Output format context * @param[out] codec_ctx, - Newly created codec context * @param[in] stream_idx - Stream index of new stream. * @param[in] type - Type of media: audio or video. * @return On success, returns 0; on error, a negative AVERROR value. */ int open_bestmatch_decoder(AVFormatContext *format_ctx, AVCodecContext **codec_ctx, int *stream_idx, AVMediaType type); /** * @brief Open the best match video stream, if present in input file. * @return On success, returns 0; on error, a negative AVERROR value. */ int open_bestmatch_video(); /** * @brief Open the best match audio stream. * @return On success, returns 0; on error, a negative AVERROR value. */ int open_bestmatch_audio(); /** * @brief Open all subtitles streams, if present in input file * and if supported by output file. The input and output codec * type must also match: Can only transcode bitmap subtitles * into bitmap subtitles or text to text. * @todo Add text to bitmap conversion. * @return On success, returns 0; on error, a negative AVERROR value. */ int open_subtitles(); /** * @brief open_albumarts * @return On success, returns 0; on error, a negative AVERROR value. */ int open_albumarts(); /** * @brief Determine the hardware pixel format for the codec, if applicable. * @param[in] codec - Input codec used * @param[in] dev_type - Hardware device type * @param[in] use_device_ctx - If true checks for pix format if using a hardware device context, for a pix format using a hardware frames context otherwise. * @return Returns hardware pixel format, or AV_PIX_FMT_NONE if not applicable. */ #if IF_DECLARED_CONST AVPixelFormat get_hw_pix_fmt(const AVCodec *codec, AVHWDeviceType dev_type, bool use_device_ctx) const; #else // !IF_DECLARED_CONST AVPixelFormat get_hw_pix_fmt(AVCodec *codec, AVHWDeviceType dev_type, bool use_device_ctx) const; #endif // !IF_DECLARED_CONST /** * @brief Open codec context for stream_idx. * @param[in] format_ctx - Output format context * @param[out] codec_ctx - Newly created codec context * @param[in] stream_idx - Stream index of new stream. * @param[in] input_codec - Decoder codec to open, may be nullptr. Will open a matching codec automatically. * @param[in] mediatype - Type of media: audio or video. * @return On success, returns 0; on error, a negative AVERROR value. */ #if IF_DECLARED_CONST int open_decoder(AVFormatContext *format_ctx, AVCodecContext **codec_ctx, int stream_idx, const AVCodec *input_codec, AVMediaType mediatype); #else // !IF_DECLARED_CONST int open_decoder(AVFormatContext *format_ctx, AVCodecContext **codec_ctx, int stream_idx, AVCodec *input_codec, AVMediaType mediatype); #endif // !IF_DECLARED_CONST /** * @brief Open output frame set. Data will actually be written to buffer and copied by FUSE when accessed. * @param[in] buffer - Stream buffer to operate on * @return On success, returns 0; on error, a negative AVERROR value. */ int open_output_frame_set(Buffer *buffer); /** * @brief Open output file. Data will actually be written to buffer and copied by FUSE when accessed. * @param[in] buffer - Stream buffer to operate on * @return On success, returns 0; on error, a negative AVERROR value. */ int open_output(Buffer *buffer); /** * @brief Process headers of output file * Write file header, process meta data and add album arts. * @return On success, returns 0; on error, a negative AVERROR value. */ int process_output(); /** * FFmpeg handles cover arts like video streams. * Try to find out if we have a video stream or a cover art. * @return Return true if file contains a video stream. */ bool is_video() const; /** * @brief Prepare codec options. * @param[in] opt - Codec private data. * @param[in] profile_option_vec - Selected profile option. * @return On success, returns 0; on error, a negative AVERROR value. */ int update_codec(void *opt, const PROFILE_OPTION_VEC & profile_option_vec) const; /** * @brief Prepare codec options for a file type. * @param[in] opt - Codec private data. * @param[in] filetype - File type: MP3, MP4 etc. * @return On success, returns 0; on error, a negative AVERROR value. */ int prepare_codec(void *opt, FILETYPE filetype) const; #if IF_DECLARED_CONST /** * @brief Find the encoder for an output stream, including hardware encoder selection. * @param[in] codec_id Requested output codec ID. * @param[out] output_codec Receives the selected encoder. * @return 0 on success, or a negative AVERROR code on failure. */ int find_output_encoder(AVCodecID codec_id, const AVCodec **output_codec); #else // !IF_DECLARED_CONST /** * @brief Find the encoder for an output stream, including hardware encoder selection. * @param[in] codec_id Requested output codec ID. * @param[out] output_codec Receives the selected encoder. * @return 0 on success, or a negative AVERROR code on failure. */ int find_output_encoder(AVCodecID codec_id, AVCodec **output_codec); #endif // !IF_DECLARED_CONST #if IF_DECLARED_CONST /** * @brief Allocate an output stream and encoder context for a newly encoded stream. * @param[in] codec_id Requested output codec ID, used for diagnostics. * @param[in] output_codec Selected encoder. * @param[out] output_stream Receives the newly allocated output stream. * @param[out] output_codec_ctx Receives the newly allocated encoder context. * @return 0 on success, or a negative AVERROR code on failure. */ int allocate_output_stream_and_context(AVCodecID codec_id, const AVCodec *output_codec, AVStream **output_stream, AVCodecContext **output_codec_ctx); #else // !IF_DECLARED_CONST /** * @brief Allocate an output stream and encoder context for a newly encoded stream. * @param[in] codec_id Requested output codec ID, used for diagnostics. * @param[in] output_codec Selected encoder. * @param[out] output_stream Receives the newly allocated output stream. * @param[out] output_codec_ctx Receives the newly allocated encoder context. * @return 0 on success, or a negative AVERROR code on failure. */ int allocate_output_stream_and_context(AVCodecID codec_id, AVCodec *output_codec, AVStream **output_stream, AVCodecContext **output_codec_ctx); #endif // !IF_DECLARED_CONST #if IF_DECLARED_CONST /** * @brief Configure an encoded audio output stream and its encoder context. * @param[in] codec_id Requested output codec ID. * @param[in] output_codec Selected encoder. * @param[in,out] output_codec_ctx Encoder context to configure. * @param[in,out] output_stream Output stream to configure. * @param[in,out] opt Encoder option dictionary. * @return 0 on success, or a negative AVERROR code on failure. */ int configure_audio_output_stream(AVCodecID codec_id, const AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt); #else // !IF_DECLARED_CONST /** * @brief Configure an encoded audio output stream and its encoder context. * @param[in] codec_id Requested output codec ID. * @param[in] output_codec Selected encoder. * @param[in,out] output_codec_ctx Encoder context to configure. * @param[in,out] output_stream Output stream to configure. * @param[in,out] opt Encoder option dictionary. * @return 0 on success, or a negative AVERROR code on failure. */ int configure_audio_output_stream(AVCodecID codec_id, AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt); #endif // !IF_DECLARED_CONST #if IF_DECLARED_CONST /** * @brief Configure an encoded video output stream and its encoder context. * @param[in] codec_id Requested output codec ID. * @param[in] output_codec Selected encoder. * @param[in,out] output_codec_ctx Encoder context to configure. * @param[in,out] output_stream Output stream to configure. * @param[in,out] opt Encoder option dictionary. * @return 0 on success, or a negative AVERROR code on failure. */ int configure_video_output_stream(AVCodecID codec_id, const AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt); #else // !IF_DECLARED_CONST /** * @brief Configure an encoded video output stream and its encoder context. * @param[in] codec_id Requested output codec ID. * @param[in] output_codec Selected encoder. * @param[in,out] output_codec_ctx Encoder context to configure. * @param[in,out] output_stream Output stream to configure. * @param[in,out] opt Encoder option dictionary. * @return 0 on success, or a negative AVERROR code on failure. */ int configure_video_output_stream(AVCodecID codec_id, AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt); #endif // !IF_DECLARED_CONST /** * @brief Store output duration metadata on the output format context. */ void set_output_format_duration_metadata(); #if IF_DECLARED_CONST /** * @brief Open an output encoder and copy its parameters back to the output stream. * @param[in] codec_id Requested output codec ID. * @param[in] output_codec Selected encoder. * @param[in,out] output_codec_ctx Encoder context to open. * @param[in,out] output_stream Output stream receiving codec parameters. * @param[in,out] opt Encoder option dictionary. * @return 0 on success, or a negative AVERROR code on failure. */ int open_output_encoder(AVCodecID codec_id, const AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt); #else // !IF_DECLARED_CONST /** * @brief Open an output encoder and copy its parameters back to the output stream. * @param[in] codec_id Requested output codec ID. * @param[in] output_codec Selected encoder. * @param[in,out] output_codec_ctx Encoder context to open. * @param[in,out] output_stream Output stream receiving codec parameters. * @param[in,out] opt Encoder option dictionary. * @return 0 on success, or a negative AVERROR code on failure. */ int open_output_encoder(AVCodecID codec_id, AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt); #endif // !IF_DECLARED_CONST /** * @brief Add a newly encoded audio or video stream to the output file. * * Finds the selected encoder, allocates an output stream and codec context, * configures the stream according to the media type, opens the encoder, and * transfers the codec parameters back to the output stream. * * @param[in] codec_id Codec for the new output stream. * @return On success, returns the index of the new stream. On error, returns * a negative AVERROR value. */ int add_stream(AVCodecID codec_id); /** * @brief Add new subtitle stream to output file. * @param[in] codec_id - Codec for this stream. * @param[in] input_streamref - Streamref of input stream. * @param[in] language - (Optional) Language or subtitle file, or std::nullopt if unknown. * @return On success, returns index of new stream [0...n]; On error, negative AVERROR value. */ int add_subtitle_stream(AVCodecID codec_id, StreamRef & input_streamref, const std::optional &language = std::nullopt); /** * @brief Allocate an output stream for stream-copy output. * @param[in] codec_id Codec ID, used for diagnostics. * @param[out] output_stream Receives the newly allocated output stream. * @return 0 on success, or a negative AVERROR code on failure. */ int allocate_stream_copy(AVCodecID codec_id, AVStream **output_stream); /** * @brief Configure an audio output stream for stream-copy output. * @param[in,out] output_stream Output stream to configure. * @return 0 on success, or a negative AVERROR code on failure. */ int configure_audio_stream_copy(AVStream *output_stream); /** * @brief Configure a video output stream for stream-copy output. * @param[in,out] output_stream Output stream to configure. * @return 0 on success, or a negative AVERROR code on failure. */ int configure_video_stream_copy(AVStream *output_stream); /** * @brief Add new stream copy to output file. * @param[in] codec_id - Codec for this stream. * @param[in] codec_type - Codec type: audio or video. * @return On success, returns 0; on error, a negative AVERROR value. */ int add_stream_copy(AVCodecID codec_id, AVMediaType codec_type); /** * @brief Add a stream for an album art. * @param[in] input_codec_ctx - Input codec context. * @return On success, returns 0; on error, a negative AVERROR value. */ int add_albumart_stream(const AVCodecContext *input_codec_ctx); /** * @brief Add album art to stream. * @param[in] output_stream - Output stream. * @param[in] pkt_in - Packet with album art. * @return On success, returns 0; on error, a negative AVERROR value. */ int add_albumart_frame(AVStream *output_stream, AVPacket *pkt_in); /** * @brief Open an output file and the required encoder. * Also set some basic encoder parameters. * Some of these parameters are based on the input file's parameters. * @param[in] buffer - Stream buffer to operate on * @return On success, returns 0; on error, a negative AVERROR value. */ int open_output_filestreams(Buffer *buffer); /** * @brief Safely copy a tag to a target buffer. If the input buffer size * is larger than output the data will be truncated to avoid overruns. * The function never appends a /0 terminator. * @param[out] out - Target buffer * @param[in] in - Input buffer * @return Constant pointer to target buffer. */ template const char * tagcpy(char (&out) [ size ], const std::string & in) const; /** * @brief Safely copy a tag to a target buffer. If the input buffer size * is larger than output the data will be truncated to avoid overruns. * The function never appends a /0 terminator. * @param[out] out - Target buffer * @param[in] in - Input buffer * @return Constant pointer to target buffer. */ template const T & tagcpy(T & out, const std::string & in) const; /** * @brief Process the metadata in the FFmpeg file. * This should be called at the beginning, before reading audio data. * The set_text_tag() and set_picture_tag() methods of the given Encoder will * be used to set the metadata, with results going into the given Buffer. * This function will also read the actual PCM stream parameters. * @param[in] metadata_out - Dictionary of output file. Metadata will be copied into it. * @param[in] metadata_in - Dictionary of input file. Metadata will be copied out of it. * @param[in] contentstream - True if this is a content stream, i.e, audio or video. False for album arts or sub titles. */ void copy_metadata(AVDictionary **metadata_out, const AVDictionary *metadata_in, bool contentstream = true); /** * @brief Copy metadata from source to target * @return On success, returns 0; on error, a negative AVERROR value. */ int process_metadata(); /** * @brief Copy all album arts from source to target. * @return On success, returns 0; on error, a negative AVERROR value. */ int process_albumarts(); /** * @brief Initialize the audio resampler based on the input and output codec settings. * If the input and output sample formats differ, a conversion is required * libswresample takes care of this, but requires initialization. * @return On success, returns 0; on error, a negative AVERROR value. */ int init_resampler(); /** * @brief Initialise a FIFO buffer for the audio samples to be encoded. * @return On success, returns 0; on error, a negative AVERROR value. */ int init_audio_fifo(); /** * @brief Update format options * @param[in] dict - Dictionary to update. * @param[in] option_vec - Profile option to set. * @return On success, returns 0; on error, a negative AVERROR value. */ int update_format(AVDictionary** dict, const PROFILE_OPTION_VEC & option_vec) const; /** * @brief Prepare format optimisations * @param[in] dict - Dictionary to update. * @param[in] filetype - File type: MP3, MP4 etc. * @return On success, returns 0; on error, a negative AVERROR value. */ int prepare_format(AVDictionary **dict, FILETYPE filetype) const; /** * @brief Write the header of the output file container. * @return On success, returns 0; on error, a negative AVERROR value. */ int write_output_file_header(); /** * @brief Store packet in output stream. * @param[in] pkt - Packet to store. * @param[in] mediatype - Typo of packet: audio, video, image (attachment) * @return On success, returns 0; on error, a negative AVERROR value. */ int store_packet(AVPacket *pkt, AVMediaType mediatype); /** * @brief Decode one audio frame * @param[in] pkt - Packet to decode. * @param[in] decoded - 1 if packet was decoded, 0 if it did not contain data. * @return On success, returns 0; on error, a negative AVERROR value. */ int decode_audio_frame(AVPacket *pkt, int *decoded); /** * @brief Decode one video frame * @param[in] pkt - Packet to decode. * @param[in] decoded - 1 if packet was decoded, 0 if it did not contain data. * @return On success, returns 0; on error, a negative AVERROR value. */ int decode_video_frame(AVPacket *pkt, int *decoded); /** * @brief Decode one subtitle * @param[in] pkt - Packet to decode. * @param[in] decoded - 1 if packet was decoded, 0 if it did not contain data. * @return On success, returns 0; on error, a negative AVERROR value. */ int decode_subtitle(AVPacket *pkt, int *decoded); /** * @brief Decode one subtitle * @param[in] codec_ctx - AVCodecContext object of output codec context. * @param[in] pkt - Packet to decode. * @param[in] decoded - 1 if packet was decoded, 0 if it did not contain data. * @param[in] out_stream_idx - Output stream index. * @return On success, returns 0; on error, a negative AVERROR value. */ int decode_subtitle(AVCodecContext *codec_ctx, AVPacket *pkt, int *decoded, int out_stream_idx); /** * @brief Create PTS/DTS and update the packet. * If the update packet lacks time stamps, create a fictitious PTS or DTS and update it. * If the packet already has valid time stamps, nothing is changed. * @param[inout] pkt - Audio/video packet to update. * @param[inout] cur_ts - Current time stamp, will be updated to the next position. */ void make_pts(AVPacket *pkt, int64_t *cur_ts) const; /** * @brief Decode one frame. * @param[in] pkt - Packet to decode. * @return On success, returns 0; on error, a negative AVERROR value. */ int decode_frame(AVPacket *pkt); /** * @brief Initialise a temporary storage for the specified number of audio samples. * The conversion requires temporary storage due to the different format. * The number of audio samples to be allocated is specified in frame_size. * @param[out] converted_input_samples - Memory for input samples. * @param[in] frame_size - Size of one frame. * @return On success, returns 0; on error, a negative AVERROR value. */ int init_converted_samples(uint8_t ***converted_input_samples, int frame_size); /** * @brief Convert the input audio samples into the output sample format. * The conversion happens on a per-frame basis, the size of which is * specified by frame_size. * @param[in] input_data - Input data. * @param[in] in_samples - Number of input samples. * @param[out] converted_data - Converted data. * @param[out] out_samples - Number of output samples * @return On success, returns 0; on error, a negative AVERROR value. */ int convert_samples(uint8_t **input_data, int in_samples, uint8_t **converted_data, int *out_samples); /** * @brief Add converted input audio samples to the FIFO buffer for later processing. * @param[in] converted_input_samples - Samples to add. * @param[in] frame_size - Frame size * @return On success, returns 0; on error, a negative AVERROR value. */ int add_samples_to_fifo(uint8_t **converted_input_samples, int frame_size); /** * @brief Flush the remaining frames for all streams. * @return On success, returns 0; on error, a negative AVERROR value. */ int flush_frames_all(bool use_flush_packet); /** * @brief Flush the remaining frames * @param[in] stream_idx - Stream index to flush. * @param[in] use_flush_packet - If true, use flush packet. Otherwise pass nullptr to avcodec_receive_frame. * @return On success, returns 0; on error, a negative AVERROR value. */ int flush_frames_single(int stream_idx, bool use_flush_packet); /** * @brief Read frame from source file, decode and store in FIFO. * @param[in] finished - 1 if at EOF. * @return On success, returns 0; on error, a negative AVERROR value. */ int read_decode_convert_and_store(int *finished); /** * @brief Initialise one input frame for writing to the output file. * The frame will be exactly frame_size samples large. * @param[in] frame - Newly initialised frame. * @param[in] frame_size - Size of new frame. * @return On success, returns 0; on error, a negative AVERROR value. */ int init_audio_output_frame(AVFrame *frame, int frame_size) const; /** * @brief Allocate memory for one picture. * @param[in] frame - Frame to prepare * @param[in] pix_fmt - Pixel format * @param[in] width - Picture width * @param[in] height - Picture height * @return On success, returns 0; on error, a negative AVERROR value. */ int alloc_picture(AVFrame *frame, AVPixelFormat pix_fmt, int width, int height) const; /** * @brief Produce audio dts/pts. This is required because the target codec usually has a different * frame size than the source, so the number of packets will not match 1:1. * @param[in] pkt - Packet to add dts/pts to. */ void produce_audio_dts(AVPacket * pkt); /** * This does not quite work like avcodec_decode_audio4/avcodec_decode_video2. * There is the following difference: if you got a frame, you must call * it again with pkt=nullptr. pkt==nullptr is treated differently from pkt->size==0 * (pkt==nullptr means get more output, pkt->size==0 is a flush/drain packet) * @param[in] codec_ctx - AVCodecContext of input stream. * @param[in] frame - Decoded frame * @param[out] got_frame - 1 if a frame was decoded, 0 if not * @param[in] pkt - Packet to decode * @return On success, returns 0. On error, returns a negative AVERROR value. */ int decode(AVCodecContext *codec_ctx, AVFrame *frame, int *got_frame, const AVPacket *pkt) const; /** * @brief Load one audio frame from the FIFO buffer and store in frame buffer. * @param[in] frame_size - Size of frame. * @return On success, returns 0. On error, returns a negative AVERROR value. */ int create_audio_frame(int frame_size); /** * @brief Create one frame worth of audio to the output file. * @param[in] frame - Audio frame to encode * @param[in] data_present - 1 if frame contained data that could be encoded, 0 if not. * @return On success, returns 0. On error, returns a negative AVERROR value. */ int encode_audio_frame(const AVFrame *frame, int *data_present); /** * @brief Encode one frame worth of video to the output file. * @param[in] frame - Video frame to encode * @param[in] data_present - 1 if frame contained data that could be encoded, 0 if not. * @return On success, returns 0. On error, returns a negative AVERROR value. */ int encode_video_frame(const AVFrame *frame, int *data_present); /** * @brief Encode one subtitle frame to the output file. * @param[in] sub - Subtitle frame to encode * @param[in] out_stream_idx - Index of stream to encode to. * @param[in] data_present - 1 if frame contained data that could be encoded, 0 if not. * @return On success, returns 0. On error, returns a negative AVERROR value. */ int encode_subtitle(const AVSubtitle *sub, int out_stream_idx, int *data_present); /** * @brief Encode frame to image * @param[in] frame - Video frame to encode * @param[out] data_present - Set to 1 if data was encoded. 0 if not. * @return On success, returns 0. On error, returns a negative AVERROR value. */ int encode_image_frame(const AVFrame *frame, int *data_present); /** * @brief Write the trailer of the output file container. * @return On success, returns 0. On error, returns a negative AVERROR value. */ int write_output_file_trailer(); /** * @brief Custom read function for FFmpeg * * Read from virtual files, may be a physical file but also a DVD, VCD or Blu-ray chapter. * * @param[in] opaque - Payload given to FFmpeg, basically the FileIO object * @param[in] data - Returned data read from file. * @param[in] size - Size of data buffer. * @return On success, returns bytes read. May be less than size or even 0. On error, returns a negative AVERROR value. */ static int input_read(void * opaque, unsigned char * data, int size); /** * @brief Custom write function for FFmpeg * @param[in] opaque - Payload given to FFmpeg, basically the FileIO object * @param[in] data - Data to be written * @param[in] size - Size of data block. * @return On success, returns bytes written. On error, returns a negative AVERROR value. */ #if LAVF_WRITEPACKET_CONST static int output_write(void * opaque, const uint8_t * data, int size); #else static int output_write(void * opaque, unsigned char * data, int size); #endif /** * @brief Custom seek function for FFmpeg * * Write to virtual files, currently only physical files. * * @param[in] opaque - Payload given to FFmpeg, basically the FileIO object * @param[in] offset - Offset to seek to. * @param[in] whence - One of the regular seek() constants like SEEK_SET/SEEK_END. Additionally FFmpeg constants like AVSEEK_SIZE are supported. * @return On successs returns 0. On error, returns -1 and sets errno accordingly. */ static int64_t seek(void * opaque, int64_t offset, int whence); /** * @brief Calculate the appropriate bitrate for a ProRes file given several parameters. * @param[in] width - Video width in pixels. * @param[in] height - Video height in pixels. * @param[in] framerate - Video frame rate. * @param[in] interleaved - If true, video is interleaved; false if not. * @param[in] profile - Selected ProRes profile. * @return Bitrate in bit/s. */ static BITRATE get_prores_bitrate(int width, int height, const AVRational &framerate, bool interleaved, PRORESLEVEL profile); /** * @brief Try to predict final file size. */ size_t calculate_predicted_filesize() const; /** * @brief Get the size of the output video based on user selection and apsect ratio. * @param[in] output_width - Output video width. * @param[in] output_height - Output video height. * @return Returns true if video height/width was reduces; false if not. */ bool get_video_size(int *output_width, int *output_height) const; /** * @brief Calculate output sample rate based on user option. * @param[in] input_sample_rate - Sample rate from input file. * @param[in] max_sample_rate - Max. sample rate if set by user * @param[in] output_sample_rate - Selected output sample rate. * @return Returns true if sample rate was changed; false if not. */ static bool get_output_sample_rate(int input_sample_rate, int max_sample_rate, int * output_sample_rate = nullptr); /** * @brief Calculate output bit rate based on user option. * @param[in] input_bit_rate - Bit rate from input file. * @param[in] max_bit_rate - Max. bit rate if set by user. * @param[in] output_bit_rate - Selected output bit rate. * @return Returns true if bit rate was changed; false if not. */ static bool get_output_bit_rate(BITRATE input_bit_rate, BITRATE max_bit_rate, BITRATE * output_bit_rate = nullptr); /** * @brief Calculate aspect ratio for width/height and sample aspect ratio (sar). * @param[in] width - Video width in pixels. * @param[in] height - Video height in pixels. * @param[in] sar - Aspect ratio of input video. * @param[in] ar - Calulcated aspect ratio, if computeable. * @return On success, returns true; if false is returned ar may not be used. */ bool get_aspect_ratio(int width, int height, const AVRational & sar, AVRational * ar) const; /** * @brief Initialise video filters * @param[in] codec_ctx - AVCodecContext object of output video. * @param[in] pix_fmt - Output stream pixel format. * @param[in] framerate - Output stream frame rate. * @param[in] time_base - Output stream time base. * @return Returns 0 if OK, or negative AVERROR value. */ int init_deinterlace_filters(AVCodecContext *codec_ctx, AVPixelFormat pix_fmt, const AVRational &framerate, const AVRational &time_base); /** * @brief Send video frame to the filters. * @param[inout] srcframe - On input video frame to process, on output video frame that was filtered. * @param[in] ret - 0 if OK, or negative AVERROR value. * @return Returns 0 if OK, or negative AVERROR value. */ int send_filters(FFmpeg_Frame * srcframe, int &ret); /** * @brief Free filter sinks. */ void free_filters(); /** * @brief Check if stream can be copied from input to output (AUTOCOPY option). * @param[in] stream - Input stream to check. * @return Returns true if stream can be copied; false if not. */ bool can_copy_stream(const AVStream *stream) const; /** * @brief Close and free the resampler context. * @return If an open context was closed, returns true; if nothing had been done returns false. */ bool close_resample(); /** * @brief Init image size rescaler and pixel format converter. * @param[in] in_pix_fmt - Input pixel format * @param[in] in_width - Input image width * @param[in] in_height - Input image height * @param[in] out_pix_fmt - Output pixel format * @param[in] out_width - Output image width * @param[in] out_height - Output pixel format * @return Returns 0 if OK, or negative AVERROR value. */ int init_rescaler(AVPixelFormat in_pix_fmt, int in_width, int in_height, AVPixelFormat out_pix_fmt, int out_width, int out_height); /** * @brief Purge all samples in audio FIFO * @return Number of samples that have been purged. Function never fails. */ int purge_audio_fifo(); /** * @brief Purge all frames in buffer * @return Number of frames that have been purged. Function never fails. */ size_t purge_multiframe_map(); /** * @brief Purge all packets in HLS FIFO buffer * @return Number of Packets that have been purged. Function never fails. */ size_t purge_hls_fifo(); /** * @brief Purge FIFO and map buffers and report lost packets/frames/samples. */ void purge(); /** * @brief Actually perform seek for frame. * This function ensures that it is positioned at a key frame, so the resulting position may be different from the requested. * If e.g. frame no. 24 is a key frame, and frame_no is set to 28, the actual position will be at frame 24. * @param[in] frame_no - Frame number 1...n to seek to. * @return Returns 0 if OK, or negative AVERROR value. */ int do_seek_frame(uint32_t frame_no); /** * @brief Skip decoded frames or force seek to frame_no. * @param[in] frame_no - Frame to seek to. * @param[in] forced_seek - Force seek even if np frames skipped. * @return Returns 0 if OK, or negative AVERROR value. */ int skip_decoded_frames(uint32_t frame_no, bool forced_seek); /** * @brief Get correct input and output pixel format * @param[in] output_codec_ctx - Output codec context. * @param[out] in_pix_fmt - Input pixel format. * @param[out] out_pix_fmt - Output pixel format. */ void get_pix_formats(AVPixelFormat *in_pix_fmt, AVPixelFormat *out_pix_fmt, AVCodecContext* output_codec_ctx = nullptr) const; // Hardware de/encoding /** * Callback to negotiate the pixelFormat * @param[in] input_codec_ctx - Input codec context * @param[in] pix_fmts is the list of formats which are supported by the codec, * it is terminated by -1 as 0 is a valid format, the formats are ordered by quality. * The first is always the native one. * @note The callback may be called again immediately if initialization for * the selected (hardware-accelerated) pixel format failed. * @warning Behavior is undefined if the callback returns a value not * in the fmt list of formats. * @return the chosen format * - encoding: unused * - decoding: Set by user, if not set the native format will be chosen. */ static enum AVPixelFormat get_format_static(AVCodecContext *input_codec_ctx, const enum AVPixelFormat *pix_fmts); /** * Callback to negotiate the pixelFormat * @param[in] input_codec_ctx - Input codec context * @param[in] pix_fmts is the list of formats which are supported by the codec, * it is terminated by -1 as 0 is a valid format, the formats are ordered by quality. * The first is always the native one. * @note The callback may be called again immediately if initialization for * the selected (hardware-accelerated) pixel format failed. * @warning Behavior is undefined if the callback returns a value not * in the fmt list of formats. * @return the chosen format * - encoding: unused * - decoding: Set by user, if not set the native format will be chosen. */ enum AVPixelFormat get_format(AVCodecContext *input_codec_ctx, const enum AVPixelFormat *pix_fmts) const; /** * Open a device of the specified type and create an AVHWDeviceContext for it. * * This is a convenience function intended to cover the simple cases. Callers * who need to fine-tune device creation/management should open the device * manually and then wrap it in an AVHWDeviceContext using * av_hwdevice_ctx_alloc()/av_hwdevice_ctx_init(). * * The returned context is already initialized and ready for use, the caller * should not call av_hwdevice_ctx_init() on it. The user_opaque/free fields of * the created AVHWDeviceContext are set by this function and should not be * touched by the caller. * * @param[out] hwaccel_enc_device_ctx - On success, a * reference to the newly-created device context will be * written here. * @param[in] dev_type - The type of the device to create. * @param[in] device - A type-specific string identifying the device to open. * * @return 0 on success, a negative AVERROR code on failure. */ int hwdevice_ctx_create(AVBufferRef **hwaccel_enc_device_ctx, AVHWDeviceType dev_type, const std::string & device) const; /** * @brief Add reference to hardware device context. * @param[in] input_codec_ctx - Input codec context * @return 0 on success, a negative AVERROR code on failure. */ int hwdevice_ctx_add_ref(AVCodecContext *input_codec_ctx); /** * @brief Free (remove reference) to hardware device context * @param[inout] hwaccel_device_ctx - Hardware device context to free */ void hwdevice_ctx_free(AVBufferRef **hwaccel_device_ctx); /** * @brief Adds a reference to an existing decoder hardware frame context or * allocates a new AVHWFramesContext tied to the given hardware device context * if if the decoder runs in software. * @param[in] output_codec_ctx - Encoder codexc context * @param[in] input_codec_ctx - Decoder codexc context * @param[in] hw_device_ctx - Existing hardware device context * @return 0 on success, a negative AVERROR code on failure. */ int hwframe_ctx_set(AVCodecContext *output_codec_ctx, AVCodecContext *input_codec_ctx, AVBufferRef *hw_device_ctx) const; /** * Copy data hardware surface to software. * @param[in] output_codec_ctx - Codec context * @param[inout] sw_frame - AVFrame to copy data to * @param[in] hw_frame - AVFrame to copy data from * @return 0 on success, a negative AVERROR code on failure. */ int hwframe_copy_from_hw(AVCodecContext *output_codec_ctx, FFmpeg_Frame *sw_frame, const AVFrame *hw_frame) const; /** * Copy data software to a hardware surface. * @param[in] output_codec_ctx - Codec context * @param[inout] hw_frame - AVFrame to copy data to * @param[in] sw_frame - AVFrame to copy data from * @return 0 on success, a negative AVERROR code on failure. */ int hwframe_copy_to_hw(AVCodecContext *output_codec_ctx, FFmpeg_Frame *hw_frame, const AVFrame *sw_frame) const; /** * @brief Get the hardware codec name as string. This is required, because e.g. * the name for the software codec is libx264, but for hardware it is h264_vaapi * under VAAPI. * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Returns the name of the codec, may be nullptr if not requitred. * @return 0 on success, a negative AVERROR code on failure. */ int get_hw_decoder_name(AVCodecID codec_id, std::string *codec_name = nullptr) const; /** * @brief Get the hardware codec name as string. This is required, because e.g. * the name for the software codec is libx264, but for hardware it is h264_vaapi * under VAAPI. * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Returns the name of the codec, may be nullptr if not requitred. * @return 0 on success, AVERROR_DECODER_NOT_FOUND if no codec available. */ int get_hw_encoder_name(AVCodecID codec_id, std::string *codec_name = nullptr) const; /** * @brief Determine VAAPI codec name * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Name of the codec. * @return 0 on success, AVERROR_DECODER_NOT_FOUND if no codec available. */ int get_hw_vaapi_codec_name(AVCodecID codec_id, std::string *codec_name) const; /** * @brief Determine MMAL decoder codec name * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Name of the codec. * @return 0 on success, AVERROR_DECODER_NOT_FOUND if no codec available. */ int get_hw_mmal_decoder_name(AVCodecID codec_id, std::string *codec_name) const; /* * @brief Determine video for linux decoder codec name * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Name of the codec. * @return 0 on success, AVERROR_DECODER_NOT_FOUND if no codec available. */ //int get_hw_v4l2m2m_decoder_name(AVCodecID codec_id, std::string *codec_name) const; /** * @brief Determine OMX encoder codec name * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Name of the codec. * @return 0 on success, AVERROR_DECODER_NOT_FOUND if no codec available. */ int get_hw_omx_encoder_name(AVCodecID codec_id, std::string *codec_name) const; /** * @brief Determine video for linux encoder codec name * @param[in] codec_id - Id of encoder/decoder codec * @param[out] codec_name - Name of the codec. * @return 0 on success, AVERROR_DECODER_NOT_FOUND if no codec available. */ int get_hw_v4l2m2m_encoder_name(AVCodecID codec_id, std::string *codec_name) const; /** * @brief Get the software pixel format for the given hardware acceleration. * @param[in] type - Selected hardware acceleration. * @return 0 on success, a negative AVERROR code on failure. */ static AVPixelFormat find_sw_fmt_by_hw_type(AVHWDeviceType type); /** * @brief Calculate next HLS segment from position * @param[in] pos - Current transcoder position in AV_TIMEBASE fractional seconds. * @return Number of next segment */ uint32_t get_next_segment(int64_t pos) const; /** * @brief Check if segment number is next designated segment. * @param[in] next_segment - Number next current segment * @return Returns true if next segment should start, false if not. */ bool goto_next_segment(uint32_t next_segment) const; /** * @brief Create a fake WAV header * Create a fake WAV header. Inserts predicted file sizes to allow playback * to start directly. * @return 0 on success, a negative AVERROR code on failure. */ int create_fake_wav_header() const; /** * @brief Create a fake AIFF header * Create a fake AIFF header. Inserts predicted file sizes to allow playback * to start directly. * @return 0 on success, a negative AVERROR code on failure. */ int create_fake_aiff_header() const; /** * @brief Read AIFF chunk * @param[in] buffer - Cache buffer to read from * @param[inout] buffoffset - Byte offset into buffer. Upon return holds offset to the position of the chunk. * @param[in] ID - Chunk ID (fourCC) * @param[out] chunk - Buffer for chunk * @param[inout] size - Size of chunk. Buffer for chunk must be large enough to hold it. Upon return holds the actual size of the chunk read. * @return Returns 0 if successful or -1 On error, or end of file. Check buffer->eof(). */ int read_aiff_chunk(Buffer *buffer, size_t *buffoffset, const char *ID, uint8_t *chunk, size_t *size) const; /** * @brief Check for audio stream * @param[in] stream_idx - ID of stream to check * @return Returns true if stream is an audio stream, false if not. */ bool is_audio_stream(int stream_idx) const; /** * @brief Check for video stream * @param[in] stream_idx - ID of stream to check * @return Returns true if stream is a video stream, false if not. */ bool is_video_stream(int stream_idx) const; /** * @brief Check for subtitle stream * @param[in] stream_idx - ID of stream to check * @return Returns true if stream is a subtitle stream, false if not. */ bool is_subtitle_stream(int stream_idx) const; /** * @brief Get subtitle stream for the stream index * @param[in] stream_idx - Stream index to get subtitle stream for * @return Pointer to subbtitle stream or nullptr if not found */ StreamRef * get_out_subtitle_stream(int stream_idx); /** * @brief Check if stream exists * @param[in] stream_idx - ID of stream to check * @return Returns 0 if stream exists, false if not. */ bool stream_exists(int stream_idx) const; /** * @brief Add entry to input stream to output stream map. * @param[in] in_stream_idx - Index of input stream * @param[in] out_stream_idx - Index of output stream */ void add_stream_map(int in_stream_idx, int out_stream_idx); /** * @brief Map input stream index to output stream index * @param[in] in_stream_idx - Index of input stream * @return Returns output stream index or INVALID_STREAM if no match */ int map_in_to_out_stream(int in_stream_idx) const; /** * @brief Add all subtitle streams. Already existing streams are not * added again. * @return 0 on success, a negative AVERROR code on failure. */ int add_subtitle_streams(); /** * @brief Frame sets only: perform seek to a certain frame. * @return 0 on success, a negative AVERROR code on failure. */ int seek_frame(); /** * @brief HLS only: discard seek requests too close to the beginning. * @param[in] segment_no Requested HLS segment number. * @return true if the request was discarded, false otherwise. */ bool discard_hls_seek_near_beginning(uint32_t segment_no) const; /** * @brief HLS only: seek the input file to the requested segment. * @param[in] segment_no Requested HLS segment number. * @param[in] require_output_streams Also require matching output streams, * preserving the mid-stream seek checks. * @return 0 on success, a negative AVERROR code on failure. */ int seek_hls_segment(uint32_t segment_no, bool require_output_streams); /** * @brief HLS only: start a new HLS segment. * @return 0 on success, a negative AVERROR code on failure. */ int start_new_segment(); /** * @brief FFmpeg_Transcoder::read_packet * @param[in] opaque * @param[in] buf * @param[in] buf_size * @return */ static int read_packet(void *opaque, uint8_t *buf, int buf_size); /** * @brief Scan for external subtitle files * @return 0 on success, a negative AVERROR code on failure. */ int add_external_subtitle_streams(); /** * @brief add_external_subtitle_stream * @param[in] subtitle_file - Name of subtitle fule * @param[in] language - Language or subtitle file, or std::nullopt if unknown. * @return 0 on success, a negative AVERROR code on failure. */ int add_external_subtitle_stream(const std::string & subtitle_file, const std::optional & language); /** * @brief foreach_subititle_file * @param[in] search_path - Directory with subtitle files * @param[in] regex - Regular expression to select subtitle files * @param[in] depth - Recursively scan for subtitles, should be 0. * @param[in] f - Funtion to be called for each file found * @return 0 on success, a negative AVERROR code on failure. */ int foreach_subtitle_file(const std::string& search_path, const std::regex& regex, int depth, const std::function &)> & f); /** * @brief Prepare the active HLS output segment before opening the output format. * * This handles the special first HLS output open, including any pending * initial seek request, and selects the physical cache file for the active * HLS segment via Buffer::set_segment(). * * For non-HLS output, or if video output has already produced timestamps, * this function does nothing. * * @param buffer Output cache buffer used for selecting the active HLS segment. * @return 0 on success, or a negative AVERROR code on failure. */ int initialise_hls_output_segment(Buffer *buffer); /** * @brief Open the output file streams, falling back from hardware to software encoding if required. * * This wraps open_output_filestreams() and preserves the existing hardware * encoder fallback behaviour. If hardware output initialisation fails while * hardware encoding is enabled, the hardware encoder state is reset and the * output is opened again using software encoding. * * @param buffer Output cache buffer, or nullptr to continue using the existing buffer. * @return 0 on success, or a negative AVERROR code on failure. */ int open_output_with_hwaccel_fallback(Buffer *buffer); /** * @brief Log output audio information and initialise the audio FIFO if required. * * If an output audio stream exists and audio is being encoded rather than * copied, this initialises the audio FIFO used to buffer samples before * encoding. * * @return 0 on success, or a negative AVERROR code on failure. */ int initialise_output_audio(); /** * @brief Log output video and subtitle stream information. * * Logs video output information if a video output stream exists and logs * all subtitle output streams. */ void log_output_video_and_subtitle_info(); /** * @brief Open the regular output cache file for non-HLS output. * * HLS uses one physical cache file per segment and must not reopen segment * 0 here. For all non-HLS formats this opens the regular cache file for * read/write access and pre-allocates the predicted output size. * * @param buffer Output cache buffer. * @return 0 on success, or a negative AVERROR code on failure. */ int open_regular_output_cache_file(Buffer *buffer); /** * @brief Initialise the output audio stream start time after writing the output header. * * process_output() writes the output header and FFmpeg may adjust stream * time bases during that step. This function therefore recalculates the * audio output start time afterwards using the final stream time base. */ void initialise_audio_output_start_time(); /** * @brief Initialise the output video stream start time after writing the output header. * * process_output() writes the output header and FFmpeg may adjust stream * time bases during that step. This function therefore recalculates the * video output start time afterwards using the final stream time base. */ void initialise_video_output_start_time(); /** * @brief Initialise subtitle output stream start times after writing the output header. * * Recalculates subtitle output start times using the final output stream * time bases after process_output() has written the output header. */ void initialise_subtitle_output_start_times(); /** * @brief Initialise cached output timestamps after writing the output header. * * Recalculates audio, video, and subtitle output start times using the final * stream time bases, then initialises the current output PTS and mux DTS * tracking state. */ void initialise_output_start_times(); private: std::shared_ptr m_fileio; /**< @brief FileIO object of input file */ time_t m_mtime; /**< @brief Modified time of input file */ std::recursive_mutex m_seek_to_fifo_mutex; /**< @brief Access mutex for seek FIFO */ std::queue m_seek_to_fifo; /**< @brief Stack of seek requests. Will be processed FIFO */ std::atomic_uint32_t m_last_seek_frame_no; /**< @brief If not 0, this is the last frame that we seeked to. Video sources only. */ bool m_have_seeked; /**< @brief After seek operations this is set to make sure the trancoding result is marked RESULTCODE_INCOMPLETE to start transcoding over next access to fill the gaps. */ bool m_skip_next_frame; /**< @brief After seek, skip next video frame */ bool m_is_video; /**< @brief true if input is a video file */ MULTIFRAME_MAP m_frame_map; /**< @brief Audio/video/subtitle frame map */ // Audio conversion and buffering AVSampleFormat m_cur_sample_fmt; /**< @brief Currently selected audio sample format */ int m_cur_sample_rate; /**< @brief Currently selected audio sample rate */ #if LAVU_DEP_OLD_CHANNEL_LAYOUT AVChannelLayout m_cur_ch_layout; /**< @brief Currently selected audio channel layout */ #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT uint64_t m_cur_channel_layout; /**< @brief Currently selected audio channel layout */ #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT FFmpeg_SwrContext m_audio_resample_ctx; /**< @brief SwResample context for audio resampling */ FFmpeg_AudioFifo m_audio_fifo; /**< @brief Audio sample FIFO */ // Video conversion and buffering FFmpeg_SwsContext m_sws_ctx; /**< @brief Context for video filtering */ AVFilterContext * m_buffer_sink_context; /**< @brief Video filter sink context */ AVFilterContext * m_buffer_source_context; /**< @brief Video filter source context */ AVFilterGraph * m_filter_graph; /**< @brief Video filter graph */ int64_t m_pts; /**< @brief Generated PTS */ int64_t m_pos; /**< @brief Generated position */ // Common things for audio/video/subtitles INPUTFILE m_in; /**< @brief Input file information */ OUTPUTFILE m_out; /**< @brief Output file information */ STREAM_MAP m_stream_map; /**< @brief Input stream to output stream map */ uint32_t m_current_segment; /**< @brief HLS only: Segment file number currently being encoded */ bool m_insert_keyframe; /**< @brief HLS only: Allow insertion of 1 keyframe */ // If the audio and/or video stream is copied, packets will be stuffed into the packet queue. bool m_copy_audio; /**< @brief If true, copy audio stream from source to target (just remux, no recode). */ bool m_copy_video; /**< @brief If true, copy video stream from source to target (just remux, no recode). */ // Time stamps int64_t m_cur_audio_ts; /**< @brief If the audio stream is copied and the time stamps are absent from the input stream, we have to generate them. */ int64_t m_cur_video_ts; /**< @brief If the video stream is copied and the time stamps are absent from the input stream, we have to generate them. */ const FFmpegfs_Format * m_current_format; /**< @brief Currently used output format(s) */ Buffer * m_buffer; /**< @brief Pointer to cache buffer object */ uint32_t m_reset_pts; /**< @brief We have to reset audio/video pts to the new position */ uint32_t m_fake_frame_no; /**< @brief The MJEPG codec requires monotonically growing PTS values so we fake some to avoid them going backwards after seeks */ static const std::vector m_prores_bitrate; /**< @brief ProRes bitrate table. Used for file size prediction. */ // Hardware acceleration static const DEVICETYPE_MAP m_devicetype_map; /**< @brief List of AVPixelFormats mapped to hardware acceleration types */ HWACCELMODE m_hwaccel_enc_mode; /**< @brief Current hardware acceleration mode for encoder */ HWACCELMODE m_hwaccel_dec_mode; /**< @brief Current hardware acceleration mode for decoder */ bool m_hwaccel_enable_enc_buffering; /**< @brief Enable hardware acceleration frame buffers for encoder */ bool m_hwaccel_enable_dec_buffering; /**< @brief Enable hardware acceleration frame buffers for decoder */ AVBufferRef * m_hwaccel_enc_device_ctx; /**< @brief Hardware acceleration device context for encoder */ AVBufferRef * m_hwaccel_dec_device_ctx; /**< @brief Hardware acceleration device context for decoder */ AVPixelFormat m_enc_hw_pix_fmt; /**< @brief Requested encoder hardware pixel format */ AVPixelFormat m_dec_hw_pix_fmt; /**< @brief Requested decoder hardware pixel format */ #define FFMPEGFS_AUDIO static_cast(0x0001) /**< @brief Denote an audio stream */ #define FFMPEGFS_VIDEO static_cast(0x0002) /**< @brief Denote a video stream */ #define FFMPEGFS_SUBTITLE static_cast(0x0004) /**< @brief Denote a subtitle stream */ uint32_t m_active_stream_msk; /**< @brief HLS: Currently active streams bit mask. Set FFMPEGFS_AUDIO and/or FFMPEGFS_VIDEO */ uint32_t m_inhibit_stream_msk; /**< @brief HLS: Currently inhibited streams bit mask. Packets temporarly go to m_hls_packet_fifo and will be prepended to next segment. Set FFMPEGFS_AUDIO and/or FFMPEGFS_VIDEO */ std::queue m_hls_packet_fifo; /**< @brief HLS packet FIFO */ }; #endif // FFMPEG_TRANSCODER_H ffmpegfs-2.50/src/vcdio.cc0000664000175000017500000001116215177713600011132 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifdef USE_LIBVCD /** * @file vcdio.cc * @brief VcdIO class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "vcdio.h" #include "vcd/vcdutils.h" #include "logging.h" VcdIO::VcdIO() : m_fpi(nullptr) , m_full_title(false) , m_track_no(0) , m_chapter_no(0) , m_start_pos(0) , m_end_pos(0) { } VcdIO::~VcdIO() { pvt_close(); } VIRTUALTYPE VcdIO::type() const { return VIRTUALTYPE::VCD; } size_t VcdIO::bufsize() const { return (32 * 1024); } int VcdIO::openio(LPVIRTUALFILE virtualfile) { std::string src_filename; set_virtualfile(virtualfile); if (virtualfile != nullptr) { m_full_title = virtualfile->m_full_title; m_track_no = virtualfile->m_vcd.m_track_no; m_chapter_no = virtualfile->m_vcd.m_chapter_no; m_start_pos = virtualfile->m_vcd.m_start_pos; m_end_pos = virtualfile->m_vcd.m_end_pos; } else { m_full_title = false; m_track_no = 1; m_chapter_no = 0; m_start_pos = 0; m_end_pos = size(); } VCDUTILS::locate_video(path(), m_track_no, src_filename); Logging::info(src_filename.c_str(), "Opening the input VCD."); m_fpi = fopen(src_filename.c_str(), "rb"); if (m_fpi == nullptr) { return errno; } return seek(0, SEEK_SET); } size_t VcdIO::readio(void * data, size_t size) { if (static_cast(ftell(m_fpi)) + size > m_end_pos) { size = static_cast(m_end_pos - static_cast(ftell(m_fpi))); } if (!size) { return 0; } return fread(data, 1, size, m_fpi); } int VcdIO::error() const { return ferror(m_fpi); } int64_t VcdIO::duration() const { return AV_NOPTS_VALUE; } size_t VcdIO::size() const { if (m_fpi == nullptr) { errno = EINVAL; return 0; } if (m_end_pos) { return static_cast(m_end_pos - m_start_pos); } struct stat stbuf; fstat(fileno(m_fpi), &stbuf); return static_cast(stbuf.st_size); } size_t VcdIO::tell() const { return static_cast(static_cast(ftell(m_fpi)) - m_start_pos); } int VcdIO::seek(int64_t offset, int whence) { off_t seek_pos; switch (whence) { case SEEK_SET: { seek_pos = static_cast(m_start_pos) + offset; break; } case SEEK_CUR: { seek_pos = static_cast(m_start_pos) + ftell(m_fpi) + offset; break; } case SEEK_END: { seek_pos = static_cast(m_end_pos) - offset; break; } default: { errno = EINVAL; return (EOF); } } if (static_cast(seek_pos) > m_end_pos) { seek_pos = static_cast(m_end_pos); // Cannot go beyond EOF. Set position to end, leave errno untouched. } if (static_cast(seek_pos) < m_start_pos) // Cannot go before head, leave position untouched, set errno. { errno = EINVAL; return (EOF); } return fseek(m_fpi, static_cast(seek_pos), SEEK_SET); } bool VcdIO::eof() const { return ((feof(m_fpi) || (static_cast(ftell(m_fpi)) >= m_end_pos)) ? true : false); } void VcdIO::closeio() { pvt_close(); } void VcdIO::pvt_close() { FILE *fpi = m_fpi; if (fpi != nullptr) { m_fpi = nullptr; fclose(fpi); } } #endif // USE_LIBVCD ffmpegfs-2.50/src/ffmpeg_packet.cc0000664000175000017500000000743015177713600012624 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_packet.cc * @brief FFmpeg_Packet class implementation * * @ingroup ffmpegfs */ #ifdef __cplusplus extern "C" { #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_packet.h" FFmpeg_Packet::FFmpeg_Packet(int stream_index) : m_packet(av_packet_alloc()), m_res(0) { m_res = (m_packet != nullptr) ? 0 : AVERROR(ENOMEM); if (m_packet != nullptr) { m_packet->stream_index = stream_index; } } FFmpeg_Packet::FFmpeg_Packet(const AVPacket *packet) : m_packet(nullptr), m_res(0) { if (packet != nullptr) { m_packet = av_packet_clone(packet); m_res = (m_packet != nullptr) ? 0 : AVERROR(ENOMEM); } else { m_res = AVERROR(EINVAL); } } FFmpeg_Packet::FFmpeg_Packet(const FFmpeg_Packet &packet) : m_packet(nullptr), m_res(0) { if (packet.m_packet != nullptr) { m_packet = av_packet_clone(packet.m_packet); m_res = (m_packet != nullptr) ? 0 : AVERROR(ENOMEM); } else { m_res = AVERROR(EINVAL); } } FFmpeg_Packet::FFmpeg_Packet(FFmpeg_Packet &&packet) noexcept : m_packet(packet.m_packet), m_res(packet.m_res) { packet.m_packet = nullptr; packet.m_res = AVERROR(EINVAL); } FFmpeg_Packet::~FFmpeg_Packet() { free(); } FFmpeg_Packet& FFmpeg_Packet::operator=(const FFmpeg_Packet &packet) noexcept { if (this != &packet && m_packet != packet.m_packet) { AVPacket *new_packet = nullptr; int new_res = AVERROR(EINVAL); if (packet.m_packet != nullptr) { new_packet = av_packet_clone(packet.m_packet); new_res = (new_packet != nullptr) ? 0 : AVERROR(ENOMEM); } free(); m_packet = new_packet; m_res = new_res; } return *this; } FFmpeg_Packet& FFmpeg_Packet::operator=(FFmpeg_Packet &&packet) noexcept { if (this != &packet) { free(); m_packet = packet.m_packet; m_res = packet.m_res; packet.m_packet = nullptr; packet.m_res = AVERROR(EINVAL); } return *this; } FFmpeg_Packet& FFmpeg_Packet::operator=(const AVPacket *packet) noexcept { if (m_packet != packet) { AVPacket *new_packet = nullptr; int new_res = AVERROR(EINVAL); if (packet != nullptr) { new_packet = av_packet_clone(packet); new_res = (new_packet != nullptr) ? 0 : AVERROR(ENOMEM); } free(); m_packet = new_packet; m_res = new_res; } return *this; } int FFmpeg_Packet::res() const { return m_res; } AVPacket* FFmpeg_Packet::clone() const { return av_packet_clone(m_packet); } void FFmpeg_Packet::unref() { if (m_packet != nullptr) { av_packet_unref(m_packet); } } void FFmpeg_Packet::free() { if (m_packet != nullptr) { av_packet_free(&m_packet); m_packet = nullptr; } } AVPacket* FFmpeg_Packet::get() { return m_packet; } const AVPacket* FFmpeg_Packet::get() const { return m_packet; } FFmpeg_Packet::operator AVPacket*() { return m_packet; } FFmpeg_Packet::operator const AVPacket*() const { return m_packet; } AVPacket* FFmpeg_Packet::operator->() { return m_packet; } const AVPacket* FFmpeg_Packet::operator->() const { return m_packet; } ffmpegfs-2.50/src/ffmpegfs.cc0000664000175000017500000025370115200152616011621 /* * Copyright (C) 2006-2008 David Collett * Copyright (C) 2008-2012 K. Henriksson * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpegfs.cc * @brief FFmpeg main function and utilities implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2006-2008 David Collett @n * Copyright (C) 2008-2013 K. Henriksson @n * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) */ #ifdef __cplusplus extern "C" { #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #include #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ffmpegfs.h" #include "logging.h" #include "ffmpegfshelp.h" #include #include #include #include #include #ifdef USE_LIBBLURAY #include #endif #ifdef __clang__ // Silence this in fuse API code for clang #pragma GCC diagnostic ignored "-Wimplicit-int-conversion" #endif #ifndef AV_PROFILE_UNKNOWN /** * @brief Compatibility alias for older FFmpeg versions that only define FF_PROFILE_UNKNOWN. */ #define AV_PROFILE_UNKNOWN FF_PROFILE_UNKNOWN #endif FFMPEGFS_FORMAT_ARR ffmpeg_format; /**< @brief Two FFmpegfs_Format infos, 0: video file, 1: audio file */ FFMPEGFS_PARAMS params; /**< @brief FFmpegfs command line parameters */ FFMPEGFS_PARAMS::FFMPEGFS_PARAMS() : m_basepath("") // required parameter , m_mountpath("") // required parameter , m_audio_codec(AV_CODEC_ID_NONE) // default: use predefined option , m_video_codec(AV_CODEC_ID_NONE) // default: use predefined option , m_autocopy(AUTOCOPY::OFF) // default: off , m_recodesame(RECODESAME::NO) // default: off , m_profile(PROFILE::DEFAULT) // default: no profile , m_level(PRORESLEVEL::NONE) // default: no level // Format // Audio , m_audiobitrate(128*1024) // default: 128 kBit , m_audiosamplerate(44100) // default: 44.1 kHz , m_audiochannels(2) // default: 2 channels , m_sample_fmt(SAMPLE_FMT::FMT_DONTCARE) // default: use source format // Video , m_videobitrate(2*1024*1024) // default: 2 MBit , m_videowidth(0) // default: do not change width , m_videoheight(0) // default: do not change height , m_deinterlace(0) // default: do not interlace video , m_segment_duration(10 * AV_TIME_BASE) // default: 10 seconds , m_min_seek_time_diff(30 * AV_TIME_BASE) // default: 30 seconds // Hardware acceleration , m_hwaccel_enc_API(HWACCELAPI::NONE) // default: Use software encoder , m_hwaccel_enc_device_type(AV_HWDEVICE_TYPE_NONE) // default: Use software encoder , m_hwaccel_dec_API(HWACCELAPI::NONE) // default: Use software encoder , m_hwaccel_dec_device_type(AV_HWDEVICE_TYPE_NONE) // default: Use software decoder , m_hwaccel_dec_blocked(nullptr) // default: No blocked encoders // Subtitles , m_no_subtitles(0) // default: enable subtitles // Album arts , m_noalbumarts(0) // default: copy album arts // Virtual Script , m_enablescript(0) // default: no virtual script , m_scriptfile("index.php") // default name , m_scriptsource("scripts/videotag.php") // default name // Other , m_debug(0) // default: no debug messages , m_log_maxlevel("INFO") // default: INFO level , m_log_stderr(0) // default: do not log to stderr , m_log_syslog(0) // default: do not use syslog , m_logfile("") // default: none // Cache/recoding options , m_expiry_time((60*60*24 /* d */) * 7) // default: 1 week) , m_max_inactive_suspend(15) // default: 15 seconds , m_max_inactive_abort(30) // default: 30 seconds , m_prebuffer_time(0) // default: no prebuffer time , m_prebuffer_size(100 /* KB */ * 1024) // default: 100 KB , m_max_cache_size(0) // default: no limit , m_min_diskspace(0) // default: no minimum , m_cachepath("") // default: $XDG_CACHE_HOME/ffmpegfs , m_disable_cache(0) // default: enabled , m_cache_maintenance((60*60)) // default: prune every 60 minutes , m_prune_cache(0) // default: Do not prune cache immediately , m_clear_cache(0) // default: Do not clear cache on startup , m_max_threads(0) // default: 16 * CPU cores (this value here is overwritten later) , m_decoding_errors(0) // default: ignore errors , m_min_dvd_chapter_duration(1) // default: 1 second , m_oldnamescheme(0) // default: new scheme , m_include_extensions(new (std::nothrow) MATCHVEC) // default: empty list , m_hide_extensions(new (std::nothrow) MATCHVEC) // default: empty list , m_win_smb_fix(1) // default: fix enabled { } FFMPEGFS_PARAMS::FFMPEGFS_PARAMS(const FFMPEGFS_PARAMS & other) { *this = other; } FFMPEGFS_PARAMS::~FFMPEGFS_PARAMS() { delete m_hwaccel_dec_blocked; } FFMPEGFS_PARAMS& FFMPEGFS_PARAMS::operator=(const FFMPEGFS_PARAMS & other) noexcept { if (this != &other) // Self assignment check { m_basepath = other.m_basepath; m_mountpath = other.m_mountpath; m_audio_codec = other.m_audio_codec; m_video_codec = other.m_video_codec; m_autocopy = other.m_autocopy; m_recodesame = other.m_recodesame; m_profile = other.m_profile; m_level = other.m_level; m_audiobitrate = other.m_audiobitrate; m_audiosamplerate = other.m_audiosamplerate; m_audiochannels = other.m_audiochannels; m_sample_fmt = other.m_sample_fmt; m_videobitrate = other.m_videobitrate; m_videowidth = other.m_videowidth; m_videoheight = other.m_videoheight; m_deinterlace = other.m_deinterlace; m_segment_duration = other.m_segment_duration; m_min_seek_time_diff = other.m_min_seek_time_diff; m_hwaccel_enc_API = other.m_hwaccel_enc_API; m_hwaccel_enc_device_type = other.m_hwaccel_enc_device_type; m_hwaccel_enc_device = other.m_hwaccel_enc_device; m_hwaccel_dec_API = other.m_hwaccel_dec_API; m_hwaccel_dec_device_type = other.m_hwaccel_dec_device_type; m_hwaccel_dec_device = other.m_hwaccel_dec_device; m_hwaccel_dec_blocked = other.m_hwaccel_dec_blocked; m_no_subtitles = other.m_no_subtitles; m_noalbumarts = other.m_noalbumarts; m_enablescript = other.m_enablescript; m_scriptfile = other.m_scriptfile; m_scriptsource = other.m_scriptsource; m_debug = other.m_debug; m_log_maxlevel = other.m_log_maxlevel; m_log_stderr = other.m_log_stderr; m_log_syslog = other.m_log_syslog; m_logfile = other.m_logfile; m_expiry_time = other.m_expiry_time; m_max_inactive_suspend = other.m_max_inactive_suspend; m_max_inactive_abort = other.m_max_inactive_abort; m_prebuffer_time = other.m_prebuffer_time; m_prebuffer_size = other.m_prebuffer_size; m_max_cache_size = other.m_max_cache_size; m_min_diskspace = other.m_min_diskspace; m_cachepath = other.m_cachepath; m_disable_cache = other.m_disable_cache; m_cache_maintenance = other.m_cache_maintenance; m_prune_cache = other.m_prune_cache; m_clear_cache = other.m_clear_cache; m_max_threads = other.m_max_threads; m_decoding_errors = other.m_decoding_errors; m_min_dvd_chapter_duration = other.m_min_dvd_chapter_duration; m_oldnamescheme = other.m_oldnamescheme; *m_include_extensions = *other.m_include_extensions; *m_hide_extensions = *other.m_hide_extensions; m_win_smb_fix = other.m_win_smb_fix; } return *this; } bool FFMPEGFS_PARAMS::smart_transcode() const { return (ffmpeg_format[FORMAT::AUDIO].filetype() != FILETYPE::UNKNOWN && ffmpeg_format[FORMAT::VIDEO].filetype() != ffmpeg_format[FORMAT::AUDIO].filetype()); } const FFmpegfs_Format *FFMPEGFS_PARAMS::current_format(LPCVIRTUALFILE virtualfile) const { if (virtualfile == nullptr || virtualfile->m_format_idx > 1) { return nullptr; } return &ffmpeg_format[virtualfile->m_format_idx]; } enum // enum class or typedef here is not compatible with Fuse API { KEY_HELP, KEY_VERSION, KEY_FFMPEG_CAPS, KEY_KEEP_OPT, // Intelligent parameters KEY_DESTTYPE, KEY_AUDIOCODEC, KEY_VIDEOCODEC, KEY_AUDIO_BITRATE, KEY_AUDIO_SAMPLERATE, KEY_AUDIO_CHANNELS, KEY_AUDIO_SAMPLE_FMT, KEY_VIDEO_BITRATE, KEY_SEGMENT_DURATION, KEY_MIN_SEEK_TIME_DIFF, KEY_SCRIPTFILE, KEY_SCRIPTSOURCE, KEY_EXPIRY_TIME, KEY_MAX_INACTIVE_SUSPEND_TIME, KEY_MAX_INACTIVE_ABORT_TIME, KEY_PREBUFFER_TIME, KEY_PREBUFFER_SIZE, KEY_MAX_CACHE_SIZE, KEY_MIN_DISKSPACE_SIZE, KEY_CACHEPATH, KEY_CACHE_MAINTENANCE, KEY_AUTOCOPY, KEY_RECODESAME, KEY_PROFILE, KEY_LEVEL, KEY_LOG_MAXLEVEL, KEY_LOGFILE, KEY_HWACCEL_ENCODER_API, KEY_HWACCEL_ENCODER_DEVICE, KEY_HWACCEL_DECODER_API, KEY_HWACCEL_DECODER_DEVICE, KEY_HWACCEL_DECODER_BLOCKED, KEY_INCLUDE_EXTENSIONS, KEY_HIDE_EXTENSIONS }; /** * Map FFmpegfs options to FUSE parameters */ #define FFMPEGFS_OPT(templ, param, value) { templ, offsetof(FFMPEGFS_PARAMS, param), value } /** * FUSE option descriptions * * Need to ignore annoying warnings caused by fuse.h */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnarrowing" #pragma GCC diagnostic ignored "-Wsign-conversion" static struct fuse_opt ffmpegfs_opts[] = // NOLINT(modernize-avoid-c-arrays) { // Output type FUSE_OPT_KEY("--desttype=%s", KEY_DESTTYPE), FUSE_OPT_KEY("desttype=%s", KEY_DESTTYPE), FUSE_OPT_KEY("--audiocodec=%s", KEY_AUDIOCODEC), FUSE_OPT_KEY("audiocodec=%s", KEY_AUDIOCODEC), FUSE_OPT_KEY("--videocodec=%s", KEY_VIDEOCODEC), FUSE_OPT_KEY("videocodec=%s", KEY_VIDEOCODEC), FUSE_OPT_KEY("--profile=%s", KEY_PROFILE), FUSE_OPT_KEY("profile=%s", KEY_PROFILE), FUSE_OPT_KEY("--autocopy=%s", KEY_AUTOCOPY), FUSE_OPT_KEY("autocopy=%s", KEY_AUTOCOPY), FUSE_OPT_KEY("--recodesame=%s", KEY_RECODESAME), FUSE_OPT_KEY("recodesame=%s", KEY_RECODESAME), FUSE_OPT_KEY("--level=%s", KEY_LEVEL), FUSE_OPT_KEY("level=%s", KEY_LEVEL), // Audio FUSE_OPT_KEY("--audiobitrate=%s", KEY_AUDIO_BITRATE), FUSE_OPT_KEY("audiobitrate=%s", KEY_AUDIO_BITRATE), FUSE_OPT_KEY("--audiosamplerate=%s", KEY_AUDIO_SAMPLERATE), FUSE_OPT_KEY("audiosamplerate=%s", KEY_AUDIO_SAMPLERATE), FUSE_OPT_KEY("--audiochannels=%s", KEY_AUDIO_CHANNELS), FUSE_OPT_KEY("audiochannels=%s", KEY_AUDIO_CHANNELS), FUSE_OPT_KEY("--audiosamplefmt=%s", KEY_AUDIO_SAMPLE_FMT), FUSE_OPT_KEY("audiosamplefmt=%s", KEY_AUDIO_SAMPLE_FMT), // Video FUSE_OPT_KEY("--videobitrate=%s", KEY_VIDEO_BITRATE), FUSE_OPT_KEY("videobitrate=%s", KEY_VIDEO_BITRATE), FFMPEGFS_OPT("--videoheight=%u", m_videoheight, 0), FFMPEGFS_OPT("videoheight=%u", m_videoheight, 0), FFMPEGFS_OPT("--videowidth=%u", m_videowidth, 0), FFMPEGFS_OPT("videowidth=%u", m_videowidth, 0), FFMPEGFS_OPT("--deinterlace", m_deinterlace, 1), FFMPEGFS_OPT("deinterlace", m_deinterlace, 1), // HLS FUSE_OPT_KEY("--segment_duration=%s", KEY_SEGMENT_DURATION), FUSE_OPT_KEY("segment_duration=%s", KEY_SEGMENT_DURATION), FUSE_OPT_KEY("--min_seek_time_diff=%s", KEY_MIN_SEEK_TIME_DIFF), FUSE_OPT_KEY("min_seek_time_diff=%s", KEY_MIN_SEEK_TIME_DIFF), // Hardware acceleration FUSE_OPT_KEY("--hwaccel_enc=%s", KEY_HWACCEL_ENCODER_API), FUSE_OPT_KEY("hwaccel_enc=%s", KEY_HWACCEL_ENCODER_API), FUSE_OPT_KEY("--hwaccel_enc_device=%s", KEY_HWACCEL_ENCODER_DEVICE), FUSE_OPT_KEY("hwaccel_enc_device=%s", KEY_HWACCEL_ENCODER_DEVICE), FUSE_OPT_KEY("--hwaccel_dec=%s", KEY_HWACCEL_DECODER_API), FUSE_OPT_KEY("hwaccel_dec=%s", KEY_HWACCEL_DECODER_API), FUSE_OPT_KEY("--hwaccel_dec_device=%s", KEY_HWACCEL_DECODER_DEVICE), FUSE_OPT_KEY("hwaccel_dec_device=%s", KEY_HWACCEL_DECODER_DEVICE), FUSE_OPT_KEY("--hwaccel_dec_blocked=%s", KEY_HWACCEL_DECODER_BLOCKED), FUSE_OPT_KEY("hwaccel_dec_blocked=%s", KEY_HWACCEL_DECODER_BLOCKED), // Subtitles FFMPEGFS_OPT("--no_subtitles", m_no_subtitles, 1), FFMPEGFS_OPT("no_subtitles", m_no_subtitles, 1), // Album arts FFMPEGFS_OPT("--noalbumarts", m_noalbumarts, 1), FFMPEGFS_OPT("noalbumarts", m_noalbumarts, 1), // Virtual script FFMPEGFS_OPT("--enablescript", m_enablescript, 1), FFMPEGFS_OPT("enablescript", m_enablescript, 1), FUSE_OPT_KEY("--scriptfile=%s", KEY_SCRIPTFILE), FUSE_OPT_KEY("scriptfile=%s", KEY_SCRIPTFILE), FUSE_OPT_KEY("--scriptsource=%s", KEY_SCRIPTSOURCE), FUSE_OPT_KEY("scriptsource=%s", KEY_SCRIPTSOURCE), // Background recoding/caching // Cache FUSE_OPT_KEY("--expiry_time=%s", KEY_EXPIRY_TIME), FUSE_OPT_KEY("expiry_time=%s", KEY_EXPIRY_TIME), FUSE_OPT_KEY("--max_inactive_suspend=%s", KEY_MAX_INACTIVE_SUSPEND_TIME), FUSE_OPT_KEY("max_inactive_suspend=%s", KEY_MAX_INACTIVE_SUSPEND_TIME), FUSE_OPT_KEY("--max_inactive_abort=%s", KEY_MAX_INACTIVE_ABORT_TIME), FUSE_OPT_KEY("max_inactive_abort=%s", KEY_MAX_INACTIVE_ABORT_TIME), FUSE_OPT_KEY("--prebuffer_time=%s", KEY_PREBUFFER_TIME), FUSE_OPT_KEY("prebuffer_time=%s", KEY_PREBUFFER_TIME), FUSE_OPT_KEY("--prebuffer_size=%s", KEY_PREBUFFER_SIZE), FUSE_OPT_KEY("prebuffer_size=%s", KEY_PREBUFFER_SIZE), FUSE_OPT_KEY("--max_cache_size=%s", KEY_MAX_CACHE_SIZE), FUSE_OPT_KEY("max_cache_size=%s", KEY_MAX_CACHE_SIZE), FUSE_OPT_KEY("--min_diskspace=%s", KEY_MIN_DISKSPACE_SIZE), FUSE_OPT_KEY("min_diskspace=%s", KEY_MIN_DISKSPACE_SIZE), FUSE_OPT_KEY("--cachepath=%s", KEY_CACHEPATH), FUSE_OPT_KEY("cachepath=%s", KEY_CACHEPATH), FFMPEGFS_OPT("--disable_cache", m_disable_cache, 1), FFMPEGFS_OPT("disable_cache", m_disable_cache, 1), FUSE_OPT_KEY("--cache_maintenance=%s", KEY_CACHE_MAINTENANCE), FUSE_OPT_KEY("cache_maintenance=%s", KEY_CACHE_MAINTENANCE), FFMPEGFS_OPT("--prune_cache", m_prune_cache, 1), FFMPEGFS_OPT("--clear_cache", m_clear_cache, 1), FFMPEGFS_OPT("clear_cache", m_clear_cache, 1), // Other FFMPEGFS_OPT("--max_threads=%u", m_max_threads, 0), FFMPEGFS_OPT("max_threads=%u", m_max_threads, 0), FFMPEGFS_OPT("--decoding_errors=%u", m_decoding_errors, 0), FFMPEGFS_OPT("decoding_errors=%u", m_decoding_errors, 0), FFMPEGFS_OPT("--min_dvd_chapter_duration=%u", m_min_dvd_chapter_duration, 0), FFMPEGFS_OPT("min_dvd_chapter_duration=%u", m_min_dvd_chapter_duration, 0), FFMPEGFS_OPT("--oldnamescheme=%u", m_oldnamescheme, 0), FFMPEGFS_OPT("oldnamescheme=%u", m_oldnamescheme, 0), FUSE_OPT_KEY("--include_extensions=%s", KEY_INCLUDE_EXTENSIONS), FUSE_OPT_KEY("include_extensions=%s", KEY_INCLUDE_EXTENSIONS), FUSE_OPT_KEY("--hide_extensions=%u", KEY_HIDE_EXTENSIONS), FUSE_OPT_KEY("hide_extensions=%u", KEY_HIDE_EXTENSIONS), // Experimental FFMPEGFS_OPT("--win_smb_fix=%u", m_win_smb_fix, 1), FFMPEGFS_OPT("win_smb_fix=%u", m_win_smb_fix, 1), // FFmpegfs options FFMPEGFS_OPT("-d", m_debug, 1), FFMPEGFS_OPT("debug", m_debug, 1), FUSE_OPT_KEY("--log_maxlevel=%s", KEY_LOG_MAXLEVEL), FUSE_OPT_KEY("log_maxlevel=%s", KEY_LOG_MAXLEVEL), FFMPEGFS_OPT("--log_stderr", m_log_stderr, 1), FFMPEGFS_OPT("log_stderr", m_log_stderr, 1), FFMPEGFS_OPT("--log_syslog", m_log_syslog, 1), FFMPEGFS_OPT("log_syslog", m_log_syslog, 1), FUSE_OPT_KEY("--logfile=%s", KEY_LOGFILE), FUSE_OPT_KEY("logfile=%s", KEY_LOGFILE), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_KEY("-c", KEY_FFMPEG_CAPS), FUSE_OPT_KEY("--capabilities", KEY_FFMPEG_CAPS), FUSE_OPT_KEY("-d", KEY_KEEP_OPT), FUSE_OPT_KEY("debug", KEY_KEEP_OPT), FUSE_OPT_END }; #pragma GCC diagnostic pop typedef std::map AUTOCOPY_MAP; /**< @brief Map command line option to AUTOCOPY enum */ typedef std::map PROFILE_MAP; /**< @brief Map command line option to PROFILE enum */ typedef std::map LEVEL_MAP; /**< @brief Map command line option to LEVEL enum */ typedef std::map RECODESAME_MAP; /**< @brief Map command line option to RECODESAME enum */ typedef struct HWACCEL /**< @brief Hardware acceleration device and type */ { bool m_supported; /**< @brief true if API supported, false if not */ HWACCELAPI m_hwaccel_API; /**< @brief Acceleration API, e.g VAAPI, MMAL or OMX */ AVHWDeviceType m_hwaccel_device_type; /**< @brief Hardware buffering type, NONE if not used */ } HWACCEL; typedef std::map HWACCEL_MAP; /**< @brief Map command line option to HWACCEL struct */ typedef std::map CODEC_MAP; /**< @brief Map command line option to AVCodecID */ typedef std::map SAMPLE_FMT_MAP; /**< @brief Map command line option to SAMPLE_FMT */ typedef std::map AUDIOCODEC_MAP; /**< @brief Map command line option to audio AVCodecID */ typedef std::map VIDEOCODEC_MAP; /**< @brief Map command line option to video AVCodecID */ /** * @brief List of audio codecs */ static const AUDIOCODEC_MAP audiocodec_map { { "AAC", AV_CODEC_ID_AAC }, // TS, MP4, MOV, MKV { "AC3", AV_CODEC_ID_AC3 }, // MP4, MOV, MKV { "MP3", AV_CODEC_ID_MP3 }, // TS, MP4, MOV, MKV { "OPUS", AV_CODEC_ID_OPUS }, // webm { "VORBIS", AV_CODEC_ID_VORBIS }, /** @todo webm: sound skips */ { "DTS", AV_CODEC_ID_DTS }, /** @todo invalid argument? */ { "PCM16", AV_CODEC_ID_PCM_S16LE }, /** @todo is this useable? */ { "PCM24", AV_CODEC_ID_PCM_S24LE }, /** @todo is this useable? */ { "PCM32", AV_CODEC_ID_PCM_S32LE }, /** @todo is this useable? */ }; /** * @brief List of video codecs */ const static VIDEOCODEC_MAP videocodec_map { { "MPEG1", AV_CODEC_ID_MPEG1VIDEO }, // TS, MP4, MKV { "MPEG2", AV_CODEC_ID_MPEG2VIDEO }, // TS, MP4, MKV { "H264", AV_CODEC_ID_H264 }, // TS, MP4, MKV { "H265", AV_CODEC_ID_H265 }, // TS, MP4, MKV { "VP8", AV_CODEC_ID_VP8 }, // WebM { "VP9", AV_CODEC_ID_VP9 }, // WebM //{ "AV1", AV_CODEC_ID_AV1 }, /** @todo WebM ends with "Could not write video frame (error 'Invalid data found when processing input')." */ }; /** * List of AUTOCOPY options */ static const AUTOCOPY_MAP autocopy_map { { "OFF", AUTOCOPY::OFF }, { "MATCH", AUTOCOPY::MATCH }, { "MATCHLIMIT", AUTOCOPY::MATCHLIMIT }, { "STRICT", AUTOCOPY::STRICT }, { "STRICTLIMIT", AUTOCOPY::STRICTLIMIT }, }; /** * List if MP4 profiles */ static const PROFILE_MAP profile_map { { "NONE", PROFILE::DEFAULT }, // MP4 { "FF", PROFILE::MP4_FF }, { "EDGE", PROFILE::MP4_EDGE }, { "IE", PROFILE::MP4_IE }, { "CHROME", PROFILE::MP4_CHROME }, { "SAFARI", PROFILE::MP4_SAFARI }, { "OPERA", PROFILE::MP4_OPERA }, { "MAXTHON", PROFILE::MP4_MAXTHON }, // ALAC { "ITUNES", PROFILE::ALAC_ITUNES }, // WEBM }; /** * List if ProRes levels. */ static const LEVEL_MAP prores_level_map { // ProRes { "PROXY", PRORESLEVEL::PRORES_PROXY }, { "LT", PRORESLEVEL::PRORES_LT }, { "STANDARD", PRORESLEVEL::PRORES_STANDARD }, { "HQ", PRORESLEVEL::PRORES_HQ }, }; /** * List if recode options. */ static const RECODESAME_MAP recode_map { // Recode to same format { "NO", RECODESAME::NO }, { "YES", RECODESAME::YES }, }; /** * List if hardware acceleration options. * See https://trac.ffmpeg.org/wiki/HWAccelIntro * * AV_HWDEVICE_TYPE_NONE will be set to the appropriate device type * in build_device_type_list() by asking the FFmpeg API for the proper * type. */ static HWACCEL_MAP hwaccel_map { { "NONE", { true, HWACCELAPI::NONE, AV_HWDEVICE_TYPE_NONE } }, // **** Supported by Linux **** { "VAAPI", { true, HWACCELAPI::VAAPI, AV_HWDEVICE_TYPE_NONE } }, // Video Acceleration API (VA-API), https://trac.ffmpeg.org/wiki/Hardware/VAAPI // RaspberryPi { "MMAL", { true, HWACCELAPI::MMAL, AV_HWDEVICE_TYPE_NONE } }, // Multimedia Abstraction Layer by Broadcom. Encoding only. { "OMX", { true, HWACCELAPI::OMX, AV_HWDEVICE_TYPE_NONE } }, // OpenMAX (Open Media Acceleration). Decoding only. #if 0 // Additional formats { "CUDA", { false, HWACCELAPI::CUDA, AV_HWDEVICE_TYPE_NONE } }, // Compute Unified Device Architecture, see https://developer.nvidia.com/ffmpeg and https://en.wikipedia.org/wiki/CUDA { "V4L2M2M", { false, HWACCELAPI::V4L2M2M, AV_HWDEVICE_TYPE_NONE } }, // v4l2 mem to mem (Video4linux) { "VDPAU", { false, HWACCELAPI::VDPAU, AV_HWDEVICE_TYPE_NONE } }, // Video Decode and Presentation API for Unix, see https://en.wikipedia.org/wiki/VDPAU { "QSV", { false, HWACCELAPI::QSV, AV_HWDEVICE_TYPE_NONE } }, // QuickSync, see https://trac.ffmpeg.org/wiki/Hardware/QuickSync { "OPENCL", { false, HWACCELAPI::OPENCL, AV_HWDEVICE_TYPE_NONE } }, // Open Standard for Parallel Programming of Heterogeneous Systems, see https://trac.ffmpeg.org/wiki/HWAccelIntro#OpenCL #if HAVE_VULKAN_HWACCEL { "VULKAN", { false, HWACCELAPI::VULKAN, AV_HWDEVICE_TYPE_NONE } }, // Low-overhead, cross-platform 3D graphics and computing API, requires Libavutil >= 56.30.100, see https://en.wikipedia.org/wiki/Vulkan_(API) #endif // HAVE_VULKAN_HWACCEL #if __APPLE__ // MacOS, not supported { "VIDEOTOOLBOX", { false, HWACCELAPI::VIDEOTOOLBOX, AV_HWDEVICE_TYPE_NONE } }, // https://trac.ffmpeg.org/wiki/HWAccelIntro#VideoToolbox #endif #if __ANDROID__ // Android { "MEDIACODEC", { false, HWACCELAPI::MEDIACODEC, AV_HWDEVICE_TYPE_NONE } }, // See https://developer.android.com/reference/android/media/MediaCodec #endif #if _WIN32 // **** Not supported **** // Digital Rights Management { "DRM", { false, HWACCELAPI::DRM, AV_HWDEVICE_TYPE_NONE } }, // Windows only, not supported { "DXVA2", { false, HWACCELAPI::DXVA2, AV_HWDEVICE_TYPE_NONE } }, // Direct3D 9 / DXVA2 { "D3D11VA", { false, HWACCELAPI::D3D11VA, AV_HWDEVICE_TYPE_NONE } }, // Direct3D 11 #endif #endif }; /** * List of AUTOCOPY options */ static const CODEC_MAP hwaccel_codec_map { { "H263", AV_CODEC_ID_H263 }, { "H264", AV_CODEC_ID_H264 }, { "HEVC", AV_CODEC_ID_HEVC }, { "MPEG2", AV_CODEC_ID_MPEG2VIDEO }, { "MPEG4", AV_CODEC_ID_MPEG4 }, { "VC1", AV_CODEC_ID_VC1 }, { "VP8", AV_CODEC_ID_VP8 }, { "VP9", AV_CODEC_ID_VP9 }, { "WMV3", AV_CODEC_ID_WMV3 }, }; /** * List of sample formats. */ static const SAMPLE_FMT_MAP sample_fmt_map { { "0", SAMPLE_FMT::FMT_DONTCARE }, { "8", SAMPLE_FMT::FMT_8 }, { "16", SAMPLE_FMT::FMT_16 }, { "24", SAMPLE_FMT::FMT_24 }, { "32", SAMPLE_FMT::FMT_32 }, { "64", SAMPLE_FMT::FMT_64 }, { "F16", SAMPLE_FMT::FMT_F16 }, { "F24", SAMPLE_FMT::FMT_F24 }, { "F32", SAMPLE_FMT::FMT_F32 }, { "F64", SAMPLE_FMT::FMT_F64 }, }; static int get_bitrate(const std::string & arg, BITRATE *bitrate); static int get_samplerate(const std::string & arg, int * samplerate); static int get_sampleformat(const std::string & arg, SAMPLE_FMT * sample_fmt); static int get_time(const std::string & arg, time_t *time); static int get_size(const std::string & arg, size_t *size); static int get_desttype(const std::string & arg, FFMPEGFS_FORMAT_ARR & format); static int get_audiocodec(const std::string & arg, AVCodecID *audio_codec); static int get_videocodec(const std::string & arg, AVCodecID *video_codec); static int get_autocopy(const std::string & arg, AUTOCOPY *autocopy); static int get_recodesame(const std::string & arg, RECODESAME *recode); static int get_profile(const std::string & arg, PROFILE *profile); static int get_level(const std::string & arg, PRORESLEVEL *level); static int get_segment_duration(const std::string & arg, int64_t *value); static int get_seek_time_diff(const std::string & arg, int64_t *value); static int get_hwaccel(const std::string & arg, HWACCELAPI *hwaccel_API, AVHWDeviceType *hwaccel_device_type); static int get_codec(const std::string & codec, AVCodecID *codec_id); static int get_hwaccel_dec_blocked(const std::string & arg, HWACCEL_BLOCKED_MAP **hwaccel_dec_blocked); static int get_value(const std::string & arg, int *value); static int get_value(const std::string & arg, std::string *value); static int get_value(const std::string & arg, MATCHVEC *value); //static int get_value(const std::string & arg, std::optional *value); static int get_value(const std::string & arg, double *value); static int ffmpegfs_opt_proc(__attribute__((unused)) void* data, const char* arg, int key, struct fuse_args *outargs); static bool set_defaults(); static void build_device_type_list(); static void print_params(); static void usage(); static void ffmpeg_log(void *ptr, int level, const char *fmt, va_list vl); static bool init_logging(const std::string &logfile, const std::string & max_level, bool to_stderr, bool to_syslog); /** * @brief Print program usage info. */ static void usage() { std::string help; size_t pos; help.assign(reinterpret_cast(ffmpegfshelp), ffmpegfshelp_len); pos = help.find("OPTIONS\n"); std::cout << help.substr(pos + sizeof("OPTIONS\n")); } /** * @brief Iterate through all elements in map print all keys * @param[in] info - Informative text, will be printed before the list. May be nullptr. * @param[in] map - Map to go through. */ template static void list_options(const char * info, const T & map) { std::string buffer; for (typename T::const_iterator it = map.cbegin(); it != map.cend();) { buffer += it->first.c_str(); if (++it != map.cend()) { buffer += ", "; } } if (info != nullptr) { std::fprintf(stderr, "%s: %s\n", info, buffer.c_str()); } else { std::fprintf(stderr, "%s\n", buffer.c_str()); } } /** * @brief Get formatted bitrate. @verbatim Supported formats: In bit/s: # or #bps In kbit/s: #M or #Mbps In Mbit/s: #M or #Mbps @endverbatim * @param[in] arg - Bitrate as string. * @param[in] bitrate - On return, contains parsed bitrate. * @return Returns 0 if valid; if invalid returns -1. */ static int get_bitrate(const std::string & arg, BITRATE *bitrate) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); int reti; // Check for decimal number reti = reg_compare(data, "^([1-9][0-9]*|0)?(bps)?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *bitrate = static_cast(std::stol(data)); return 0; // OK } // Check for number with optional descimal point and K modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?K(bps)?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *bitrate = static_cast(std::stof(data) * 1000); return 0; // OK } // Check for number with optional descimal point and M modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?M(bps)?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *bitrate = static_cast(std::stof(data) * 1000000); return 0; // OK } std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid bit rate '%s'\n", param.c_str(), data.c_str()); } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get formatted sample rate. @verbatim Supported formats: In Hz: # or #Hz In kHz: #K or #KHz @endverbatim * @param[in] arg - Samplerate as string. * @param[in] samplerate - On return, contains parsed sample rate. * @return Returns 0 if valid; if invalid returns -1. */ static int get_samplerate(const std::string & arg, int * samplerate) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); int reti; // Check for decimal number reti = reg_compare(data, "^([1-9][0-9]*|0)(Hz)?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *samplerate = std::stoi(data); return 0; // OK } // Check for number with optional descimal point and K modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?K(Hz)?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *samplerate = static_cast(std::stof(data) * 1000); return 0; // OK } std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid sample rate '%s'\n", param.c_str(), data.c_str()); } else { std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); } return -1; } /** * @brief Get sample format * @param[in] arg - Sample format as string. * @param[in] sample_fmt - On return, contains parsed sample format. * @return Returns 0 if valid; if invalid returns -1. */ static int get_sampleformat(const std::string & arg, SAMPLE_FMT * sample_fmt) { size_t pos = arg.find('='); *sample_fmt = SAMPLE_FMT::FMT_DONTCARE; if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); SAMPLE_FMT_MAP::const_iterator it = sample_fmt_map.find(data); if (it == sample_fmt_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid sample format option: %s\n", param.c_str(), data.c_str()); list_options("Valid sample formats are", sample_fmt_map); return -1; } // May fail later: Can only be checked when destination format is known. *sample_fmt = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } std::string get_sampleformat_text(SAMPLE_FMT sample_fmt) { SAMPLE_FMT_MAP::const_iterator it = search_by_value(sample_fmt_map, sample_fmt); if (it != sample_fmt_map.cend()) { return it->first; } return "INVALID"; } /** * @brief Get formatted time, @verbatim Supported formats: Seconds: # @n Minutes: #m @n Hours: #h @n Days: #d @n Weeks: #w @endverbatim * @param[in] arg - Time as string. * @param[in] time - On return, contains parsed time. * @return Returns 0 if valid; if invalid returns -1. */ static int get_time(const std::string & arg, time_t *time) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); int reti; // Check for decimal number reti = reg_compare(data, "^([1-9][0-9]*|0)?s?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *time = static_cast(std::stol(data)); return 0; // OK } // Check for number with optional descimal point and m modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?m$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *time = static_cast(std::stof(data) * 60); return 0; // OK } // Check for number with optional descimal point and h modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?h$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *time = static_cast(std::stof(data) * 60 * 60); return 0; // OK } // Check for number with optional descimal point and d modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?d$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *time = static_cast(std::stof(data) * 60 * 60 * 24); return 0; // OK } // Check for number with optional descimal point and w modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?w$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *time = static_cast(std::stof(data) * 60 * 60 * 24 * 7); return 0; // OK } std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid time format '%s'\n", param.c_str(), data.c_str()); } else { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid time format\n", arg.c_str()); } return -1; } /** * @brief Read size: @n @verbatim Supported formats: In bytes: # or #B @n In KBytes: #K or #KB @n In MBytes: #B or #MB @n In GBytes: #G or #GB @n In TBytes: #T or #TB @endverbatim * @param[in] arg - Time as string. * @param[out] size - On return, contains parsed size. * @return Returns 0 if valid; if invalid returns -1. */ static int get_size(const std::string & arg, size_t *size) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); int reti; // Check for decimal number reti = reg_compare(data, "^([1-9][0-9]*|0)?B?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *size = static_cast(std::stol(data)); return 0; // OK } // Check for number with optional descimal point and K/KB modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?KB?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *size = static_cast(std::stof(data) * 1024); return 0; // OK } // Check for number with optional descimal point and M/MB modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?MB?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *size = static_cast(std::stof(data) * 1024 * 1024); return 0; // OK } // Check for number with optional descimal point and G/GB modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?GB?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *size = static_cast(std::stof(data) * 1024 * 1024 * 1024); return 0; // OK } // Check for number with optional descimal point and T/TB modifier reti = reg_compare(data, "^[1-9][0-9]*(\\.[0-9]+)?TB?$", std::regex::icase); if (reti == -1) { return -1; } else if (!reti) { *size = static_cast(std::stof(data) * 1024 * 1024 * 1024 * 1024); return 0; // OK } std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid size '%s'\n", param.c_str(), data.c_str()); } else { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid size\n", arg.c_str()); } return -1; } /** * @brief Get destination type. * @param[in] arg - Format as string (MP4, OGG etc.). * @param[out] format - Index 0: Selected video format.@n * Index 1: Selected audio format. * @return Returns 0 if found; if not found returns -1. */ static int get_desttype(const std::string & arg, FFMPEGFS_FORMAT_ARR & format) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::vector results = split(arg.substr(pos + 1), "\\+"); if (results.size() > 0 && results.size() < 3) { // Check for valid destination type and obtain codecs and file type. if (!format[0].init(results[0])) { std::fprintf(stderr, "INVALID PARAMETER (%s): No codecs available for desttype: %s\n", param.c_str(), results[0].c_str()); return 1; } if (results.size() == 2) { if (format[0].video_codec() == AV_CODEC_ID_NONE) { std::fprintf(stderr, "INVALID PARAMETER (%s): First format %s does not support video\n", param.c_str(), results[0].c_str()); return 1; } if (!format[1].init(results[1])) { std::fprintf(stderr, "INVALID PARAMETER (%s): No codecs available for desttype: %s\n", param.c_str(), results[1].c_str()); return 1; } if (format[1].video_codec() != AV_CODEC_ID_NONE) { std::fprintf(stderr, "INVALID PARAMETER (%s): Second format %s should be audio only\n", param.c_str(), results[1].c_str()); return 1; } } return 0; } } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get the audio codec. * @param[in] arg - One of the possible audio codecs. * @param[out] audio_codec - Upon return contains selected AVCodecID enum. * @return Returns 0 if found; if not found returns -1. */ static int get_audiocodec(const std::string & arg, AVCodecID *audio_codec) { *audio_codec = AV_CODEC_ID_NONE; size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); AUDIOCODEC_MAP::const_iterator it = audiocodec_map.find(data); if (it == audiocodec_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid videocodec option: %s\n", param.c_str(), data.c_str()); list_options("Valid audio codecs", audiocodec_map); return -1; } *audio_codec = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get the video codec. * @param[in] arg - One of the possible video codecs. * @param[out] video_codec - Upon return contains selected AVCodecID enum. * @return Returns 0 if found; if not found returns -1. */ static int get_videocodec(const std::string & arg, AVCodecID *video_codec) { *video_codec = AV_CODEC_ID_NONE; size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); VIDEOCODEC_MAP::const_iterator it = videocodec_map.find(data); if (it == videocodec_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid videocodec option: %s\n", param.c_str(), data.c_str()); list_options("Valid video codecs", videocodec_map); return -1; } *video_codec = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get autocopy option. * @param[in] arg - One of the auto copy options. * @param[out] autocopy - Upon return contains selected AUTOCOPY enum. * @return Returns 0 if found; if not found returns -1. */ static int get_autocopy(const std::string & arg, AUTOCOPY *autocopy) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); AUTOCOPY_MAP::const_iterator it = autocopy_map.find(data); if (it == autocopy_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid autocopy option: %s\n", param.c_str(), data.c_str()); list_options("Valid autocopy options are", autocopy_map); return -1; } *autocopy = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } std::string get_audio_codec_text(AVCodecID audio_codec) { AUDIOCODEC_MAP::const_iterator it = search_by_value(audiocodec_map, audio_codec); if (it != audiocodec_map.cend()) { return it->first; } return "INVALID"; } std::string get_video_codec_text(AVCodecID video_codec) { AUDIOCODEC_MAP::const_iterator it = search_by_value(videocodec_map, video_codec); if (it != videocodec_map.cend()) { return it->first; } return "INVALID"; } std::string get_autocopy_text(AUTOCOPY autocopy) { AUTOCOPY_MAP::const_iterator it = search_by_value(autocopy_map, autocopy); if (it != autocopy_map.cend()) { return it->first; } return "INVALID"; } /** * @brief Get recode option. * @param[in] arg - One of the recode options. * @param[out] recode - Upon return contains selected RECODESAME enum. * @return Returns 0 if found; if not found returns -1. */ static int get_recodesame(const std::string & arg, RECODESAME *recode) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); RECODESAME_MAP::const_iterator it = recode_map.find(data); if (it == recode_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid recode option: %s\n", param.c_str(), data.c_str()); list_options("Valid recode options are", recode_map); return -1; } *recode = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } std::string get_recodesame_text(RECODESAME recode) { RECODESAME_MAP::const_iterator it = search_by_value(recode_map, recode); if (it != recode_map.cend()) { return it->first; } return "INVALID"; } /** * @brief Get profile option. * @param[in] arg - One of the auto profile options. * @param[out] profile - Upon return contains selected PROFILE enum. * @return Returns 0 if found; if not found returns -1. */ static int get_profile(const std::string & arg, PROFILE *profile) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); PROFILE_MAP::const_iterator it = profile_map.find(data); if (it == profile_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid profile: %s\n", param.c_str(), data.c_str()); list_options("Valid profiles are", profile_map); return -1; } *profile = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } std::string get_profile_text(PROFILE profile) { PROFILE_MAP::const_iterator it = search_by_value(profile_map, profile); if (it != profile_map.cend()) { return it->first; } return "INVALID"; } // Read level /** * @brief Get ProRes level * @param[in] arg - One of the ProRes levels. * @param[out] level - Upon return contains selected PRORESLEVEL enum. * @return Returns 0 if found; if not found returns -1. */ static int get_level(const std::string & arg, PRORESLEVEL *level) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); LEVEL_MAP::const_iterator it = prores_level_map.find(data); if (it == prores_level_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid level: %s\n", param.c_str(), data.c_str()); list_options("Valid levels are", prores_level_map); return -1; } *level = it->second; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } // Get level text std::string get_level_text(PRORESLEVEL level) { LEVEL_MAP::const_iterator it = search_by_value(prores_level_map, level); if (it != prores_level_map.cend()) { return it->first; } return "INVALID"; } /** * @brief Get HLS segment duration. Input value must be in seconds. * @param[in] arg - Segment duration in seconds. Must be greater than 0. * @param[out] value - Upon return contains segment duration in AV_TIME_BASE units. * @return Returns 0 if valid; if out of range returns -1. */ static int get_segment_duration(const std::string & arg, int64_t *value) { double duration; if (get_value(arg, &duration) < 0) { return -1; } if (*value <= 0) { std::fprintf(stderr, "INVALID PARAMETER: segment_duration %.1f is out of range. For obvious reasons this must be greater than zero.\n", duration); return -1; } *value = static_cast(duration * AV_TIME_BASE); return 0; } /** * @brief Get seek time diff. Input value must be in seconds. * @param[in] arg - Segment duration in seconds. Must be greater or equal 0. * @param[out] value - Upon return contains seek time diff in AV_TIME_BASE units. * @return Returns 0 if valid; if out of range returns -1. */ static int get_seek_time_diff(const std::string & arg, int64_t *value) { double duration; if (get_value(arg, &duration) < 0) { return -1; } if (*value <= 0) { std::fprintf(stderr, "INVALID PARAMETER: seek time %.1f is out of range. For obvious reasons this must be greater than or equal zero.\n", duration); return -1; } *value = static_cast(duration * AV_TIME_BASE); return 0; } /** * @brief Get type of hardware acceleration. * To keep it simple, currently all values are accepted. * @param[in] arg - One of the hardware acceleration types, e.g. VAAPI. * @param[out] hwaccel_API - Upon return contains the hardware acceleration API. * @param[out] hwaccel_device_type - Upon return contains the hardware acceleration device type. * @return Returns 0 if found; if not found returns -1. */ static int get_hwaccel(const std::string & arg, HWACCELAPI *hwaccel_API, AVHWDeviceType *hwaccel_device_type) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::string data(arg.substr(pos + 1)); HWACCEL_MAP::const_iterator it = hwaccel_map.find(data); if (it == hwaccel_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER (%s): Invalid hardware acceleration API: %s\n", param.c_str(), data.c_str()); list_options("Valid hardware acceleration APIs are", hwaccel_map); return -1; } const HWACCEL & hwaccel = it->second; if (!hwaccel.m_supported) { std::fprintf(stderr, "INVALID PARAMETER (%s): Unsupported hardware acceleration API: %s\n", param.c_str(), data.c_str()); return -1; } *hwaccel_API = hwaccel.m_hwaccel_API; *hwaccel_device_type = hwaccel.m_hwaccel_device_type; return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get AVCodecID for codec string * @param[in] codec - Codec string * @param[out] codec_id - AVCodecID of codec string * @return Returns 0 if found; if not found returns -1 and codec_id set to AV_CODEC_ID_NONE. */ static int get_codec(const std::string & codec, AVCodecID *codec_id) { CODEC_MAP::const_iterator it = hwaccel_codec_map.find(codec); if (it == hwaccel_codec_map.cend()) { std::fprintf(stderr, "INVALID PARAMETER: Unknown codec '%s'.\n", codec.c_str()); list_options("Valid hardware acceleration APIs are", hwaccel_codec_map); *codec_id = AV_CODEC_ID_NONE; return -1; } *codec_id = it->second; return 0; } /** * @brief Get list of codecs and optional profiles blocked for hardware accelerated decoding * @param[in] arg - Parameter with codec string and optional profile * @param[out] hwaccel_dec_blocked - Map with blocked codecs and profiles. Will be allocated if necessary. * @return Returns 0 on success; on error returns -1. */ static int get_hwaccel_dec_blocked(const std::string & arg, HWACCEL_BLOCKED_MAP **hwaccel_dec_blocked) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::string param(arg.substr(0, pos)); std::stringstream data(arg.substr(pos + 1)); std::string codec; if (*hwaccel_dec_blocked == nullptr) { *hwaccel_dec_blocked = new (std::nothrow) HWACCEL_BLOCKED_MAP; } if (!std::getline(data, codec, ':')) { std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", param.c_str()); return -1; } AVCodecID codec_id; if (get_codec(codec, &codec_id)) { std::fprintf(stderr, "INVALID PARAMETER (%s): Unknown codec '%s'\n", param.c_str(), codec.c_str()); return -1; } int nProfilesFound = 0; for (std::string profile; std::getline(data, profile, ':');) { nProfilesFound++; // Block codec and profile (*hwaccel_dec_blocked)->insert(std::pair(codec_id, std::stoi(profile))); } if (!nProfilesFound) { // No profile (*hwaccel_dec_blocked)->insert(std::pair(codec_id, AV_PROFILE_UNKNOWN)); } return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } bool check_hwaccel_dec_blocked(AVCodecID codec_id, int profile) { if (params.m_hwaccel_dec_blocked == nullptr) { return false; // Nothing blocked } for (HWACCEL_BLOCKED_MAP::const_iterator it = params.m_hwaccel_dec_blocked->find(codec_id); it != params.m_hwaccel_dec_blocked->cend(); ++it) { if (it->first == codec_id && (it->second == profile || it->second == AV_PROFILE_UNKNOWN)) { return true; } } return false; } std::string get_hwaccel_API_text(HWACCELAPI hwaccel_API) { HWACCEL_MAP::const_iterator it = hwaccel_map.cbegin(); while (it != hwaccel_map.cend()) { if (it->second.m_hwaccel_API == hwaccel_API) { return it->first; } ++it; } return "INVALID"; } /** * @brief Get value from command line string. * Finds whatever is after the "=" sign. * @param[in] arg - Command line option. * @param[in] value - Upon return, contains the value after the "=" sign. * @return Returns 0 if valid; if invalid returns -1. */ static int get_value(const std::string & arg, int *value) { size_t pos = arg.find('='); if (pos != std::string::npos) { *value = std::stoi(arg.substr(pos + 1)); return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get value from command line string. * Finds whatever is after the "=" sign. * @param[in] arg - Command line option. * @param[in] value - Upon return, contains the value after the "=" sign. * @return Returns 0 if valid; if invalid returns -1. */ static int get_value(const std::string & arg, std::string *value) { size_t pos = arg.find('='); if (pos != std::string::npos) { *value = arg.substr(pos + 1); return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief Get comma separated values from command line string. * Finds whatever is after the "=" sign. * @param[in] arg - Command line option. * @param[in] value - Upon return, contains a set of the values after the "=" sign. * @return Returns 0 if valid; if invalid returns -1. */ static int get_value(const std::string & arg, MATCHVEC *value) { size_t pos = arg.find('='); if (pos != std::string::npos) { std::vector v = split(arg.substr(pos + 1), ","); for (const std::string & str : v) { int res = fnmatch(str.c_str(), "", 0); if (res != 0 && res != FNM_NOMATCH) { std::fprintf(stderr, "INVALID PARAMETER (%s): Error in wildcard pattern\n", str.c_str()); return -1; } } value->insert(value->end(), v.begin(), v.end()); return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } #if 0 /** * @brief Get value from command line string. * Finds whatever is after the "=" sign. * @param[in] arg - Command line option. * @param[in] value - Upon return, contains the value after the "=" sign. * @return Returns 0 if valid; if invalid returns -1. */ static int get_value(const std::string & arg, std::optional *value) { size_t pos = arg.find('='); if (pos != std::string::npos) { *value = arg.substr(pos + 1); return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } #endif /** * @brief Get value from command line string. * Finds whatever is after the "=" sign. * @param[in] arg - Command line option. * @param[in] value - Upon return, contains the value after the "=" sign. * @return Returns 0 if valid; if invalid returns -1. */ static int get_value(const std::string & arg, double *value) { size_t pos = arg.find('='); if (pos != std::string::npos) { *value = std::stof(arg.substr(pos + 1)); return 0; } std::fprintf(stderr, "INVALID PARAMETER (%s): Missing argument\n", arg.c_str()); return -1; } /** * @brief FUSE option parsing function. * @param[in] data - is the user data passed to the fuse_opt_parse() function * @param[in] arg - is the whole argument or option * @param[in] key - determines why the processing function was called * @param[in] outargs - the current output argument list * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept */ static int ffmpegfs_opt_proc(__attribute__((unused)) void* data, const char* arg, int key, struct fuse_args *outargs) { switch (key) { case FUSE_OPT_KEY_NONOPT: { static int n; // check for basepath and bitrate parameters if (n == 0 && params.m_basepath.empty()) { expand_path(¶ms.m_basepath, arg); sanitise_filepath(¶ms.m_basepath); append_sep(¶ms.m_basepath); n++; return 0; } else if (n == 1 && params.m_mountpath.empty()) { expand_path(¶ms.m_mountpath, arg); sanitise_filepath(¶ms.m_mountpath); append_sep(¶ms.m_mountpath); if (!docker_client) { switch (is_mount(params.m_mountpath)) { case 1: { std::fprintf(stderr, "%-25s: already mounted\n", params.m_mountpath.c_str()); exit(1); } //case -1: //{ // // Error already reported // exit(1); //} } } n++; return 1; } break; } case KEY_HELP: { usage(); fuse_opt_add_arg(outargs, "-ho"); fuse_main(outargs->argc, outargs->argv, &ffmpegfs_ops, nullptr); exit(1); } case KEY_VERSION: { std::printf("-------------------------------------------------------------------------------------------\n"); #ifdef __GNUC__ #ifndef __clang_version__ std::printf("%-20s: %s (%s)\n", "Built with", "gcc " __VERSION__, HOST_OS); #else std::printf("%-20s: %s (%s)\n", "Built with", "clang " __clang_version__, HOST_OS); #endif #endif std::printf("%-20s: %s\n\n", "configuration", CONFIGURE_ARGS); std::printf("%-20s: %s\n", PACKAGE_NAME " Version", FFMPEFS_VERSION); std::printf("%s", ffmpeg_libinfo().c_str()); #ifdef USE_LIBVCD std::printf("%-20s: %s\n", "Video CD Library", "enabled"); #endif // USE_LIBVCD #ifdef USE_LIBDVD std::printf("%-20s: %s\n", "DVD Library", "enabled"); #endif // USE_LIBDVD #ifdef USE_LIBBLURAY std::printf("%-20s: %s\n", "Blu-ray Library", BLURAY_VERSION_STRING); #endif // USE_LIBBLURAY fuse_opt_add_arg(outargs, "--version"); fuse_main(outargs->argc, outargs->argv, &ffmpegfs_ops, nullptr); exit(0); } case KEY_FFMPEG_CAPS: { std::printf("-------------------------------------------------------------------------------------------\n\n"); std::printf("%-20s: %s\n", PACKAGE_NAME " Version", FFMPEFS_VERSION); std::printf("%s", ffmpeg_libinfo().c_str()); std::printf("\nFFMpeg Capabilities\n\n"); show_caps(0); exit(0); } case KEY_DESTTYPE: { return get_desttype(arg, ffmpeg_format); } case KEY_AUDIOCODEC: { return get_audiocodec(arg, ¶ms.m_audio_codec); } case KEY_VIDEOCODEC: { return get_videocodec(arg, ¶ms.m_video_codec); } case KEY_AUTOCOPY: { return get_autocopy(arg, ¶ms.m_autocopy); } case KEY_RECODESAME: { return get_recodesame(arg, ¶ms.m_recodesame); } case KEY_PROFILE: { return get_profile(arg, ¶ms.m_profile); } case KEY_LEVEL: { return get_level(arg, ¶ms.m_level); } case KEY_AUDIO_BITRATE: { return get_bitrate(arg, ¶ms.m_audiobitrate); } case KEY_AUDIO_SAMPLERATE: { return get_samplerate(arg, ¶ms.m_audiosamplerate); } case KEY_AUDIO_CHANNELS: { return get_value(arg, ¶ms.m_audiochannels); } case KEY_AUDIO_SAMPLE_FMT: { return get_sampleformat(arg, ¶ms.m_sample_fmt); } case KEY_SCRIPTFILE: { return get_value(arg, ¶ms.m_scriptfile); } case KEY_SCRIPTSOURCE: { return get_value(arg, ¶ms.m_scriptsource); } case KEY_VIDEO_BITRATE: { return get_bitrate(arg, ¶ms.m_videobitrate); } case KEY_SEGMENT_DURATION: { return get_segment_duration(arg, ¶ms.m_segment_duration); } case KEY_MIN_SEEK_TIME_DIFF: { return get_seek_time_diff(arg, ¶ms.m_min_seek_time_diff); } case KEY_HWACCEL_ENCODER_API: { return get_hwaccel(arg, ¶ms.m_hwaccel_enc_API, ¶ms.m_hwaccel_enc_device_type); } case KEY_HWACCEL_ENCODER_DEVICE: { return get_value(arg, ¶ms.m_hwaccel_enc_device); } case KEY_HWACCEL_DECODER_API: { return get_hwaccel(arg, ¶ms.m_hwaccel_dec_API, ¶ms.m_hwaccel_dec_device_type); } case KEY_HWACCEL_DECODER_DEVICE: { return get_value(arg, ¶ms.m_hwaccel_dec_device); } case KEY_HWACCEL_DECODER_BLOCKED: { return get_hwaccel_dec_blocked(arg, ¶ms.m_hwaccel_dec_blocked); } case KEY_EXPIRY_TIME: { return get_time(arg, ¶ms.m_expiry_time); } case KEY_MAX_INACTIVE_SUSPEND_TIME: { return get_time(arg, ¶ms.m_max_inactive_suspend); } case KEY_MAX_INACTIVE_ABORT_TIME: { return get_time(arg, ¶ms.m_max_inactive_abort); } case KEY_PREBUFFER_TIME: { return get_time(arg, ¶ms.m_prebuffer_time); } case KEY_PREBUFFER_SIZE: { return get_size(arg, ¶ms.m_prebuffer_size); } case KEY_MAX_CACHE_SIZE: { return get_size(arg, ¶ms.m_max_cache_size); } case KEY_MIN_DISKSPACE_SIZE: { return get_size(arg, ¶ms.m_min_diskspace); } case KEY_CACHEPATH: { return get_value(arg, ¶ms.m_cachepath); } case KEY_CACHE_MAINTENANCE: { return get_time(arg, ¶ms.m_cache_maintenance); } case KEY_LOG_MAXLEVEL: { return get_value(arg, ¶ms.m_log_maxlevel); } case KEY_LOGFILE: { std::string logfile; int res = get_value(arg, &logfile); if (res) { return res; } expand_path(¶ms.m_logfile, logfile); sanitise_filepath(¶ms.m_logfile); return 0; } case KEY_INCLUDE_EXTENSIONS: { return get_value(arg, params.m_include_extensions.get()); } case KEY_HIDE_EXTENSIONS: { return get_value(arg, params.m_hide_extensions.get()); } } return 1; } /** * @brief Set default values. * @return Returns true if options are OK, false if option combination is invalid. */ static bool set_defaults() { if (ffmpeg_format[FORMAT::VIDEO].video_codec() == AV_CODEC_ID_PRORES) { if (params.m_level == PRORESLEVEL::NONE) { params.m_level = PRORESLEVEL::PRORES_HQ; } } return true; } /** * @brief Build list of available device types. * Builds a list of device types supported by the current * FFmpeg libary. */ static void build_device_type_list() { for (AVHWDeviceType device_type = AV_HWDEVICE_TYPE_NONE; (device_type = av_hwdevice_iterate_types(device_type)) != AV_HWDEVICE_TYPE_NONE;) { HWACCEL_MAP::iterator it = hwaccel_map.find(av_hwdevice_get_type_name(device_type)); if (it == hwaccel_map.end()) { continue; } it->second.m_hwaccel_device_type = device_type; } } /** * @brief Return a printable parameter value. * * Converts nullable C strings used by optional command-line parameters into a * stable text representation for diagnostic output. * * @param[in] value String value to print, or nullptr if the value is unset. * @return value if it is not nullptr, otherwise the literal string "NONE". */ const char *value_or_none(const char *value) { return (value != nullptr ? value : "NONE"); } /** * @brief Print currently selected parameters. */ static void print_params() { std::string cachepath; transcoder_cache_path(&cachepath); Logging::trace(nullptr, "********* " PACKAGE_NAME " Options *********"); Logging::trace(nullptr, "Base Path : %1", params.m_basepath.c_str()); Logging::trace(nullptr, "Mount Path : %1", params.m_mountpath.c_str()); Logging::trace(nullptr, "--------- Format ---------"); if (ffmpeg_format[FORMAT::AUDIO].filetype() != FILETYPE::UNKNOWN) { Logging::trace(nullptr, "Audio File Type : %1", ffmpeg_format[FORMAT::AUDIO].desttype().c_str()); if (ffmpeg_format[FORMAT::AUDIO].audio_codec() != AV_CODEC_ID_NONE) { Logging::trace(nullptr, "Audio Codec : %1 (%2)", get_codec_name(ffmpeg_format[FORMAT::AUDIO].audio_codec(), false), get_codec_name(ffmpeg_format[FORMAT::AUDIO].audio_codec(), true)); } Logging::trace(nullptr, "Video File Type : %1", ffmpeg_format[FORMAT::VIDEO].desttype().c_str()); if (ffmpeg_format[FORMAT::VIDEO].audio_codec() != AV_CODEC_ID_NONE) { Logging::trace(nullptr, "Audio Codec : %1 (%2)", get_codec_name(ffmpeg_format[FORMAT::VIDEO].audio_codec(), false), get_codec_name(ffmpeg_format[FORMAT::VIDEO].audio_codec(), true)); } if (ffmpeg_format[FORMAT::VIDEO].video_codec() != AV_CODEC_ID_NONE) { Logging::trace(nullptr, "Video Codec : %1 (%2)", get_codec_name(ffmpeg_format[FORMAT::VIDEO].video_codec(), false), get_codec_name(ffmpeg_format[FORMAT::VIDEO].video_codec(), true)); } } else { Logging::trace(nullptr, "File Type : %1", ffmpeg_format[FORMAT::VIDEO].desttype().c_str()); if (ffmpeg_format[FORMAT::VIDEO].audio_codec() != AV_CODEC_ID_NONE) { Logging::trace(nullptr, "Audio Codec : %1 (%2)", get_codec_name(ffmpeg_format[FORMAT::VIDEO].audio_codec(), false), get_codec_name(ffmpeg_format[FORMAT::VIDEO].audio_codec(), true)); } if (ffmpeg_format[FORMAT::VIDEO].video_codec() != AV_CODEC_ID_NONE) { Logging::trace(nullptr, "Video Codec : %1 (%2)", get_codec_name(ffmpeg_format[FORMAT::VIDEO].video_codec(), false), get_codec_name(ffmpeg_format[FORMAT::VIDEO].video_codec(), true)); } } Logging::trace(nullptr, "Smart Transcode : %1", params.smart_transcode() ? "yes" : "no"); Logging::trace(nullptr, "Auto Copy : %1", get_autocopy_text(params.m_autocopy).c_str()); Logging::trace(nullptr, "Recode to same fmt: %1", get_recodesame_text(params.m_recodesame).c_str()); Logging::trace(nullptr, "Profile : %1", get_profile_text(params.m_profile).c_str()); Logging::trace(nullptr, "Level : %1", get_level_text(params.m_level).c_str()); Logging::trace(nullptr, "Include Extensions: %1", implode(*params.m_include_extensions).c_str()); Logging::trace(nullptr, "Hide Extensions : %1", implode(*params.m_hide_extensions).c_str()); Logging::trace(nullptr, "--------- Audio ---------"); Logging::trace(nullptr, "Codecs : %1+%2", get_codec_name(ffmpeg_format[FORMAT::VIDEO].audio_codec(), true), get_codec_name(ffmpeg_format[FORMAT::AUDIO].audio_codec(), true)); Logging::trace(nullptr, "Bitrate : %1", format_bitrate(params.m_audiobitrate).c_str()); Logging::trace(nullptr, "Sample Rate : %1", format_samplerate(params.m_audiosamplerate).c_str()); Logging::trace(nullptr, "Max. Channels : %1", params.m_audiochannels); if (params.m_sample_fmt != SAMPLE_FMT::FMT_DONTCARE) { Logging::trace(nullptr, "Sample Format : %1", get_sampleformat_text(params.m_sample_fmt).c_str()); } Logging::trace(nullptr, "--------- Video ---------"); Logging::trace(nullptr, "Codec : %1", get_codec_name(ffmpeg_format[FORMAT::VIDEO].video_codec(), true)); Logging::trace(nullptr, "Bitrate : %1", format_bitrate(params.m_videobitrate).c_str()); Logging::trace(nullptr, "Dimension : width=%1 height=%2", format_number(params.m_videowidth).c_str(), format_number(params.m_videoheight).c_str()); Logging::trace(nullptr, "Deinterlace : %1", params.m_deinterlace ? "yes" : "no"); Logging::trace(nullptr, "--------- HLS Options ---------"); Logging::trace(nullptr, "Segment Duration : %1", format_time(static_cast(params.m_segment_duration / AV_TIME_BASE)).c_str()); Logging::trace(nullptr, "Seek Time Diff : %1", format_time(static_cast(params.m_min_seek_time_diff / AV_TIME_BASE)).c_str()); Logging::trace(nullptr, "---- Hardware Acceleration ----"); Logging::trace(nullptr, "Hardware Decoder:"); Logging::trace(nullptr, "API : %1", get_hwaccel_API_text(params.m_hwaccel_dec_API).c_str()); Logging::trace(nullptr, "Frame Buffering : %1", value_or_none(av_hwdevice_get_type_name(params.m_hwaccel_dec_device_type))); Logging::trace(nullptr, "Device : %1", params.m_hwaccel_dec_device.c_str()); Logging::trace(nullptr, "Hardware Encoder:"); Logging::trace(nullptr, "API : %1", get_hwaccel_API_text(params.m_hwaccel_enc_API).c_str()); Logging::trace(nullptr, "Frame Buffering : %1", value_or_none(av_hwdevice_get_type_name(params.m_hwaccel_enc_device_type))); Logging::trace(nullptr, "Device : %1", params.m_hwaccel_enc_device.c_str()); Logging::trace(nullptr, "--------- Subtitles ---------"); Logging::trace(nullptr, "No subtitles : %1", params.m_no_subtitles ? "yes" : "no"); Logging::trace(nullptr, "--------- Virtual Script ---------"); Logging::trace(nullptr, "Create script : %1", params.m_enablescript ? "yes" : "no"); Logging::trace(nullptr, "Script file name : %1", params.m_scriptfile.c_str()); Logging::trace(nullptr, "Input file : %1", params.m_scriptsource.c_str()); Logging::trace(nullptr, "--------- Logging ---------"); Logging::trace(nullptr, "Max. Log Level : %1", params.m_log_maxlevel.c_str()); Logging::trace(nullptr, "Log to stderr : %1", params.m_log_stderr ? "yes" : "no"); Logging::trace(nullptr, "Log to syslog : %1", params.m_log_syslog ? "yes" : "no"); Logging::trace(nullptr, "Logfile : %1", !params.m_logfile.empty() ? params.m_logfile.c_str() : "none"); Logging::trace(nullptr, "--------- Cache Settings ---------"); Logging::trace(nullptr, "Expiry Time : %1", format_time(params.m_expiry_time).c_str()); Logging::trace(nullptr, "Inactivity Suspend: %1", format_time(params.m_max_inactive_suspend).c_str()); Logging::trace(nullptr, "Inactivity Abort : %1", format_time(params.m_max_inactive_abort).c_str()); Logging::trace(nullptr, "Pre-buffer Time : %1", format_time(params.m_prebuffer_time).c_str()); Logging::trace(nullptr, "Pre-buffer Size : %1", format_size(params.m_prebuffer_size).c_str()); Logging::trace(nullptr, "Max. Cache Size : %1", format_size(params.m_max_cache_size).c_str()); Logging::trace(nullptr, "Min. Disk Space : %1", format_size(params.m_min_diskspace).c_str()); Logging::trace(nullptr, "Cache Path : %1", cachepath.c_str()); Logging::trace(nullptr, "Disable Cache : %1", params.m_disable_cache ? "yes" : "no"); Logging::trace(nullptr, "Maintenance Timer : %1", params.m_cache_maintenance ? format_time(params.m_cache_maintenance).c_str() : "inactive"); Logging::trace(nullptr, "Clear Cache : %1", params.m_clear_cache ? "yes" : "no"); Logging::trace(nullptr, "--------- Various Options ---------"); Logging::trace(nullptr, "Remove Album Arts : %1", params.m_noalbumarts ? "yes" : "no"); Logging::trace(nullptr, "Max. Threads : %1", format_number(params.m_max_threads).c_str()); Logging::trace(nullptr, "Decoding Errors : %1", params.m_decoding_errors ? "break transcode" : "ignore"); Logging::trace(nullptr, "Min. DVD Chapter : %1", format_duration(params.m_min_dvd_chapter_duration * AV_TIME_BASE).c_str()); Logging::trace(nullptr, "Old Name Scheme : %1", params.m_oldnamescheme ? "yes" : "no"); Logging::trace(nullptr, "--------- Experimental Options ---------"); Logging::trace(nullptr, "Windows 10 Fix : %1", params.m_win_smb_fix ? "SMB Lockup Fix Active" : "inactive"); } /** * @brief Custom FFmpeg log function. Used with av_log_set_callback(). * @param[in] ptr - See av_log_set_callback() in FFmpeg API. * @param[in] level - See av_log_set_callback() in FFmpeg API. * @param[in] fmt - See av_log_set_callback() in FFmpeg API. * @param[in] vl - See av_log_set_callback() in FFmpeg API. */ static void ffmpeg_log(void *ptr, int level, const char *fmt, va_list vl) { Logging::LOGLEVEL ffmpegfs_level; // Map log level // AV_LOG_PANIC 0 // AV_LOG_FATAL 8 // AV_LOG_ERROR 16 if (level <= AV_LOG_ERROR) { ffmpegfs_level = LOGERROR; } // AV_LOG_WARNING 24 else if (level <= AV_LOG_WARNING) { ffmpegfs_level = LOGWARN; } #ifdef AV_LOG_TRACE // AV_LOG_INFO 32 //else if (level <= AV_LOG_INFO) //{ // ffmpegfs_level = LOGINFO; //} // AV_LOG_VERBOSE 40 // AV_LOG_DEBUG 48 else if (level < AV_LOG_DEBUG) { ffmpegfs_level = LOGDEBUG; } // AV_LOG_TRACE 56 else // if (level <= AV_LOG_TRACE) { ffmpegfs_level = LOGTRACE; } #else // AV_LOG_INFO 32 else // if (level <= AV_LOG_INFO) { ffmpegfs_level = DEBUG; } #endif if (!Logging::show(ffmpegfs_level)) { return; } va_list vl2; static int print_prefix = 1; #if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 23, 0)) char * line; int line_size; std::string category; if (ptr != nullptr) { AVClass* avc = *(AVClass **)ptr; switch (avc->category) { case AV_CLASS_CATEGORY_NA: { break; } case AV_CLASS_CATEGORY_INPUT: { category = "INPUT "; break; } case AV_CLASS_CATEGORY_OUTPUT: { category = "OUTPUT "; break; } case AV_CLASS_CATEGORY_MUXER: { category = "MUXER "; break; } case AV_CLASS_CATEGORY_DEMUXER: { category = "DEMUXER "; break; } case AV_CLASS_CATEGORY_ENCODER: { category = "ENCODER "; break; } case AV_CLASS_CATEGORY_DECODER: { category = "DECODER "; break; } case AV_CLASS_CATEGORY_FILTER: { category = "FILTER "; break; } case AV_CLASS_CATEGORY_BITSTREAM_FILTER: { category = "BITFILT "; break; } case AV_CLASS_CATEGORY_SWSCALER: { category = "SWSCALE "; break; } case AV_CLASS_CATEGORY_SWRESAMPLER: { category = "SWRESAM "; break; } default: { strsprintf(&category, "CAT %3i ", static_cast(avc->category)); break; } } } va_copy(vl2, vl); av_log_default_callback(ptr, level, fmt, vl); line_size = av_log_format_line2(ptr, level, fmt, vl2, nullptr, 0, &print_prefix); if (line_size < 0) { va_end(vl2); return; } line = static_cast(av_malloc(static_cast(line_size))); if (line == nullptr) { return; } av_log_format_line2(ptr, level, fmt, vl2, line, line_size, &print_prefix); va_end(vl2); #else char line[1024]; va_copy(vl2, vl); av_log_default_callback(ptr, level, fmt, vl); av_log_format_line(ptr, level, fmt, vl2, line, sizeof(line), &print_prefix); va_end(vl2); #endif Logging::log_with_level(ffmpegfs_level, "", category + line); #if (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 23, 0)) av_free(line); #endif } /** * @brief Inititalise logging facility * @param[in] logfile - Name of log file if file writing is selected. * @param[in] max_level - Maximum level to log. * @param[in] to_stderr - If true, log to stderr. * @param[in] to_syslog - If true, log to syslog. * @return Returns true on success; false on error. */ static bool init_logging(const std::string &logfile, const std::string & max_level, bool to_stderr, bool to_syslog) { static const std::map log_level_map = { { "ERROR", LOGERROR }, { "WARNING", LOGWARN }, { "INFO", LOGINFO }, { "DEBUG", LOGDEBUG }, { "TRACE", LOGTRACE }, }; std::map::const_iterator it = log_level_map.find(max_level); if (it == log_level_map.cend()) { std::fprintf(stderr, "Invalid logging level string: %s\n", max_level.c_str()); return false; } return Logging::init_logging(logfile, it->second, to_stderr, to_syslog); } /** * @brief Main program entry point. * @param[in] argc - Number of command line arguments. * @param[in] argv - Command line argument array. * @return Return value will be the errorlevel of the executable. * Returns 0 on success, 1 on error. */ int main(int argc, char *argv[]) { int ret; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); // Check if run from other process group like mount and if so, inhibit startup message if (getppid() == getpgid(0)) { std::printf("%s V%s\n", PACKAGE_NAME, FFMPEFS_VERSION); std::printf("Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de)\n" "David Collett (2006-2008) and K. Henriksson (2008-2012)\n\n"); } // Check if run under Docker docker_client = detect_docker(); init_fuse_ops(); // Redirect FFmpeg logs av_log_set_callback(ffmpeg_log); // Set default params.m_max_threads = static_cast(get_nprocs() * 16); // Build list of supported device types build_device_type_list(); if (fuse_opt_parse(&args, ¶ms, ffmpegfs_opts, ffmpegfs_opt_proc)) { std::fprintf(stderr, "\nError parsing command line options.\n\n"); //usage(argv[0]); return 1; } // Expand cache path if (!params.m_cachepath.empty()) { expand_path(¶ms.m_cachepath, params.m_cachepath); append_sep(¶ms.m_cachepath); } // Log to the screen, and enable debug messages, if debug is enabled. if (params.m_debug) { params.m_log_stderr = 1; params.m_log_maxlevel = "DEBUG"; av_log_set_level(AV_LOG_INFO); // Do not use AV_LOG_DEBUG; AV_LOG_INFO is chatty enough } else { av_log_set_level(AV_LOG_QUIET); } if (!init_logging(params.m_logfile, params.m_log_maxlevel, params.m_log_stderr ? true : false, params.m_log_syslog ? true : false)) { std::fprintf(stderr, "ERROR: Failed to initialise logging module.\n"); std::fprintf(stderr, "Maybe log file couldn't be opened for writing?\n\n"); return 1; } if (params.m_prune_cache) { if (args.argc > 1) { std::fprintf(stderr, "INVALID PARAMETER: Invalid additional parameters for --prune_cache:\n"); for (int n = 1; n < args.argc; n++) { std::fprintf(stderr, "Invalid: '%s'\n", args.argv[n]); } return 1; } // Prune cache and exit if (!transcoder_init()) { return 1; } transcoder_cache_maintenance(); return 0; } if (params.m_basepath.empty()) { std::fprintf(stderr, "INVALID PARAMETER: No valid basepath specified.\n\n"); return 1; } if (params.m_basepath.front() != '/') { std::fprintf(stderr, "INVALID PARAMETER: basepath must be an absolute path.\n\n"); return 1; } struct stat stbuf; if (stat(params.m_basepath.c_str(), &stbuf) != 0 || !S_ISDIR(stbuf.st_mode)) { std::fprintf(stderr, "INVALID PARAMETER: basepath is not a valid directory: %s\n\n", params.m_basepath.c_str()); return 1; } if (params.m_mountpath.empty()) { std::fprintf(stderr, "INVALID PARAMETER: No valid mountpath specified.\n\n"); return 1; } if (params.m_mountpath.front() != '/') { std::fprintf(stderr, "INVALID PARAMETER: mountpath must be an absolute path.\n\n"); return 1; } if (stat(params.m_mountpath.c_str(), &stbuf) != 0 || !S_ISDIR(stbuf.st_mode)) { std::fprintf(stderr, "INVALID PARAMETER: mountpath is not a valid directory: %s\n\n", params.m_mountpath.c_str()); return 1; } // Check if sample format is supported for (const FFmpegfs_Format & fmt : ffmpeg_format) { if (fmt.filetype() != FILETYPE::UNKNOWN && !fmt.is_sample_fmt_supported()) { std::fprintf(stderr, "INVALID PARAMETER: %s does not support the sample format %s\n\n", fmt.desttype().c_str(), get_sampleformat_text(params.m_sample_fmt).c_str()); std::fprintf(stderr, "Supported formats: %s\n\n", fmt.sample_fmt_list().c_str()); return 1; } } // Check if audio or video codec is supported for (const FFmpegfs_Format & fmt : ffmpeg_format) { if (fmt.filetype() != FILETYPE::UNKNOWN) { if (params.m_audio_codec != AV_CODEC_ID_NONE && !fmt.is_audio_codec_supported(params.m_audio_codec)) { std::fprintf(stderr, "INVALID PARAMETER: %s does not support audio codec %s\n\n", fmt.desttype().c_str(), get_audio_codec_text(params.m_audio_codec).c_str()); std::fprintf(stderr, "Supported formats: %s\n\n", fmt.audio_codec_list().c_str()); return 1; } if (params.m_video_codec != AV_CODEC_ID_NONE && !fmt.is_video_codec_supported(params.m_video_codec)) { std::fprintf(stderr, "INVALID PARAMETER: %s does not support video codec %s\n\n", fmt.desttype().c_str(), get_video_codec_text(params.m_video_codec).c_str()); std::fprintf(stderr, "Supported formats: %s\n\n", fmt.video_codec_list().c_str()); return 1; } } } if (!set_defaults()) { return 1; } if (!transcoder_init()) { return 1; } print_params(); if (params.m_clear_cache) { // Prune cache and exit if (!transcoder_cache_clear()) { return 1; } } // start FUSE ret = fuse_main(args.argc, args.argv, &ffmpegfs_ops, nullptr); fuse_opt_free_args(&args); return ret; } ffmpegfs-2.50/src/id3v1tag.h0000664000175000017500000000414415177713600011314 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file id3v1tag.h * @brief %ID3v1 tag structure * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef ID3V1TAG_H #define ID3V1TAG_H #pragma once #include /** @brief %ID3 version 1 tag */ struct ID3v1 { std::array m_tag; /**< @brief Contains "TAG" */ std::array m_title; /**< @brief Title of sound track */ std::array m_artist; /**< @brief Artist name */ std::array m_album; /**< @brief Album name */ std::array m_year; /**< @brief Year of publishing */ std::array m_comment; /**< @brief Any user comments */ char m_padding; /**< @brief Padding byte, must be '\0' */ char m_title_no; /**< @brief Title number */ char m_genre; /**< @brief Type of music */ }; static_assert(sizeof(ID3v1) == 128); extern void init_id3v1(ID3v1 *id3v1); /**< @brief Initialise ID3v1 tag */ #define ID3V1_TAG_LENGTH sizeof(ID3v1) /**< @brief Fixed 128 bytes */ #endif // ID3V1TAG_H ffmpegfs-2.50/src/dvdio.h0000664000175000017500000002316415177713600011002 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file dvdio.h * @brief DVD I/O * * This is only available if built with -DUSE_LIBDVD parameter. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef DVDIO_H #define DVDIO_H #pragma once #ifdef USE_LIBDVD #include "fileio.h" #include #include /** @brief DVD I/O class */ class DvdIO : public FileIO { /** * @brief Type of a DSI block. * * This is used to identify a DSI block on a DVD: Until end of a chapter * we simply continue. At the end of chapters we may stop continue to * the end of a title */ enum class DSITYPE { CONTINUE, /**< @brief Chapter continues */ EOF_CHAPTER, /**< @brief End of chapter */ EOF_TITLE /**< @brief End of title */ }; public: /** * @brief Create #DvdIO object */ explicit DvdIO(); /** * @brief Free #DvdIO object */ virtual ~DvdIO(); /** * @brief Get type of the virtual file * @return Returns the type of the virtual file. */ virtual VIRTUALTYPE type() const override; /** * @brief Get the ideal buffer size. * @return Return the ideal buffer size. */ virtual size_t bufsize() const override; /** * @brief Open a virtual file * @param[in] virtualfile - LPCVIRTUALFILE of file to open * @return Upon successful completion, #openio() returns 0. @n * On error, an nonzero value is returned and errno is set to indicate the error. */ virtual int openio(LPVIRTUALFILE virtualfile) override; /** * @brief Read data from file * @param[out] data - buffer to store read bytes in. Must be large enough to hold up to size bytes. * Special case: If set to nullptr as many bytes as possible are "read" (and discarded). Can be used * to determine the net file size of the virtual stream. * @param[in] size - number of bytes to read * @return Upon successful completion, #readio() returns the number of bytes read. @n * This may be less than size. @n * On error, the value 0 is returned and errno is set to indicate the error. @n * If at end of file, 0 may be returned by errno not set. error() will return 0 if at EOF. */ virtual size_t readio(void *data, size_t size) override; /** * @brief Get last error. * @return errno value of last error. */ virtual int error() const override; /** * @brief Get the duration of the file, in AV_TIME_BASE fractional seconds. * @return Returns the duration of the file, in AV_TIME_BASE fractional seconds. */ virtual int64_t duration() const override; /** * @brief Get the file size. * @return Returns the file size. */ virtual size_t size() const override; /** * @brief Get current read position. * @return Gets the current read position. */ virtual size_t tell() const override; /** * @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. @n * On error, the value -1 is returned and errno is set to indicate the error. */ virtual int seek(int64_t offset, int whence) override; /** * @brief Check if at end of file. * @return Returns true if at end of file. */ virtual bool eof() const override; /** * @brief Close virtual file. */ virtual void closeio() override; private: /** * @brief Close virtual file. * Non-virtual version to be safely called from constructor/destructor */ void pvt_close(); /** * @brief Do a rough check if this is really a navigation packet. * @param[in] buffer - Buffer with data. * @return true if the pack is a NAV pack. */ bool is_nav_pack(const unsigned char *buffer) const; /** * @brief return the size of the next packet * @param[in] p - Buffer with PES data. * @param[in] peek - How many bytes to peek. * @param[in] size - Size of the next packet * @return Size ofr ID invalid. */ bool get_packet_size(const uint8_t *p, size_t peek, size_t *size) const; /** * @brief return the id of a Packetized Elementary Stream (PES) (should be valid) * @param[in] buffer - Buffer with PES data. * @param[in] size - Size of buffer. * @return The id of a PES (should be valid) */ int get_pes_id(const uint8_t *buffer, size_t size) const; /** * @brief Extract only the interesting portion of the VOB input stream * @param[out] out - Stream stripped from unnessessary data * @param[in] in - Input raw stream * @param[in] len - Length of input raw stream * @return Size of restulting data block. */ size_t demux_pes(uint8_t *out, const uint8_t *in, size_t len) const; /** * @brief Handle DSI (Data Search Information) packet * @param[out] _dsi_pack - buffer with DSI packet. * @param[out] cur_output_size - Net size of packet. * @param[out] next_block - Address of next VOBU (Video Object Unit). * @param[out] data - Data extracted from DSI packet. * @return */ DSITYPE handle_DSI(void *_dsi_pack, size_t *cur_output_size, unsigned int * next_block, uint8_t *data); /** * @brief Goto next DVD cell */ void next_cell(); /** * @brief Rewind to start of stream */ void rewind(); protected: dvd_reader_t * m_dvd; /**< @brief DVD reader handle */ dvd_file_t * m_dvd_title; /**< @brief DVD title handle */ ifo_handle_t * m_vmg_file; /**< @brief DVD video manager handle */ ifo_handle_t * m_vts_file; /**< @brief DVD video title stream handle */ pgc_t * m_cur_pgc; /**< @brief Current program chain */ int m_start_cell; /**< @brief Start cell */ int m_end_cell; /**< @brief End cell (of title) */ int m_cur_cell; /**< @brief Current cell */ int m_next_cell; /**< @brief Next cell to be processed */ bool m_goto_next_cell; /**< @brief If logc needs to go to next cell before next read */ unsigned int m_cur_block; /**< @brief Current processing block */ bool m_is_eof; /**< @brief true if at "end of file", i.e, end of chapter or title */ int m_errno; /**< @brief errno of last operation */ size_t m_rest_size; /**< @brief Rest bytes in buffer */ size_t m_rest_pos; /**< @brief Position in buffer */ size_t m_cur_pos; /**< @brief Current position in virtual file */ bool m_full_title; /**< @brief If true, ignore m_chapter_no and provide full track */ int m_title_idx; /**< @brief Track index (track number - 1) */ int m_chapter_idx; /**< @brief Chapter index (chapter number - 1) */ int m_angle_idx; /**< @brief Selected angle index (angle number -1) */ std::array m_data; /**< @brief Buffer for readio() data */ std::array m_buffer; /**< @brief Buffer for data extracted from VOB file */ int64_t m_duration; /**< @brief Track/chapter duration, in AV_TIME_BASE fractional seconds. */ size_t m_size; /**< @brief Size of virtual file */ }; #endif // USE_LIBDVD #endif // DVDIO_H ffmpegfs-2.50/src/vcdio.h0000664000175000017500000001241215177713600010773 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file vcdio.h * @brief Video CD and Super Video CD I/O * * This is only available if built with -DUSE_LIBVCD parameter. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef VCDIO_H #define VCDIO_H #pragma once #ifdef USE_LIBVCD #include "fileio.h" /** @brief Video CD and Super Video CD I/O class */ class VcdIO : public FileIO { public: /** * @brief Create #VcdIO object */ explicit VcdIO(); /** * @brief Free #VcdIO object * * Close file pointers */ virtual ~VcdIO(); /** * @brief Get type of the virtual file * @return Returns the type of the virtual file. */ virtual VIRTUALTYPE type() const override; /** * @brief Get the ideal buffer size. * @return Return the ideal buffer size. */ virtual size_t bufsize() const override; /** * @brief Open a virtual file * @param[in] virtualfile - LPCVIRTUALFILE of file to open * @return Upon successful completion, #openio() returns 0. @n * On error, an nonzero value is returned and errno is set to indicate the error. */ virtual int openio(LPVIRTUALFILE virtualfile) override; /** * @brief Read data from file * @param[out] data - buffer to store read bytes in. Must be large enough to hold up to size bytes. * @param[in] size - number of bytes to read * @return Upon successful completion, #readio() returns the number of bytes read. @n * This may be less than size. @n * On error, the value 0 is returned and errno is set to indicate the error. @n * If at end of file, 0 may be returned by errno not set. error() will return 0 if at EOF. */ virtual size_t readio(void *data, size_t size) override; /** * @brief Get last error. * @return errno value of last error. */ virtual int error() const override; /** * @brief Get the duration of the file, in AV_TIME_BASE fractional seconds. * @return Could work for VCD, but always returns AV_NOPTS_VALUE. * @todo Implement duration of video CD chapters */ virtual int64_t duration() const override; /** * @brief Get the file size. * @return Returns the file size. */ virtual size_t size() const override; /** * @brief Get current read position. * @return Gets the current read position. */ virtual size_t tell() const override; /** * @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. @n * On error, the value -1 is returned and errno is set to indicate the error. */ virtual int seek(int64_t offset, int whence) override; /** * @brief Check if at end of file. * @return Returns true if at end of file. */ virtual bool eof() const override; /** * @brief Close virtual file. */ virtual void closeio() override; private: /** * @brief Close virtual file. * Non-virtual version to be safely called from constructor/destructor */ void pvt_close(); protected: FILE * m_fpi; /**< @brief File pointer to source media */ bool m_full_title; /**< @brief If true, ignore m_chapter_no and provide full track */ int m_track_no; /**< @brief Track number (1..) */ int m_chapter_no; /**< @brief Chapter number (1..) */ uint64_t m_start_pos; /**< @brief Start offset in bytes */ uint64_t m_end_pos; /**< @brief End offset in bytes (not including this byte) */ }; #endif // USE_LIBVCD #endif // VCDIO_H ffmpegfs-2.50/src/buffer.h0000664000175000017500000005536115201047617011146 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file buffer.h * @brief Buffer class * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef BUFFER_H #define BUFFER_H #pragma once #include "fileio.h" #include #include #include #define CACHE_CHECK_BIT(mask, var) ((mask) == (mask & (var))) /**< @brief Check bit in bitmask */ #define CACHE_CLOSE_NOOPT 0x00000000 /**< @brief Dummy, do nothing special */ #define CACHE_CLOSE_FREE 0x00000001 /**< @brief Free memory for cache entry */ #define CACHE_CLOSE_DELETE (0x00000002 | CACHE_CLOSE_FREE) /**< @brief Delete cache entry, will unlink cached file! Implies CACHE_CLOSE_FREE. */ #define CACHE_FLAG_RO 0x00000001 /**< @brief Mark cache file read-only */ #define CACHE_FLAG_RW 0x00000002 /**< @brief Mark cache file writeable, implies read permissions */ /** * @brief The #Buffer class */ class Buffer : public FileIO { /** * @brief PREALLOC_FACTOR - Number of elements allocated on reallocate calls * Number of elements allocated on reallocate calls. The buffer keeps track of the * typical buffer write size. To prevent repeated reallocations, reallocate() * attempts to set aside enough memory for PREALLOC FACTOR average elements whenever * it is invoked. */ static constexpr int PREALLOC_FACTOR = 5; public: /** * @brief Structure to hold current cache state */ typedef struct _tagCACHEINFO { public: _tagCACHEINFO() : m_fd(-1) , m_buffer(nullptr) , m_buffer_pos(0) , m_buffer_watermark(0) , m_buffer_size(0) , m_seg_finished(false) , m_fd_idx(-1) , m_buffer_idx(nullptr) , m_buffer_size_idx(0) , m_flags(0) , m_buffer_write_size(0) , m_buffer_writes(0) { } /** * @brief Reset buffer pointers */ void reset() { m_fd = -1; m_buffer = nullptr; m_buffer_pos = 0; m_buffer_watermark = 0; m_buffer_size = 0; m_buffer_write_size = 0; m_buffer_writes = 0; } // Main cache std::string m_cachefile; /**< @brief Cache file name */ int m_fd; /**< @brief File handle for buffer */ uint8_t * m_buffer; /**< @brief Pointer to buffer memory */ size_t m_buffer_pos; /**< @brief Read/write position */ size_t m_buffer_watermark; /**< @brief Number of bytes in buffer */ size_t m_buffer_size; /**< @brief Current buffer size */ bool m_seg_finished; /**< @brief True if segment completely decoded */ // Index for frame sets std::string m_cachefile_idx; /**< @brief Index file name */ int m_fd_idx; /**< @brief File handle for index */ uint8_t * m_buffer_idx; /**< @brief Pointer to index memory */ size_t m_buffer_size_idx; /**< @brief Size of index buffer */ // Flags uint32_t m_flags; /**< @brief CACHE_FLAG_* options */ // Statistics size_t m_buffer_write_size; /**< @brief Sum of bytes written to the buffer */ unsigned int m_buffer_writes; /**< @brief Total number of writes to the buffer */ } CACHEINFO, *LPCACHEINFO; /**< @brief Pointer version of CACHEINFO */ typedef CACHEINFO const * LPCCACHEINFO; /**< @brief Pointer to const version of CACHEINFO */ public: /** * @brief Create #Buffer object */ explicit Buffer(); /** * @brief Free #Buffer object * * Release memory and close files */ virtual ~Buffer(); /** * @brief Get type of this virtual file. * @return Returns VIRTUALTYPE::BUFFER. */ virtual VIRTUALTYPE type() const override; /** * @brief Initialise cache * @param[in] erase_cache - If true, delete the old file before opening. * @return Returns true on success; false on error. */ bool init(bool erase_cache); /** * @brief Set the current segment. * @param[in] segment_no - [1..n] HLS segment file number. * @param[in] size - Estimated size of segment. Should be large enough to hold the resulting size. * @return Returns true on success; if segment_no is 0 or greater, then segment_count() returns false and sets errno to EINVALID. */ bool set_segment(uint32_t segment_no, size_t size); /** * @brief Get segment count. * @return Number of segments. */ uint32_t segment_count(); /** * @brief Get the currently selected segment. * @return current segment number [1..n], or 0 if none is chosen. */ uint32_t current_segment_no(); /** * @brief Check if segment exists. * @param[in] segment_no - [1..n] HLS segment file number. * @return Returns true if it exists, or false if it still has to be decoded. */ bool segment_exists(uint32_t segment_no); /** * @brief Check whether the physical cache file for the requested segment exists and contains data. * * This checks the cache file on disk, not the logical in-memory/database * cache state. It is used by the read paths to detect stale cache index * entries after manual cache file deletion or aborted writes. * * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current/single cache file. * @return Returns true if the cache file exists, is a regular file, and has a non-zero size. */ bool cachefile_valid(uint32_t segment_no); /** * @brief Invalidate the requested cache segment/file. * * Clears the logical finished state, drops any open mapping for the cache * file, removes the physical cache file, and resets the buffered size * counters. For frame-set caches, invalidating segment 0 also clears the * frame index because offsets into a missing cache file are no longer * trustworthy. * * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current/single cache file. * @return Returns true if the segment was invalidated successfully. */ bool invalidate_segment(uint32_t segment_no = 0); /** * @brief Release cache buffer. * @param[in] flags - One of the CACHE_CLOSE_* flags. * @return Returns true on success; false on error. */ bool release(int flags = CACHE_CLOSE_NOOPT); /** * @brief Size of this buffer. * @return Not applicable, returns 0. */ virtual size_t bufsize() const override; /** @brief Open a virtual file * @param[in] virtualfile - LPCVIRTUALFILE of file to open * @return Upon successful completion, #openio() returns 0. * On error, an nonzero value is returned and errno is set to indicate the error. */ virtual int openio(LPVIRTUALFILE virtualfile) override; /** * @brief Not implemented. * @param[out] data - unused * @param[in] size - unused * @return Always returns 0 and errno is EPERM. */ virtual size_t readio(void *data, size_t size) override; /** * @brief Write image data for the frame number into the buffer. * @param[out] data - Buffer to read data in. * @param[in] frame_no - Number of the frame to write. * @return Upon successful completion, #readio() returns the number of bytes read. @n * This may be less than size. @n * On error, the value 0 is returned and errno is set to indicate the error. @n * If at the end of the file, 0 may be returned by errno not set. error() will return 0 if at EOF. @n * If the image frame is not yet read, the function also returns 0 and errno is EAGAIN. */ size_t read_frame(std::vector * data, uint32_t frame_no); /** * @brief Get last error. * @return errno value of last error. */ virtual int error() const override; /** @brief Get the duration of the file, in AV_TIME_BASE fractional seconds. * @return Not applicable to buffer, always returns AV_NOPTS_VALUE. */ virtual int64_t duration() const override; /** * @brief Get the value of the internal buffer size pointer. * @return Returns the value of the internal buffer size pointer. */ virtual size_t size() const override; /** * @brief Get the value of the internal buffer size pointer. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns the value of the internal buffer size pointer. */ virtual size_t size(uint32_t segment_no) const; /** * @brief Get the value of the internal read position pointer. * @return Returns the value of the internal read position pointer. */ virtual size_t tell() const override; /** * @brief Get the value of the internal read position pointer. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns the value of the internal read position pointer. */ virtual size_t tell(uint32_t segment_no) const; /** @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * May block for a long time if the buffer has not been filled to the requested offset. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. */ virtual int seek(int64_t offset, int whence) override; /** @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * May block for a long time if the buffer has not been filled to the requested offset. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. */ virtual int seek(int64_t offset, int whence, uint32_t segment_no); /** * @brief Check if at end of file. * @return Returns true if at end of buffer. */ virtual bool eof() const override; /** * @brief Check if at end of file. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns true if at end of buffer. */ virtual bool eof(uint32_t segment_no) const; /** * @brief Close buffer. */ virtual void closeio() override; /** * @brief Write data to the current position in the buffer. The position pointer will be updated. * @param[in] data - Buffer with data to write. * @param[in] length - Length of buffer to write. * @return Returns the bytes written to the buffer. If less than length, this indicates an error. Consult errno for details. */ size_t writeio(const uint8_t* data, size_t length); /** * @brief Write image data for the frame number into the buffer. * @param[in] data - Buffer with data to write. * @param[in] length - Length of buffer to write. * @param[in] frame_no - Number of the frame to write. * @return Returns the bytes written to the buffer. If less than length, this indicates an error. Consult errno for details. */ size_t write_frame(const uint8_t* data, size_t length, uint32_t frame_no); /** * @brief Flush buffer to disk * @return Returns true on success; false on error. Check errno for details. */ bool flush(); /** * @brief Clear (delete) buffer. * @return Returns true on success; false on error. Check errno for details. */ bool clear(); /** * @brief Reserve memory without changing size to reduce re-allocations. * @param[in] size - Size of buffer to reserve. * @return Returns true on success; false on error. */ bool reserve(size_t size); /** @brief Return the current watermark of the file while transcoding. * * While transcoding, this value reflects the current size of the transcoded file. * This is the maximum byte offset until the file can be read so far. * * @param[in] segment_no - If > 0 returns watermark for specific segment. * If 0, returns watermark for current write segment. * @return Returns the current watermark. */ size_t buffer_watermark(uint32_t segment_no = 0) const; /** * @brief Copy buffered data into output buffer. * @param[in] out_data - Buffer to copy data to. * @param[in] offset - Offset in buffer to copy data from. * @param[in] segment_no - HLS segment file number [1..n] or 0 for current segment. * @return Returns true on success; false on error. */ bool copy(std::vector * out_data, size_t offset, uint32_t segment_no = 0); /** * @brief Copy buffered data into output buffer. * @param[in] out_data - Buffer to copy data to. * @param[in] offset - Offset in buffer to copy data from. * @param[in] bufsize - Size of out_data buffer. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns true on success; false on error. */ bool copy(uint8_t* out_data, size_t offset, size_t bufsize, uint32_t segment_no = 0); /** * @brief Get cache filename. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns cache filename. */ const std::string & cachefile(uint32_t segment_no) const; /** * @brief Make up a cache file name, including the full path. * @param[out] cachefile - Name of cache file. * @param[in] filename - Source file name. * @param[in] fileext - File extension (MP4, WEBM etc.). * @param[in] is_idx - If true, create an index file; otherwise, create a cache. * @return Returns the name of the cache/index file. */ static const std::string & make_cachefile_name(std::string *cachefile, const std::string & filename, const std::string &fileext, bool is_idx); /** * @brief Remove (unlink) the file. * @param[in] filename - Name of the file to remove. * @return Returns true on success; false on error. */ static bool remove_file(const std::string & filename); /** * @brief Check if we have the requested frame number. Works only when processing a frame set. * @param[in] frame_no - 1...frames * @return Returns true if the frame is already in the cache, false if not. */ bool have_frame(uint32_t frame_no); /** * @brief Complete the segment decoding. */ void finished_segment(); /** * @brief Return true if transcoding of the segment is finished. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns true if finished, false if not. */ bool is_segment_finished(uint32_t segment_no) const; /** * @brief Open the cache file if not already open. * @param[in] segment_no - [0..n-1] Index of segment file number. * @param[in] flags - CACHE_FLAG_* options. * @param[in] defaultsize - If nonzero, after opening, the file will be resized to this value. Works on writeable files only. * @return Returns true if the operation was successful or the file was already open; false otherwise. */ bool open_file(uint32_t segment_no, uint32_t flags, size_t defaultsize = 0); /** * @brief If it hasn't already been done, close the cache file. * @param[in] segment_no - [0..n-1] Index of segment file number. * @param[in] flags - CACHE_FLAG_* options * @return Returns true if the operation was successful or the file was already closed; false otherwise. */ bool close_file(uint32_t segment_no, uint32_t flags); protected: /** * @brief Remove the cachefile. * @param[in] segment_no - [1..n] HLS segment file number or 0 for the current segment. * @return Returns true on success; false on error. */ bool remove_cachefile(uint32_t segment_no = 0) const; /** * @brief Check if the cache file is open. * @return Returns true if the cache file is open; false if not. */ bool is_open(); private: /** * @brief Prepare for the writing operation. * * Ensure the buffer has sufficient space for the quantity of data and return * a pointer to where the data may be written. The position pointer should be * updated afterward with increment_pos(). * @param[in] length - Buffer length to prepare. * @return Returns a pointer to the memory to write to. */ uint8_t* write_prepare(size_t length); /** * @brief Increment buffer position. * * Increment the location of the internal pointer. This cannot fail and so * returns void. It does not ensure the position is valid memory. That is * done by the write_prepare methods via reallocate. * @param[in] increment - Increment size. */ void increment_pos(size_t increment); /** * @brief Reallocate the buffer to a new size. * * Ensure the allocation has at least the size of bytes available. * If not, reallocate memory to make more available. Fill the newly * allocated memory with zeroes. * @param[in] newsize - New buffer size. * @return Returns true on success; false on error. */ bool reallocate(size_t newsize); /** * @brief Map memory to a file. * @param[in] filename - Name of the cache file to open. * @param[out] fd - The file descriptor of the open cache file. * @param[out] p - Memory pointer to the cache file. * @param[out] filesize - Actual size of the cache file after this call. * @param[inout] isdefaultsize -@n * In: If false, the file size will be the size of the existing file, returning the size in filesize. If the file does not exist, it will be sized to defaultsize. * If true, the defaultsize will be used in any case, resizing an existing file if necessary.@n * Out: true if the file size was set to default. * @param[out] defaultsize - The default size of the file if it does not exist. This parameter can be zero, in which case the size will be set to the system's page size. * @param[out] truncate - If true, when the file is opened, it is truncated. * @return Returns true if successful and fd, p, filesize, and isdefaultsize are filled in, or false on error. */ bool map_file(const std::string & filename, volatile int *fd, uint8_t **p, size_t *filesize, bool *isdefaultsize, size_t defaultsize, bool truncate) const; /** * @brief Unmap memory from the file. * @param[in] filename - Name of cache file to unmap. * @param[in] fd - The file descriptor of the open cache file. * @param[in] p - Memory pointer to the cache file. * @param[in] len - Length of the allocated block. * @param[in] filesize - Actual size of the cache file. * @return Returns true on success; false on error. */ bool unmap_file(const std::string & filename, volatile int *fd, uint8_t **p, size_t len, size_t *filesize) const; /** * @brief Get cache information. * @param[in] segment_no - HLS segment file number [1..n] or 0 for the current segment. * @return Pointer to CACHEINFO object, or nullptr on error. */ LPCACHEINFO cacheinfo(uint32_t segment_no); /** * @brief Get cache information. * @param[in] segment_no - HLS segment file number [1..n] or 0 for the current segment. * @return Pointer to CACHEINFO object, or nullptr on error. */ LPCCACHEINFO const_cacheinfo(uint32_t segment_no) const; private: std::recursive_mutex m_mutex; /**< @brief Access mutex */ LPCACHEINFO m_cur_ci; /**< @brief Convenience pointer to current write segment */ uint32_t m_cur_open; /**< @brief Number of open files */ std::vector m_ci; /**< @brief Cache info */ }; #endif ffmpegfs-2.50/src/cache.cc0000664000175000017500000013730615177713600011102 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cache.cc * @brief Cache class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "cache.h" #include "cache_entry.h" #include "ffmpegfs.h" #include "logging.h" #include #include #include #ifndef HAVE_SQLITE_ERRSTR #define sqlite3_errstr(rc) "" /**< @brief If our version of SQLite hasn't go this function */ #endif // HAVE_SQLITE_ERRSTR #define STRINGIFY(x) #x /**< @brief Stringification helper for STRINGIFY. Not to be used separately. */ #define TOSTRING(x) STRINGIFY(x) /**< @brief Convert a macro argument into a string constant */ Cache::sqlite_t::sqlite_t(const std::string & filename, int flags, const char *zVfs) : m_ret(SQLITE_OK) , m_filename(filename) , m_select_stmt(nullptr) , m_insert_stmt(nullptr) , m_delete_stmt(nullptr) { m_ret = sqlite3_open_v2(m_filename.c_str(), &m_db_handle, flags, zVfs); } Cache::sqlite_t::~sqlite_t() { if (m_db_handle != nullptr) { #ifdef HAVE_SQLITE_CACHEFLUSH flush_index(); #endif // HAVE_SQLITE_CACHEFLUSH sqlite3_finalize(m_select_stmt); sqlite3_finalize(m_insert_stmt); sqlite3_finalize(m_delete_stmt); sqlite3_close(m_db_handle); } sqlite3_shutdown(); } const Cache::TABLE_DEF Cache::m_table_cache_entry = { // // Table name // "cache_entry", // // Primary key // "PRIMARY KEY(`filename`,`desttype`)" }; const Cache::TABLECOLUMNS_VEC Cache::m_columns_cache_entry = { // // Primary key: filename + desttype // { "filename", "TEXT NOT NULL" }, { "desttype", "CHAR ( 10 ) NOT NULL" }, // // Encoding parameters // { "enable_ismv", "BOOLEAN NOT NULL" }, { "audiobitrate", "UNSIGNED INT NOT NULL" }, { "audiosamplerate", "UNSIGNED INT NOT NULL" }, { "videobitrate", "UNSIGNED INT NOT NULL" }, { "videowidth", "UNSIGNED INT NOT NULL" }, { "videoheight", "UNSIGNED INT NOT NULL" }, { "deinterlace", "BOOLEAN NOT NULL" }, // // Encoding results // { "duration", "UNSIGNED BIG INT NOT NULL" }, { "predicted_filesize", "UNSIGNED BIG INT NOT NULL" }, { "encoded_filesize", "UNSIGNED BIG INT NOT NULL" }, { "video_frame_count", "UNSIGNED BIG INT NOT NULL" }, { "segment_count", "UNSIGNED BIG INT NOT NULL" }, { "finished", "INT NOT NULL" }, { "error", "BOOLEAN NOT NULL" }, { "errno", "INT NOT NULL" }, { "averror", "INT NOT NULL" }, { "creation_time", "DATETIME NOT NULL" }, { "access_time", "DATETIME NOT NULL" }, { "file_time", "DATETIME NOT NULL" }, { "file_size", "UNSIGNED BIG INT NOT NULL" } }; const Cache::TABLE_DEF Cache::m_table_version = { // // Table name // "version", // nullptr }; const Cache::TABLECOLUMNS_VEC Cache::m_columns_version = { { "db_version_major", "INTEGER NOT NULL" }, { "db_version_minor", "INTEGER NOT NULL" } }; Cache::Cache() { } Cache::~Cache() { // Clean up memory for (auto& [key, value] : m_cache) { value->destroy(); } m_cache.clear(); close_index(); } bool Cache::prepare_stmts() { int ret; const char * sql; sql = "INSERT OR REPLACE INTO cache_entry\n" "(filename, desttype, enable_ismv, audiobitrate, audiosamplerate, videobitrate, videowidth, videoheight, deinterlace, duration, predicted_filesize, encoded_filesize, video_frame_count, segment_count, finished, error, errno, averror, creation_time, access_time, file_time, file_size) VALUES\n" "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), datetime(?, 'unixepoch'), ?);\n"; if (SQLITE_OK != (ret = sqlite3_prepare_v2(*m_cacheidx_db, sql, -1, &m_cacheidx_db->m_insert_stmt, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to prepare insert: (%1) %2\n%3", ret, sqlite3_errmsg(*m_cacheidx_db), sql); return false; } sql = "SELECT desttype, enable_ismv, audiobitrate, audiosamplerate, videobitrate, videowidth, videoheight, deinterlace, duration, predicted_filesize, encoded_filesize, video_frame_count, segment_count, finished, error, errno, averror, strftime('%s', creation_time), strftime('%s', access_time), strftime('%s', file_time), file_size FROM cache_entry WHERE filename = ? AND desttype = ?;\n"; if (SQLITE_OK != (ret = sqlite3_prepare_v2(*m_cacheidx_db, sql, -1, &m_cacheidx_db->m_select_stmt, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to prepare select: (%1) %2\n%3", ret, sqlite3_errmsg(*m_cacheidx_db), sql); return false; } sql = "DELETE FROM cache_entry WHERE filename = ? AND desttype = ?;\n"; if (SQLITE_OK != (ret = sqlite3_prepare_v2(*m_cacheidx_db, sql, -1, &m_cacheidx_db->m_delete_stmt, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to prepare delete: (%1) %2\n%3", ret, sqlite3_errmsg(*m_cacheidx_db), sql); return false; } return true; } bool Cache::table_exists(const char *table) { std::string sql; sqlite3_stmt * stmt = nullptr; int results = 0; int ret; sql = "SELECT Count(*) FROM sqlite_master WHERE type='table' AND name='"; sql += table; sql += "'"; if (SQLITE_OK != (ret = sqlite3_prepare_v2(*m_cacheidx_db, sql.c_str(), -1, &stmt, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to prepare statement for table_exists: (%1) %2\n%3", ret, sqlite3_errmsg(*m_cacheidx_db), sql.c_str()); return false; } ret = sqlite3_step(stmt); if (ret == SQLITE_ROW) { results = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); return (results == 1); } bool Cache::column_exists(const char *table, const char *column) { std::string sql; sqlite3_stmt * stmt = nullptr; int results = 0; int ret; sql = "SELECT COUNT(*) AS CNTREC FROM pragma_table_info('"; sql += table; sql += "') WHERE name='"; sql += column; sql += "';"; if (SQLITE_OK != (ret = sqlite3_prepare_v2(*m_cacheidx_db, sql.c_str(), -1, &stmt, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to prepare statement for table_exists: (%1) %2\n%3", ret, sqlite3_errmsg(*m_cacheidx_db), sql.c_str()); return false; } ret = sqlite3_step(stmt); if (ret == SQLITE_ROW) { results = sqlite3_column_int(stmt, 0); } sqlite3_finalize(stmt); return (results == 1); } bool Cache::check_min_version(int *db_version_major, int *db_version_minor) { std::string sql; sqlite3_stmt * stmt = nullptr; int ret; sql = "SELECT db_version_major, db_version_minor FROM version;"; if (SQLITE_OK != (ret = sqlite3_prepare_v2(*m_cacheidx_db, sql.c_str(), -1, &stmt, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to prepare statement for check_min_version: (%1) %2\n%3", ret, sqlite3_errmsg(*m_cacheidx_db), sql.c_str()); return false; } ret = sqlite3_step(stmt); if (ret == SQLITE_ROW) { *db_version_major = sqlite3_column_int(stmt, 0); *db_version_minor = sqlite3_column_int(stmt, 1); } sqlite3_finalize(stmt); return (cmp_version(*db_version_major, *db_version_minor, DB_MIN_VERSION_MAJOR, DB_MIN_VERSION_MINOR) >= 0); } int Cache::cmp_version(int version_major_l, int version_minor_l, int version_major_r, int version_minor_r) { if (version_major_l > version_major_r) { return 1; } if (version_major_l < version_major_r) { return -1; } // version_major_l == version_major_r if (version_minor_l > version_minor_r) { return 1; } if (version_minor_l < version_minor_r) { return -1; } // version_minor_l == version_minor_r return 0; } bool Cache::begin_transaction() { char *errmsg = nullptr; const char * sql; int ret; sql = "BEGIN TRANSACTION;"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql, nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 begin transaction failed: (%1) %2\n%3", ret, errmsg, sql); sqlite3_free(errmsg); return false; } return true; } bool Cache::end_transaction() { char *errmsg = nullptr; const char * sql; int ret; sql = "END TRANSACTION;"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql, nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 end transaction failed: (%1) %2\n%3", ret, errmsg, sql); sqlite3_free(errmsg); return false; } return true; } bool Cache::rollback_transaction() { char *errmsg = nullptr; const char * sql; int ret; sql = "ROLLBACK;"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql, nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 rollback transaction failed: (%1) %2\n%3", ret, errmsg, sql); sqlite3_free(errmsg); return false; } return true; } bool Cache::create_table_cache_entry(LPCTABLE_DEF table, const TABLECOLUMNS_VEC & columns) { char *errmsg = nullptr; std::string sql; int ret; sql = "CREATE TABLE `"; sql += table->name; sql += "` (\n"; int i = 0; for (const TABLE_COLUMNS & col : columns) { if (i++) { sql += ",\n"; } sql += "`"; sql += col.name; sql += "` "; sql += col.type; } if (table->primary_key != nullptr) { sql += ",\n"; sql += table->primary_key; } sql += "\n"; sql += ");\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } return true; } bool Cache::upgrade_db(int *db_version_major, int *db_version_minor) { if (!column_exists("cache_entry", "video_frame_count")) { // If video_frame_count is missing, this db is definetly old { char *errmsg = nullptr; std::string sql; int ret; Logging::debug(m_cacheidx_db->filename(), "Adding `video_frame_count` column."); // Add `video_frame_count` UNSIGNED BIG INT NOT NULL DEFAULT 0 sql = "ALTER TABLE `"; sql += m_table_cache_entry.name; sql += "` ADD COLUMN `video_frame_count` UNSIGNED BIG INT NOT NULL DEFAULT 0;\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error adding column `video_frame_count`: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } Logging::debug(m_cacheidx_db->filename(), "Altering `finished` from BOOLEAN to INT."); } { char *errmsg = nullptr; std::string sql; int ret; //ALTER `finished` from BOOLEAN to INT NOT NULL // sqlite can't do that for us, we must... // // 1. Rename `cache_entry` to `cache_entry_old` // 2. Create new table `cache_entry` with new structure // 3. Copy all data from `cache_entry_old` to `cache_entry`, converting old to new column // 4. Delete `cache_entry_old` sql = "PRAGMA foreign_keys=off;\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } // Step 1 { char *errmsg = nullptr; std::string sql; int ret; sql = "ALTER TABLE `"; sql += m_table_cache_entry.name; sql += "` RENAME TO `"; sql += m_table_cache_entry.name; sql += "_old`;\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } // Step 2 if (!create_table_cache_entry(&m_table_cache_entry, m_columns_cache_entry)) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error creating 'cache_entry' table."); return false; } // Step 3 { char *errmsg = nullptr; std::string sql; std::string columns; int ret; for (const TABLE_COLUMNS & col : m_columns_cache_entry) { if (!columns.empty()) { columns += ","; } columns += "`"; columns += col.name; columns += "`"; } sql = "INSERT INTO `"; sql += m_table_cache_entry.name; sql += "` ("; sql += columns; sql += ")\nSELECT "; sql += columns; sql += " FROM `"; sql += m_table_cache_entry.name; sql += "_old`;"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } { char *errmsg = nullptr; std::string sql; int ret; // Old 0 is RESULTCODE::NONE (0) // Old 1 is RESULTCODE::FINISHED (2) sql = "UPDATE `"; sql += m_table_cache_entry.name; sql += "`\n"; sql += "SET `finished` = 3\n"; sql += "WHERE `finished` = 1\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error updating column `finished`: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } // Step 4 { char *errmsg = nullptr; std::string sql; int ret; sql = "DROP TABLE `cache_entry_old`"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error adding column `video_frame_count`: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } sql = "PRAGMA foreign_keys=on;\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } } if (!column_exists("cache_entry", "duration")) { char *errmsg = nullptr; std::string sql; int ret; Logging::debug(m_cacheidx_db->filename(), "Adding `duration` column."); // Add `duration` UNSIGNED BIG INT NOT NULL DEFAULT 0 sql = "ALTER TABLE `"; sql += m_table_cache_entry.name; sql += "` ADD COLUMN `duration` UNSIGNED BIG INT NOT NULL DEFAULT 0;\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error adding column `duration`: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } if (!column_exists("cache_entry", "segment_count")) { char *errmsg = nullptr; std::string sql; int ret; Logging::debug(m_cacheidx_db->filename(), "Adding `segment_count` column."); // Add `segment_count` UNSIGNED BIG INT NOT NULL DEFAULT 0 sql = "ALTER TABLE `"; sql += m_table_cache_entry.name; sql += "` ADD COLUMN `segment_count` UNSIGNED BIG INT NOT NULL DEFAULT 0;\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql.c_str(), nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error adding column `segment_count`: (%1) %2\n%3", ret, errmsg, sql.c_str()); sqlite3_free(errmsg); return false; } } // Update DB version Logging::debug(m_cacheidx_db->filename(), "Updating version table to V%1.%2.", DB_VERSION_MAJOR, DB_VERSION_MINOR); { char *errmsg = nullptr; const char * sql; int ret; sql = "UPDATE `version` SET db_version_major = " TOSTRING(DB_VERSION_MAJOR) ", db_version_minor = " TOSTRING(DB_VERSION_MINOR) ";\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql, nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql); sqlite3_free(errmsg); return false; } *db_version_major = DB_VERSION_MAJOR; *db_version_minor = DB_VERSION_MINOR; } Logging::info(m_cacheidx_db->filename(), "Database successfully upgraded to V%1.%2.", *db_version_major, *db_version_minor); return true; } bool Cache::load_index() { bool success = true; try { std::string filename; char *errmsg = nullptr; int ret; bool new_database = false; bool need_upate = false; transcoder_cache_path(&filename); if (mktree(filename, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) && errno != EEXIST) { Logging::error(filename, "Error creating cache directory: (%1) %2\n%3", errno, strerror(errno), m_cacheidx_db->filename().c_str()); throw false; } append_filename(&filename, "cacheidx.sqlite"); // initialise engine if (SQLITE_OK != (ret = sqlite3_initialize())) { Logging::error(filename, "Failed to initialise SQLite3 library: (%1) %2", ret, sqlite3_errstr(ret)); throw false; } // open connection to a DB m_cacheidx_db = std::make_unique(filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_SHAREDCACHE); if (m_cacheidx_db == nullptr) { Logging::error(filename, "Out of memory."); throw false; } if (SQLITE_OK != (ret = m_cacheidx_db->ret())) { Logging::error(m_cacheidx_db->filename(), "Failed to initialise SQLite3 connection: (%1) %2", ret, sqlite3_errmsg(*m_cacheidx_db)); throw false; } if (SQLITE_OK != (ret = sqlite3_busy_timeout(*m_cacheidx_db, 1000))) { Logging::error(m_cacheidx_db->filename(), "Failed to set SQLite3 busy timeout: (%1) %2", ret, sqlite3_errmsg(*m_cacheidx_db)); throw false; } // Beginning with version 3.7.0 (2010-07-21), a new "Write-Ahead Log" option // We support Sqlite from 3.7.13 anyway if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, "pragma journal_mode = WAL", nullptr, nullptr, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to set SQLite3 WAL mode: (%1) %2", ret, sqlite3_errmsg(*m_cacheidx_db)); throw false; } // Very strange: Compare operations with =, > etc. are case sensitive, while LIKE by default ignores upper/lowercase. // Produces strange results when reading from a Samba drive and different cases are used... if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, "PRAGMA case_sensitive_like = 1;", nullptr, nullptr, nullptr))) { Logging::error(m_cacheidx_db->filename(), "Failed to set SQLite3 case_sensitive_like = 1: (%1) %2", ret, sqlite3_errmsg(*m_cacheidx_db)); throw false; } // Make sure the next changes are either all successfull or rolled back if (!begin_transaction()) { throw false; } // Check if we got a new, empty database and create necessary tables // Create cache_entry table if not already existing if (!table_exists("cache_entry")) { Logging::debug(m_cacheidx_db->filename(), "Creating 'cache_entry' table in database."); if (!create_table_cache_entry(&m_table_cache_entry, m_columns_cache_entry)) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error creating 'cache_entry' table: (%1) %2", ret, errmsg); throw false; } new_database = true; // Created a new database } // If version table does not exist add it if (!table_exists("version")) { const char * sql; Logging::debug(m_cacheidx_db->filename(), "Creating 'version' table in database."); if (!create_table_cache_entry(&m_table_version, m_columns_version)) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error creating 'cache_entry' table: (%1) %2", ret, errmsg); throw false; } sql = "INSERT INTO `version` (db_version_major, db_version_minor) VALUES (" TOSTRING(DB_VERSION_MAJOR) ", " TOSTRING(DB_VERSION_MINOR) ");\n"; if (SQLITE_OK != (ret = sqlite3_exec(*m_cacheidx_db, sql, nullptr, nullptr, &errmsg))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 exec error: (%1) %2\n%3", ret, errmsg, sql); sqlite3_free(errmsg); throw false; } if (!new_database) { // Added version only, old database, need upgrade need_upate = true; } } // Check if database needs a structure upgrade int db_version_major = DB_BASE_VERSION_MAJOR; // Old database contains no version table. This is the version of this database. int db_version_minor = DB_BASE_VERSION_MINOR; if (need_upate || !check_min_version(&db_version_major, &db_version_minor)) { // No version table found, or minimum version too low, do an upgrade. Logging::warning(m_cacheidx_db->filename(), "Database version is %1.%2, but a least %3.%4 required. Upgrading database now.", db_version_major, db_version_minor, DB_MIN_VERSION_MAJOR, DB_MIN_VERSION_MINOR); if (!upgrade_db(&db_version_major, &db_version_minor)) { throw false; } } if (!end_transaction()) { throw false; } #ifdef HAVE_SQLITE_CACHEFLUSH if (!m_cacheidx_db->flush_index()) { throw false; } #endif // HAVE_SQLITE_CACHEFLUSH // prepare the statements if (!prepare_stmts()) { throw false; } } catch (bool _success) { success = _success; } return success; } #ifdef HAVE_SQLITE_CACHEFLUSH bool Cache::sqlite_t::flush_index() { if (m_db_handle != nullptr) { int ret; // Flush cache to disk if (SQLITE_OK != (ret = sqlite3_db_cacheflush(m_db_handle))) { Logging::error(m_filename, "SQLite3 cache flush error: (%1) %2", ret, sqlite3_errstr(ret)); return false; } } return true; } #endif // HAVE_SQLITE_CACHEFLUSH bool Cache::read_info(LPCACHE_INFO cache_info) { bool success = true; //cache_info->m_enable_ismv = 0; cache_info->m_audiobitrate = 0; cache_info->m_audiosamplerate = 0; cache_info->m_videobitrate = 0; cache_info->m_videowidth = 0; cache_info->m_videoheight = 0; cache_info->m_deinterlace = false; cache_info->m_duration = 0; cache_info->m_predicted_filesize = 0; cache_info->m_encoded_filesize = 0; cache_info->m_video_frame_count = 0; cache_info->m_segment_count = 0; cache_info->m_result = RESULTCODE::NONE; cache_info->m_error = false; cache_info->m_errno = 0; cache_info->m_averror = 0; cache_info->m_creation_time = 0; cache_info->m_access_time = 0; cache_info->m_file_time = 0; cache_info->m_file_size = 0; if (m_cacheidx_db->m_select_stmt == nullptr) { Logging::error(m_cacheidx_db->filename(), "SQLite3 select statement not open."); return false; } std::lock_guard lock_mutex(m_mutex); try { int ret; assert(sqlite3_bind_parameter_count(m_cacheidx_db->m_select_stmt) == 2); if (SQLITE_OK != (ret = sqlite3_bind_text(m_cacheidx_db->m_select_stmt, 1, cache_info->m_destfile.c_str(), -1, nullptr))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 select error binding 'filename': (%1) %2", ret, sqlite3_errstr(ret)); throw false; } if (SQLITE_OK != (ret = sqlite3_bind_text(m_cacheidx_db->m_select_stmt, 2, cache_info->m_desttype.data(), -1, nullptr))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 select error binding 'desttype': (%1) %2", ret, sqlite3_errstr(ret)); throw false; } ret = sqlite3_step(m_cacheidx_db->m_select_stmt); if (ret == SQLITE_ROW) { const char *text = reinterpret_cast(sqlite3_column_text(m_cacheidx_db->m_select_stmt, 0)); if (text != nullptr) { cache_info->m_desttype[0] = '\0'; strncat(cache_info->m_desttype.data(), text, cache_info->m_desttype.size() - 1); } //cache_info->m_enable_ismv = sqlite3_column_int(m_cacheidx_db->m_cacheidx_select_stmt, 1); cache_info->m_audiobitrate = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 2); cache_info->m_audiosamplerate = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 3); cache_info->m_videobitrate = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 4); cache_info->m_videowidth = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 5); cache_info->m_videoheight = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 6); cache_info->m_deinterlace = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 7); cache_info->m_duration = sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 8); cache_info->m_predicted_filesize = static_cast(sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 9)); cache_info->m_encoded_filesize = static_cast(sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 10)); cache_info->m_video_frame_count = static_cast(sqlite3_column_int(m_cacheidx_db->m_select_stmt, 11)); cache_info->m_segment_count = static_cast(sqlite3_column_int(m_cacheidx_db->m_select_stmt, 12)); cache_info->m_result = static_cast(sqlite3_column_int(m_cacheidx_db->m_select_stmt, 13)); cache_info->m_error = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 14); cache_info->m_errno = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 15); cache_info->m_averror = sqlite3_column_int(m_cacheidx_db->m_select_stmt, 16); cache_info->m_creation_time = static_cast(sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 17)); cache_info->m_access_time = static_cast(sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 18)); cache_info->m_file_time = static_cast(sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 19)); cache_info->m_file_size = static_cast(sqlite3_column_int64(m_cacheidx_db->m_select_stmt, 20)); } else if (ret != SQLITE_DONE) { Logging::error(m_cacheidx_db->filename(), "Sqlite 3 could not step (execute) select statement: (%1) %2", ret, sqlite3_errstr(ret)); throw false; } } catch (bool _success) { success = _success; } sqlite3_reset(m_cacheidx_db->m_select_stmt); if (success) { errno = 0; // sqlite3 sometimes sets errno without any reason, better reset any error } return success; } #define SQLBINDTXT(idx, var) \ if (SQLITE_OK != (ret = sqlite3_bind_text(m_cacheidx_db->m_insert_stmt, idx, var, -1, nullptr))) \ { \ Logging::error(m_cacheidx_db->filename(), "SQLite3 select column #%1 error: %2\n%3", idx, ret, sqlite3_errstr(ret)); \ throw false; \ } /**< @brief Bind text column to SQLite statement */ #define SQLBINDNUM(func, idx, var) \ if (SQLITE_OK != (ret = func(m_cacheidx_db->m_insert_stmt, idx, var))) \ { \ Logging::error(m_cacheidx_db->filename(), "SQLite3 select column #%1 error: %2\n%3", idx, ret, sqlite3_errstr(ret)); \ throw false; \ } /**< @brief Bind numeric column to SQLite statement */ bool Cache::write_info(LPCCACHE_INFO cache_info) { bool success = true; if (m_cacheidx_db->m_insert_stmt == nullptr) { Logging::error(m_cacheidx_db->filename(), "SQLite3 select statement not open."); return false; } std::lock_guard lock_mutex(m_mutex); try { int ret; bool enable_ismv_dummy = false; assert(sqlite3_bind_parameter_count(m_cacheidx_db->m_insert_stmt) == 22); SQLBINDTXT(1, cache_info->m_destfile.c_str()); SQLBINDTXT(2, cache_info->m_desttype.data()); //SQLBINDNUM(sqlite3_bind_int, 3, cache_info->m_enable_ismv); SQLBINDNUM(sqlite3_bind_int, 3, enable_ismv_dummy); SQLBINDNUM(sqlite3_bind_int64, 4, cache_info->m_audiobitrate); SQLBINDNUM(sqlite3_bind_int, 5, cache_info->m_audiosamplerate); SQLBINDNUM(sqlite3_bind_int64, 6, cache_info->m_videobitrate); SQLBINDNUM(sqlite3_bind_int, 7, static_cast(cache_info->m_videowidth)); SQLBINDNUM(sqlite3_bind_int, 8, static_cast(cache_info->m_videoheight)); SQLBINDNUM(sqlite3_bind_int, 9, cache_info->m_deinterlace); SQLBINDNUM(sqlite3_bind_int64, 10, static_cast(cache_info->m_duration)); SQLBINDNUM(sqlite3_bind_int64, 11, static_cast(cache_info->m_predicted_filesize)); SQLBINDNUM(sqlite3_bind_int64, 12, static_cast(cache_info->m_encoded_filesize)); SQLBINDNUM(sqlite3_bind_int, 13, static_cast(cache_info->m_video_frame_count)); SQLBINDNUM(sqlite3_bind_int, 14, static_cast(cache_info->m_segment_count)); SQLBINDNUM(sqlite3_bind_int, 15, static_cast(cache_info->m_result)); SQLBINDNUM(sqlite3_bind_int, 16, cache_info->m_error); SQLBINDNUM(sqlite3_bind_int, 17, cache_info->m_errno); SQLBINDNUM(sqlite3_bind_int, 18, cache_info->m_averror); SQLBINDNUM(sqlite3_bind_int64, 19, cache_info->m_creation_time); SQLBINDNUM(sqlite3_bind_int64, 20, cache_info->m_access_time); SQLBINDNUM(sqlite3_bind_int64, 21, cache_info->m_file_time); SQLBINDNUM(sqlite3_bind_int64, 22, static_cast(cache_info->m_file_size)); ret = sqlite3_step(m_cacheidx_db->m_insert_stmt); if (ret != SQLITE_DONE) { Logging::error(m_cacheidx_db->filename(), "Sqlite 3 could not step (execute) insert statement: (%1) %2", ret, sqlite3_errstr(ret)); throw false; } } catch (bool _success) { success = _success; } sqlite3_reset(m_cacheidx_db->m_insert_stmt); if (success) { errno = 0; // sqlite3 sometimes sets errno without any reason, better reset any error } return success; } bool Cache::delete_info(const std::string & filename, const std::string & desttype) { bool success = true; if (m_cacheidx_db->m_delete_stmt == nullptr) { Logging::error(m_cacheidx_db->filename(), "SQLite3 delete statement not open."); return false; } std::lock_guard lock_mutex(m_mutex); try { int ret; assert(sqlite3_bind_parameter_count(m_cacheidx_db->m_delete_stmt) == 2); if (SQLITE_OK != (ret = sqlite3_bind_text(m_cacheidx_db->m_delete_stmt, 1, filename.c_str(), -1, nullptr))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 select error binding 'filename': (%1) %2", ret, sqlite3_errstr(ret)); throw false; } if (SQLITE_OK != (ret = sqlite3_bind_text(m_cacheidx_db->m_delete_stmt, 2, desttype.c_str(), -1, nullptr))) { Logging::error(m_cacheidx_db->filename(), "SQLite3 select error binding 'desttype': (%1) %2", ret, sqlite3_errstr(ret)); throw false; } ret = sqlite3_step(m_cacheidx_db->m_delete_stmt); if (ret != SQLITE_DONE) { Logging::error(m_cacheidx_db->filename(), "Sqlite 3 could not step (execute) delete statement: (%1) %2", ret, sqlite3_errstr(ret)); throw false; } } catch (bool _success) { success = _success; } sqlite3_reset(m_cacheidx_db->m_delete_stmt); if (success) { errno = 0; // sqlite3 sometimes sets errno without any reason, better reset any error } return success; } void Cache::close_index() { m_cacheidx_db.reset(); } Cache_Entry* Cache::create_entry(LPVIRTUALFILE virtualfile, const std::string & desttype) { //Cache_Entry* cache_entry = new (std::nothrow) Cache_Entry(this, filename); Cache_Entry* cache_entry = Cache_Entry::create(this, virtualfile); if (cache_entry == nullptr) { Logging::error(m_cacheidx_db->filename(), "Out of memory creating cache entry."); return nullptr; } m_cache.emplace(std::pair{virtualfile->m_destfile, desttype}, cache_entry); return cache_entry; } bool Cache::delete_entry(Cache_Entry ** cache_entry, int flags) { if (*cache_entry == nullptr) { return true; } bool deleted = false; if ((*cache_entry)->closeio(flags)) { // If CACHE_CLOSE_FREE is set, also free memory if (CACHE_CHECK_BIT(CACHE_CLOSE_FREE, flags)) { m_cache.erase(make_pair((*cache_entry)->m_cache_info.m_destfile, (*cache_entry)->m_cache_info.m_desttype.data())); deleted = (*cache_entry)->destroy(); *cache_entry = nullptr; } } return deleted; } Cache_Entry *Cache::openio(LPVIRTUALFILE virtualfile) { Cache_Entry* cache_entry = nullptr; cache_t::const_iterator p = m_cache.find(make_pair(virtualfile->m_destfile, params.current_format(virtualfile)->desttype())); if (p == m_cache.cend()) { Logging::trace(virtualfile->m_destfile, "Created new transcoder."); cache_entry = create_entry(virtualfile, params.current_format(virtualfile)->desttype()); } else { Logging::trace(virtualfile->m_destfile, "Reusing cached transcoder."); cache_entry = p->second; } return cache_entry; } bool Cache::closeio(Cache_Entry **cache_entry, int flags /*= CACHE_CLOSE_NOOPT*/) { if (*cache_entry == nullptr) { return true; } bool deleted; std::string filename((*cache_entry)->filename()); if (delete_entry(cache_entry, flags)) { Logging::trace(filename, "Freed cache entry."); deleted = true; } else { Logging::trace(filename, "Keeping cache entry."); deleted = false; } return deleted; } bool Cache::prune_expired() { if (params.m_expiry_time <= 0) { // There's no limit. return true; } std::vector keys; sqlite3_stmt * stmt; time_t now = time(nullptr); std::string sql; Logging::trace(m_cacheidx_db->filename(), "Pruning expired cache entries older than %1...", format_time(params.m_expiry_time).c_str()); strsprintf(&sql, "SELECT filename, desttype, strftime('%%s', access_time) FROM cache_entry WHERE strftime('%%s', access_time) + %" FFMPEGFS_FORMAT_TIME_T " < %" FFMPEGFS_FORMAT_TIME_T ";\n", params.m_expiry_time, now); std::lock_guard lock_mutex(m_mutex); sqlite3_prepare(*m_cacheidx_db, sql.c_str(), -1, &stmt, nullptr); int ret = 0; while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { const char *filename = reinterpret_cast(sqlite3_column_text(stmt, 0)); const char *desttype = reinterpret_cast(sqlite3_column_text(stmt, 1)); keys.emplace_back(filename, desttype); Logging::trace(filename, "Found %1 old entries.", format_time(now - static_cast(sqlite3_column_int64(stmt, 2))).c_str()); } Logging::trace(m_cacheidx_db->filename(), "%1 expired cache entries found.", keys.size()); if (ret == SQLITE_DONE) { for (const auto& [key, value] : keys) { Logging::trace(m_cacheidx_db->filename(), "Pruning '%1' - Type: %2", key.c_str(), value.c_str()); cache_t::iterator p = m_cache.find(make_pair(key, value)); if (p != m_cache.end()) { delete_entry(&p->second, CACHE_CLOSE_DELETE); } if (delete_info(key, value)) { remove_cachefile(key, value); } } } else { Logging::error(m_cacheidx_db->filename(), "Failed to execute select. Return code: %1 Error: %2 SQL: %3", ret, sqlite3_errmsg(*m_cacheidx_db), expanded_sql(stmt).c_str()); } sqlite3_finalize(stmt); return true; } bool Cache::prune_cache_size() { if (!params.m_max_cache_size) { // There's no limit. return true; } std::vector keys; std::vector filesizes; sqlite3_stmt * stmt; const char * sql; Logging::trace(m_cacheidx_db->filename(), "Pruning oldest cache entries exceeding %1 cache size...", format_size(params.m_max_cache_size).c_str()); sql = "SELECT filename, desttype, encoded_filesize FROM cache_entry ORDER BY access_time ASC;\n"; std::lock_guard lock_mutex(m_mutex); sqlite3_prepare(*m_cacheidx_db, sql, -1, &stmt, nullptr); int ret = 0; size_t total_size = 0; while((ret = sqlite3_step(stmt)) == SQLITE_ROW) { const char *filename = reinterpret_cast(sqlite3_column_text(stmt, 0)); const char *desttype = reinterpret_cast(sqlite3_column_text(stmt, 1)); size_t size = static_cast(sqlite3_column_int64(stmt, 2)); keys.emplace_back(filename, desttype); filesizes.push_back(size); total_size += size; } Logging::trace(m_cacheidx_db->filename(), "%1 in cache.", format_size(total_size).c_str()); if (total_size > params.m_max_cache_size) { Logging::trace(m_cacheidx_db->filename(), "Pruning %1 of oldest cache entries to limit cache size.", format_size(total_size - params.m_max_cache_size).c_str()); if (ret == SQLITE_DONE) { size_t n = 0; for (const auto& [key, value] : keys) { Logging::trace(m_cacheidx_db->filename(), "Pruning: %1 Type: %2", key.c_str(), value.c_str()); cache_t::iterator p = m_cache.find(make_pair(key, value)); if (p != m_cache.end()) { delete_entry(&p->second, CACHE_CLOSE_DELETE); } if (delete_info(key, value)) { remove_cachefile(key, value); } total_size -= filesizes[n++]; if (total_size <= params.m_max_cache_size) { break; } } Logging::trace(m_cacheidx_db->filename(), "%1 left in cache.", format_size(total_size).c_str()); } else { Logging::error(m_cacheidx_db->filename(), "Failed to execute select. Return code: %1 Error: %2 SQL: %3", ret, sqlite3_errmsg(*m_cacheidx_db), expanded_sql(stmt).c_str()); } } sqlite3_finalize(stmt); return true; } bool Cache::prune_disk_space(size_t predicted_filesize) { std::string cachepath; transcoder_cache_path(&cachepath); size_t free_bytes = get_disk_free(cachepath); if (!free_bytes && errno) { if (errno == ENOENT) { // Cache path does not exist. Strange problem, but not error. Ignore silently. return true; } Logging::error(cachepath, "prune_disk_space() cannot determine free disk space: (%1) %2", errno, strerror(errno)); return false; } if (free_bytes < predicted_filesize) { Logging::error(cachepath, "prune_disk_space() : Insufficient disk space %1 on cache drive, at least %2 required.", format_size(free_bytes).c_str(), format_size(predicted_filesize).c_str()); errno = ENOSPC; return false; } std::lock_guard lock_mutex(m_mutex); Logging::trace(cachepath, "%1 disk space before prune.", format_size(free_bytes).c_str()); if (free_bytes < params.m_min_diskspace + predicted_filesize) { std::vector keys; std::vector filesizes; sqlite3_stmt * stmt; const char * sql; sql = "SELECT filename, desttype, encoded_filesize FROM cache_entry ORDER BY access_time ASC;\n"; sqlite3_prepare(*m_cacheidx_db, sql, -1, &stmt, nullptr); int ret = 0; while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { const char *filename = reinterpret_cast(sqlite3_column_text(stmt, 0)); const char *desttype = reinterpret_cast(sqlite3_column_text(stmt, 1)); size_t size = static_cast(sqlite3_column_int64(stmt, 2)); keys.emplace_back(filename, desttype); filesizes.push_back(size); } Logging::trace(cachepath, "Pruning %1 of oldest cache entries to keep disk space above %2 limit...", format_size(params.m_min_diskspace + predicted_filesize - free_bytes).c_str(), format_size(params.m_min_diskspace).c_str()); if (ret == SQLITE_DONE) { size_t n = 0; for (const auto& [key, value] : keys) { Logging::trace(cachepath, "Pruning: %1 Type: %2", key.c_str(), value.c_str()); cache_t::iterator p = m_cache.find(make_pair(key, value)); if (p != m_cache.end()) { delete_entry(&p->second, CACHE_CLOSE_DELETE); } if (delete_info(key, value)) { remove_cachefile(key, value); } free_bytes += filesizes[n++]; if (free_bytes >= params.m_min_diskspace + predicted_filesize) { break; } } Logging::trace(cachepath, "Disk space after prune: %1", format_size(free_bytes).c_str()); } else { Logging::error(cachepath, "Failed to execute select. Return code: %1 Error: %2 SQL: %3", ret, sqlite3_errmsg(*m_cacheidx_db), expanded_sql(stmt).c_str()); } sqlite3_finalize(stmt); } return true; } bool Cache::maintenance(size_t predicted_filesize) { bool success = true; // Find and remove expired cache entries success &= prune_expired(); // Check max. cache size success &= prune_cache_size(); // Check min. diskspace required for cache success &= prune_disk_space(predicted_filesize); return success; } bool Cache::clear() { bool success = true; std::lock_guard lock_mutex(m_mutex); std::vector keys; sqlite3_stmt * stmt; const char * sql; sql = "SELECT filename, desttype FROM cache_entry;\n"; sqlite3_prepare(*m_cacheidx_db, sql, -1, &stmt, nullptr); int ret = 0; while((ret = sqlite3_step(stmt)) == SQLITE_ROW) { const char *filename = reinterpret_cast(sqlite3_column_text(stmt, 0)); const char *desttype = reinterpret_cast(sqlite3_column_text(stmt, 1)); keys.emplace_back(filename, desttype); } Logging::trace(m_cacheidx_db->filename(), "Clearing all %1 entries from cache...", keys.size()); if (ret == SQLITE_DONE) { for (const auto& [key, value] : keys) { Logging::trace(m_cacheidx_db->filename(), "Pruning: %1 Type: %2", key.c_str(), value.c_str()); cache_t::iterator p = m_cache.find(make_pair(key, value)); if (p != m_cache.end()) { delete_entry(&p->second, CACHE_CLOSE_DELETE); } if (delete_info(key, value)) { remove_cachefile(key, value); } } } else { Logging::error(m_cacheidx_db->filename(), "Failed to execute select. Return code: %1 Error: %2 SQL: %3", ret, sqlite3_errmsg(*m_cacheidx_db), expanded_sql(stmt).c_str()); } sqlite3_finalize(stmt); return success; } bool Cache::remove_cachefile(const std::string & filename, const std::string & fileext) { std::string cachefile; bool success; Buffer::make_cachefile_name(&cachefile, filename, fileext, false); success = Buffer::remove_file(cachefile); Buffer::make_cachefile_name(&cachefile, filename, fileext, true); if (!Buffer::remove_file(cachefile) && errno != ENOENT) { success = false; } return success; } std::string Cache::expanded_sql(sqlite3_stmt *pStmt) { std::string sql; #ifdef HAVE_SQLITE_EXPANDED_SQL char * p = sqlite3_expanded_sql(pStmt); sql = p; sqlite3_free(p); #else const char *p = sqlite3_sql(pStmt); if (p != nullptr) { sql=p; } else { sql="(nullptr)"; } #endif return sql; } ffmpegfs-2.50/src/makehelp.sh0000775000175000017500000000202615177713600011643 # Copyright (C) 2019-2026 Norbert Schlia (nschlia@oblivion-software.de) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # On Debian systems, the complete text of the GNU General Public License # Version 3 can be found in `/usr/share/common-licenses/GPL-3'. set -e sed '1,/OPTIONS/d;/General\/FUSE options/,$d' ../ffmpegfs.1.text > ffmpegfshelp xxd -i ffmpegfshelp > $1 rm ffmpegfshelp ffmpegfs-2.50/src/cuesheetparser.cc0000664000175000017500000004045715177713600013061 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cuesheetparser.cc * @brief Cue sheet parser implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "ffmpegfs.h" #include "cuesheetparser.h" #include "transcode.h" #include "logging.h" #include #define FPS (75) ///<* On sector contains 75 frames per CD specs #define VAL_OR_EMPTY(val) ((val) != nullptr ? (val) : "") ///<* Return string or empty string if val is nullptr /** * @brief Cuesheet structure * Structure see https://en.wikipedia.org/wiki/Cue_sheet_(computing) @n * @n * Real life example: @n * @n * PERFORMER "Subway to Sally" @n * TITLE "Nord Nord Ost" @n * CATALOG 0727361134129 @n * REM DATE 2005 @n * REM DISCNUMBER 1 @n * REM TOTALDISCS 1 @n * FILE "Subway to Sally - Nord Nord Ost.flac" WAVE @n * TRACK 01 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Sarabande de noir" @n * INDEX 01 00:00:00 @n * TRACK 02 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Schneekönigin" @n * INDEX 01 00:55:30 @n * TRACK 03 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Feuerland" @n * INDEX 01 06:41:38 @n * TRACK 04 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Sieben" @n * INDEX 01 10:48:42 @n * TRACK 05 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Lacrimae '74" @n * INDEX 01 14:11:14 @n * TRACK 06 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Feuerkind" @n * INDEX 01 15:57:07 @n * TRACK 07 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Das Rätsel II" @n * INDEX 01 22:03:28 @n * TRACK 08 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "S.O.S." @n * INDEX 01 26:25:23 @n * TRACK 09 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Eisblumen" @n * INDEX 01 32:21:42 @n * TRACK 10 AUDIO @n * PERFORMER "Subway to Sally" @n * TITLE "Seemannslied" @n * INDEX 01 36:53:71 @n */ static bool create_cuesheet_virtualfile(LPCVIRTUALFILE virtualfile, Track *track, int titleno, const std::string & path, const struct stat * statbuf, int trackcount, int trackno, const std::string &aperformer, const std::string & album, const std::string & genre, const std::string & date, int64_t *remainingduration, LPVIRTUALFILE *lastfile); static int parse_cuesheet_file(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler); static int parse_cuesheet_text(LPCVIRTUALFILE virtualfile, void *buf, fuse_fill_dir_t filler); static int parse_cuesheet(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, Cd *cd, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler); /** * @brief Create a virtual file entry of a cue sheet title. * @param[in] virtualfile - VIRTUALFILE struct of a file. * @param[in] track - libcue2 track handle * @param[in] titleno - Title number. * @param[in] path - Path to check. * @param[in] statbuf - File status structure of original file. * @param[in] trackcount - Number of tracks in cue sheet. * @param[in] trackno - Track number. * @param[in] aperformer - Album performer. * @param[in] album - Name of album. * @param[in] genre - Album genre. * @param[in] date - Publishing date. * @param[inout] remainingduration - Remaining duration of file in AV_TIME_BASE fractions. * @param[in] lastfile - Pointer to last virtual file. May be nullptr if none exists. * @return On error, returns false. On success, returns true. */ static bool create_cuesheet_virtualfile(LPCVIRTUALFILE virtualfile, Track *track, int titleno, const std::string & path, const struct stat * statbuf, int trackcount, int trackno, const std::string & aperformer, const std::string & album, const std::string & genre, const std::string & date, int64_t *remainingduration, LPVIRTUALFILE *lastfile) { Cdtext *cuesheetcdtext = track_get_cdtext(track); if (cuesheetcdtext == nullptr) { Logging::error(virtualfile->m_origfile, "The track CD text could not be extracted from the cue sheet."); errno = EIO; return false; } std::string performer = VAL_OR_EMPTY(cdtext_get(PTI_PERFORMER, cuesheetcdtext)); std::string title = VAL_OR_EMPTY(cdtext_get(PTI_TITLE, cuesheetcdtext)); if (performer.empty()) { // If track performer is empty, try album performer. performer = aperformer; } int64_t start = AV_TIME_BASE * static_cast(track_get_start(track)) / FPS; int64_t length = track_get_length(track); int64_t duration; if (length > -1) { duration = AV_TIME_BASE * length / FPS; *remainingduration -= duration; } else { // Length of duration = *remainingduration; } std::string virtfilename; strsprintf(&virtfilename, "%02d. %s - %s [%s].%s", titleno, performer.c_str(), title.c_str(), replace_all(format_duration(duration), ":", "-").c_str(), ffmpeg_format[virtualfile->m_format_idx].fileext().c_str()); // Filenames can't contain '/' in POSIX etc. std::replace(virtfilename.begin(), virtfilename.end(), '/', '-'); LPVIRTUALFILE newvirtualfile = nullptr; if (!ffmpeg_format[FORMAT::VIDEO].is_multiformat()) { newvirtualfile = insert_file(VIRTUALTYPE::DISK, path + virtfilename, virtualfile->m_origfile, statbuf, VIRTUALFLAG_CUESHEET); } else { newvirtualfile = insert_dir(VIRTUALTYPE::DISK, path + virtfilename, statbuf, VIRTUALFLAG_CUESHEET); } if (newvirtualfile == nullptr) { Logging::error(path, "Failed to create virtual path: %1", (path + virtfilename).c_str()); errno = EIO; return false; } // We do not add the file to fuse here because it's in a sub directory. // Will be done later on request by load_path() newvirtualfile->m_format_idx = virtualfile->m_format_idx; // Store the correct index (audio) in m_format_idx if (!transcoder_cached_filesize(newvirtualfile, &newvirtualfile->m_st)) { BITRATE video_bit_rate = 0; ///< @todo probe original file for info BITRATE audio_bit_rate = 0; int width = 0; int height = 0; AVRational framerate = { 0, 0 }; bool interleaved = false; newvirtualfile->m_duration = duration; newvirtualfile->m_cuesheet_track.m_duration = duration; newvirtualfile->m_cuesheet_track.m_start = start; newvirtualfile->m_cuesheet_track.m_tracktotal = trackcount; newvirtualfile->m_cuesheet_track.m_trackno = trackno; newvirtualfile->m_cuesheet_track.m_artist = performer; newvirtualfile->m_cuesheet_track.m_title = title; newvirtualfile->m_cuesheet_track.m_album = album; newvirtualfile->m_cuesheet_track.m_genre = genre; newvirtualfile->m_cuesheet_track.m_date = date; if (*lastfile != nullptr) { (*lastfile)->m_cuesheet_track.m_nextfile = newvirtualfile; } *lastfile = newvirtualfile; transcoder_set_filesize(newvirtualfile, duration, audio_bit_rate, virtualfile->m_channels, virtualfile->m_sample_rate, AV_SAMPLE_FMT_NONE, video_bit_rate, width, height, interleaved, framerate); stat_set_size(&newvirtualfile->m_st, newvirtualfile->m_predicted_size); } return true; } /** * @brief Parse a cue sheet file and build virtual set of files * @param[in] virtualfile - VIRTUALFILE struct of a file. * @param[in] cuesheet - Name of cue sheet file * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @return On success, returns number of titles in cue sheet. On error, returns -errno. */ static int parse_cuesheet_file(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler) { // Check for cue sheet std::string text; int res = read_file(cuesheet, text); if (res >= 0) { return -res; } // Found cue sheet Logging::trace(cuesheet, "Found an external cue sheet file."); return parse_cuesheet(virtualfile, cuesheet, cue_parse_string(text.c_str()), statbuf, buf, filler); } /** * @brief Parse a cue sheet and build virtual set of files * @param[in] virtualfile - VIRTUALFILE struct of a file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @return On success, returns number of titles in cue sheet. On error, returns -errno. */ static int parse_cuesheet_text(LPCVIRTUALFILE virtualfile, void *buf, fuse_fill_dir_t filler) { // Found cue sheet Logging::trace(virtualfile->m_origfile, "Found an embedded cue sheet file."); return parse_cuesheet(virtualfile, virtualfile->m_origfile, cue_parse_string(virtualfile->m_cuesheet.c_str()), &virtualfile->m_st, buf, filler); } /** * @brief Parse a cue sheet and build virtual set of files * @param[in] virtualfile - VIRTUALFILE struct of a file. * @param[in] cuesheet - Name of cue sheet file * @param[in] cd - libcue2 cue sheet handle * @param[in] statbuf - File status structure of original file. * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @return On success, returns number of titles in cue sheet or 0 if not found. On error, returns -errno. */ static int parse_cuesheet(LPCVIRTUALFILE virtualfile, const std::string & cuesheet, Cd *cd, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler) { int res = 0; try { if (cd == nullptr) { Logging::error(cuesheet, "The cue sheet could not be parsed."); throw AVERROR(EIO); } Rem *rem = cd_get_rem(cd); if (rem == nullptr) { Logging::error(cuesheet, "Unable to parse remarks from the cue sheet."); throw AVERROR(EIO); } Cdtext *cdtext = cd_get_cdtext(cd); if (cdtext == nullptr) { Logging::error(cuesheet, "The CD text could not be extracted from the cue sheet."); throw AVERROR(EIO); } std::string performer = VAL_OR_EMPTY(cdtext_get(PTI_PERFORMER, cdtext)); std::string album = VAL_OR_EMPTY(cdtext_get(PTI_TITLE, cdtext)); std::string genre = VAL_OR_EMPTY(cdtext_get(PTI_GENRE, cdtext)); std::string date = VAL_OR_EMPTY(rem_get(REM_DATE, rem)); int trackcount = static_cast(cd_get_ntrack(cd)); if (trackcount) { LPVIRTUALFILE insertedvirtualfile = nullptr; std::string subbdir(virtualfile->m_origfile); append_ext(&subbdir, TRACKDIR); std::string dirname(subbdir); append_sep(&subbdir); remove_path(&dirname); insertedvirtualfile = insert_dir(VIRTUALTYPE::DISK, subbdir, statbuf, VIRTUALFLAG_CUESHEET); if (insertedvirtualfile == nullptr) { Logging::error(cuesheet, "Failed to create virtual path: %1", subbdir.c_str()); errno = EIO; return -errno; } if (buf != nullptr && filler(buf, dirname.c_str(), &insertedvirtualfile->m_st, 0, FUSE_FILL_DIR_PLUS)) { // break; } std::string path(virtualfile->m_origfile); remove_filename(&path); LPVIRTUALFILE lastfile = nullptr; int64_t remainingduration = virtualfile->m_duration; for (int trackno = 1; trackno <= trackcount; trackno++) { Track *track = cd_get_track(cd, trackno); if (track == nullptr) { Logging::error(cuesheet, "Track no. %1 could not be obtained from the cue sheet.", trackno); errno = EIO; throw -errno; } if (!create_cuesheet_virtualfile(virtualfile, track, trackno, path + dirname + "/", statbuf, trackcount, trackno, performer, album, genre, date, &remainingduration, &lastfile)) { throw -errno; } } } res = trackcount; } catch (int _res) { res = _res; } if (cd != nullptr) { cd_delete(cd); } return res; } int check_cuesheet(const std::string & filename, void *buf, fuse_fill_dir_t filler) { std::string trackdir(filename); // Tracks directory (with extra TRACKDIR extension) std::string cuesheet(filename); // Cue sheet name (original name, extension replaced by .cue) struct stat stbuf; int res = 0; append_ext(&trackdir, TRACKDIR); // Need to add TRACKDIR to file name append_sep(&trackdir); replace_ext(&cuesheet, "cue"); // Get the cue sheet file name by replacing the extension with .cue try { LPCVIRTUALFILE virtualfile = find_file_from_orig(filename); if (virtualfile == nullptr) { // Should never happen... Logging::error(filename, "INTERNAL ERROR: check_cuesheet()! virtualfile is NULL."); errno = EINVAL; throw -errno; } if (stat(filename.c_str(), &stbuf) == -1) { // Media file does not exist, can be ignored silently throw 0; } if (stat(cuesheet.c_str(), &stbuf) != -1) { // Cue sheet file exists, preferrably use its contents if (!check_path(trackdir)) { // Not a virtual directory res = parse_cuesheet_file(virtualfile, cuesheet, &stbuf, buf, filler); Logging::trace(cuesheet, "%1 titles were discovered.", res); } else { // Obviously a virtual directory, simply add it std::string dirname(trackdir); remove_path(&dirname); LPCVIRTUALFILE virtualdir = find_file(trackdir); if (virtualdir == nullptr) { Logging::error(filename, "INTERNAL ERROR: check_cuesheet()! virtualdir is NULL."); errno = EIO; throw -errno; } if (buf != nullptr && filler(buf, dirname.c_str(), &virtualdir->m_st, 0, FUSE_FILL_DIR_PLUS)) { // break; } res = 0; } } else if (!virtualfile->m_cuesheet.empty()) { // No cue sheet file, but there is one embedded in media file res = parse_cuesheet_text(virtualfile, buf, filler); } } catch (int _res) { res = _res; } return res; } ffmpegfs-2.50/src/cache.h0000664000175000017500000004067115177713600010742 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cache.h * @brief Data cache management * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef CACHE_H #define CACHE_H #pragma once #include "buffer.h" #include #include #define DB_BASE_VERSION_MAJOR 1 /**< @brief The oldest database version major (Release < 1.95) */ #define DB_BASE_VERSION_MINOR 0 /**< @brief The oldest database version minor (Release < 1.95) */ #define DB_VERSION_MAJOR 1 /**< @brief Current database version major */ #define DB_VERSION_MINOR 97 /**< @brief Current database version minor */ #define DB_MIN_VERSION_MAJOR 1 /**< @brief Required database version major (required 1.95) */ #define DB_MIN_VERSION_MINOR 97 /**< @brief Required database version minor (required 1.95) */ typedef struct sqlite3 sqlite3; /**< @brief Forward declaration of sqlite3 handle */ typedef struct sqlite3_stmt sqlite3_stmt; /**< @brief Forward declaration of sqlite3 statement handle */ /** * @brief RESULTCODE of transcoding operation */ enum class RESULTCODE { NONE, /**< @brief No result code available */ FINISHED_INCOMPLETE, /**< @brief Transcode finished, but incomplete */ FINISHED_SUCCESS, /**< @brief Transcode finished successfully */ FINISHED_ERROR, /**< @brief Transcode finished with error */ }; typedef RESULTCODE const *LPCRESULTCODE; /**< @brief Pointer version of RESULTCODE */ typedef RESULTCODE *LPRESULTCODE; /**< @brief Pointer to const version of RESULTCODE */ /** * @brief Cache information block */ typedef struct CACHE_INFO { std::string m_origfile; /**< @brief Original filename before transcode */ std::string m_destfile; /**< @brief Destination filename after transcode */ std::array m_desttype; /**< @brief Destination type */ int64_t m_audiobitrate; /**< @brief Audio bitrate in bit/s */ int m_audiosamplerate; /**< @brief Audio sample rate in Hz */ int64_t m_videobitrate; /**< @brief Video bitrate in bit/s */ int m_videowidth; /**< @brief Video width */ int m_videoheight; /**< @brief Video height */ bool m_deinterlace; /**< @brief true if video was deinterlaced */ int64_t m_duration; /**< @brief File duration, in AV_TIME_BASE fractional seconds. */ size_t m_predicted_filesize; /**< @brief Predicted file size */ size_t m_encoded_filesize; /**< @brief Actual file size after encode */ uint32_t m_video_frame_count; /**< @brief Number of frames in video or 0 if not a video */ uint32_t m_segment_count; /**< @brief Number of segments for HLS */ RESULTCODE m_result; /**< @brief Result code: */ bool m_error; /**< @brief true if encode failed */ int m_errno; /**< @brief errno if encode failed */ int m_averror; /**< @brief FFmpeg error code if encode failed */ time_t m_creation_time; /**< @brief Source file creation time */ time_t m_access_time; /**< @brief Source file last access time */ time_t m_file_time; /**< @brief Source file file time */ size_t m_file_size; /**< @brief Source file file size */ unsigned int m_access_count; /**< @brief Read access counter */ } CACHE_INFO; typedef CACHE_INFO const *LPCCACHE_INFO; /**< @brief Pointer version of CACHE_INFO */ typedef CACHE_INFO *LPCACHE_INFO; /**< @brief Pointer to const version of CACHE_INFO */ class Cache_Entry; /** * @brief The #Cache class */ class Cache { typedef std::pair cache_key_t; /**< @brief Filenames and destination types */ typedef std::map cache_t; /**< @brief Map of cache entries */ public: /** * @brief Definition of sql table */ typedef struct { const char * name; /**< @brief Table name */ const char * primary_key; /**< @brief Primary key of table */ } TABLE_DEF; typedef TABLE_DEF const *LPCTABLE_DEF; /**< @brief Pointer version of TABLE_DEF */ typedef TABLE_DEF *LPTABLE_DEF; /**< @brief Pointer to const version of TABLE_DEF */ /** * @brief Column definition of sql table */ typedef struct { const char * name; /**< @brief Column name */ const char * type; /**< @brief Column type (INT, CHAR etc) */ } TABLE_COLUMNS; typedef TABLE_COLUMNS const *LPCTABLE_COLUMNS; /**< @brief Pointer version of TABLE_COLUMNS */ typedef TABLE_COLUMNS *LPTABLE_COLUMNS; /**< @brief Pointer to const version of TABLE_COLUMNS */ typedef std::vector TABLECOLUMNS_VEC; /**< @brief Table columns array */ typedef TABLECOLUMNS_VEC const *LPCTABLECOLUMNS_VEC; /**< @brief Pointer version of TABLECOLUMNS_VEC */ typedef TABLECOLUMNS_VEC *LPTABLECOLUMNS_VEC; /**< @brief Pointer to const version of TABLECOLUMNS_VEC */ friend class Cache_Entry; /** * @brief The sqlite_t class * Wrapper for sqlite3 struct to make use of std::shared_ptr */ class sqlite_t { public: /** * @brief Construct #sqlite_t object * @param[in] filename - Database filename (UTF-8) * @param[in] flags - Flags * @param[in] zVfs - Name of VFS module to use */ explicit sqlite_t(const std::string & filename, int flags, const char *zVfs = nullptr); /** * @brief Free #sqlite_t object */ virtual ~sqlite_t(); /** * @brief Return code of last Sqlite operation * @return Returns result code of last Sqlite operation */ int ret() const { return m_ret; }; #ifdef HAVE_SQLITE_CACHEFLUSH /** * @brief Flush cache index to disk. * @return Returns true on success; false on error. */ bool flush_index(); #endif // HAVE_SQLITE_CACHEFLUSH /** * @brief operator sqlite_t * * Default return operator. * @return Returns sqlite_t handle. May be nullptr if invalid. */ operator sqlite3*() { return m_db_handle; }; /** * @brief Get current database file name * @return Returns current database file name */ const std::string & filename() const { return m_filename; }; protected: int m_ret; /**< @brief Return code of last SQL operation */ std::string m_filename; /**< @brief Name of SQLite cache index database */ sqlite3* m_db_handle; /**< @brief SQLite handle of cache index database */ public: sqlite3_stmt * m_select_stmt; /**< @brief Prepared select statement */ sqlite3_stmt * m_insert_stmt; /**< @brief Prepared insert statement */ sqlite3_stmt * m_delete_stmt; /**< @brief Prepared delete statement */ }; public: /** * @brief Construct #Cache object. */ explicit Cache(); /** * @brief Destruct #Cache object. */ virtual ~Cache(); /** * @brief Open cache entry. * * Opens a cache entry and opens the cache file. * * @param[in] virtualfile - VIRTUALFILE struct of a file. * @return On success, returns pointer to a Cache_Entry. On error, returns nullptr. */ Cache_Entry * openio(LPVIRTUALFILE virtualfile); /** * @brief Close a cache entry. * * If the cache entry is in use will not be deleted. * * @param[in, out] cache_entry - Cache entry object to be closed. * @param[in] flags - One of the CACHE_CLOSE_* flags. * @return Returns true if the object was deleted; false if not. */ bool closeio(Cache_Entry **cache_entry, int flags = CACHE_CLOSE_NOOPT); /** * @brief Load cache index from disk. * @return Returns true on success; false on error. */ bool load_index(); /** * @brief Run disk maintenance. * * Can be done before a new file is added. Set predicted_filesize to make sure disk space * or cache size will be kept within limits. * * @param[in] predicted_filesize - Size of new file * @return Returns true on success; false on error. */ bool maintenance(size_t predicted_filesize = 0); /** * @brief Clear cache: deletes all entries. * @return Returns true on success; false on error. */ bool clear(); /** * @brief Prune expired cache entries. * @return Returns true on success; false on error. */ bool prune_expired(); /** * @brief Prune cache entries to keep cache size within limit. * @return Returns true on success; false on error. */ bool prune_cache_size(); /** * @brief Prune cache entries to ensure disk space. * @return Returns true on success; false on error. */ bool prune_disk_space(size_t predicted_filesize); /** * @brief Remove a cache file from disk. * @param[in] filename - Source file name. * @param[in] fileext - File extension of target file. * @return Returns true on success; false on error. */ bool remove_cachefile(const std::string & filename, const std::string &fileext); protected: /** * @brief Read cache file info. * @param[in] cache_info - Structure with cache info data. * @return Returns true on success; false on error. */ bool read_info(LPCACHE_INFO cache_info); /** * @brief Write cache file info. * @param[in] cache_info - Structure with cache info data. * @return Returns true on success; false on error. */ bool write_info(LPCCACHE_INFO cache_info); /** * @brief Delete cache file info. * @param[in] filename - Source file name. * @param[in] desttype - Destination type (MP4, WEBM etc.). * @return Returns true on success; false on error. */ bool delete_info(const std::string & filename, const std::string & desttype); /** * @brief Create cache entry object for a VIRTUALFILE. * @param[in] virtualfile - VIRTUALFILE struct of a file. * @param[in] desttype - Destination type (MP4, WEBM etc.). * @return On success, returns pointer to a Cache_Entry. On error, returns nullptr. */ Cache_Entry* create_entry(LPVIRTUALFILE virtualfile, const std::string & desttype); /** * @brief Delete cache entry object. * @param[in, out] cache_entry - Cache entry object to be closed. * @param[in] flags - One of the CACHE_CLOSE_* flags. * @return Returns true if the object was deleted; false if not. */ bool delete_entry(Cache_Entry **cache_entry, int flags); /** * @brief Close cache index. */ void close_index(); /** * @brief Get expanded SQL string for a statement. * @param[in] pStmt - SQLite statement handle. * @return Returns the SQL string bound to the statement handle. */ std::string expanded_sql(sqlite3_stmt *pStmt); /** * @brief Prepare all SQL statements * @return Returns true on success, false on error. */ bool prepare_stmts(); /** * @brief Check if SQL table exists in database. * @param[in] table - name of table * @return Returns true if table exists, false if not. */ bool table_exists(const char *table); /** * @brief Check if column exists in SQL table. * @param[in] table - name of table * @param[in] column - name of column * @return Returns true if table exists, false if not. */ bool column_exists(const char *table, const char *column); /** * @brief Check the db version if upgrade needed. * @param[out] db_version_major - Upon return, contains the major database version. * @param[out] db_version_minor - Upon return, contains the minor database version. * @return Returns true of version is OK, false if upgrade is needed. */ bool check_min_version(int *db_version_major, int *db_version_minor); /** * @brief Compare two versions. * @param[in] version_major_l - Left major version * @param[in] version_minor_l - Left minor version * @param[in] version_major_r - Right major version * @param[in] version_minor_r - Right minor version * @return Returns +1 if left version is larger then right, 0 if versions are the same, -1 if right version is larger than left. */ int cmp_version(int version_major_l, int version_minor_l, int version_major_r, int version_minor_r); /** * @brief Begin a database transactio.n * @return Returns true on success; false on error. */ bool begin_transaction(); /** * @brief End a database transaction. * @return Returns true on success; false on error. */ bool end_transaction(); /** * @brief Rollback a database transaction. * @return Returns true on success; false on error. */ bool rollback_transaction(); /** * @brief Create cache_entry table * @return Returns true on success; false on error. */ bool create_table_cache_entry(LPCTABLE_DEF table, const TABLECOLUMNS_VEC & columns); /* */ /** * @brief Upgrade database from version below 1.95 * @param[out] db_version_major - Upon return, contains the new major database version. * @param[out] db_version_minor - Upon return, contains the new minor database version. * @return Returns true on success; false on error. */ bool upgrade_db(int *db_version_major, int *db_version_minor); private: static const TABLE_DEF m_table_cache_entry; /**< @brief Definition and indexes of table "cache_entry" */ static const TABLECOLUMNS_VEC m_columns_cache_entry; /**< @brief Columns of table "cache_entry" */ static const TABLE_DEF m_table_version; /**< @brief Definition and indexes of table "version" */ static const TABLECOLUMNS_VEC m_columns_version; /**< @brief Columns of table "version" */ std::recursive_mutex m_mutex; /**< @brief Access mutex */ std::unique_ptr m_cacheidx_db; /**< @brief SQLite handle of cache index database */ cache_t m_cache; /**< @brief Cache file (memory mapped file) */ }; #endif ffmpegfs-2.50/src/aiff.h0000664000175000017500000000747115177713600010605 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file aiff.h * @brief AIFF file structures * http://paulbourke.net/dataformats/audio/ * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef AIFF_H #define AIFF_H #pragma once #include #include #pragma pack(push, 1) typedef std::array AIFF_ID; /**< @brief AIFF fourcc ID */ #define AIFF_FORMID "FORM" /**< @brief ckID for Form Chunk */ /** * AIFF format chunk */ typedef struct { AIFF_ID m_ckID; /**< @brief Chunk ID, always "FORM" */ uint32_t m_ckSize; /**< @brief Total file size - 8 */ AIFF_ID formType; /**< @brief */ //uint8_t chunks[]; } AIFF_FORMCHUNK; /** * AIFF chunk (anything else than the special chunks like form, common etc.) */ typedef struct { AIFF_ID m_ckID; /**< @brief Chunk ID */ uint32_t m_ckSize; /**< @brief Size of this chunk - 8 */ //uint8_t data[]; } AIFF_CHUNK; #define AIFF_COMMONID "COMM" /**< @brief ckID for Common Chunk */ /** * AIFF Common chunk */ typedef struct { AIFF_ID m_ckID; /**< @brief Chunk ID, always "COMM" */ uint32_t m_ckSize; /**< @brief Size of this chunk - 8 */ uint8_t m_numChannels; /**< @brief Number of audio channels for the sound. */ uint32_t m_numSampleFrames; /**< @brief Number of sample frames in the sound data chunk. */ uint8_t m_sampleSize; /**< @brief Number of bits in each sample point. */ //extended sampleRate; } AIFF_COMMONCHUNK; #define AIFF_SOUNDATAID "SSND" /**< @brief ckID for Sound Data Chunk */ /** * AIFF sound data chunk */ typedef struct { AIFF_ID m_ckID; /**< @brief Chunk ID, always "SSND" */ uint32_t m_ckSize; /**< @brief Total size of sound data chunk - 8 */ uint32_t m_offset; /**< @brief Determines where the first sample frame in the soundData starts. */ uint32_t m_blockSize; /**< @brief Contains the size in bytes of the blocks that sound data is aligned to. */ //uint8_t soundData[]; } AIFF_SOUNDDATACHUNK; #define AIFF_NAMEID "NAME" /**< @brief ckID for Name Chunk. */ #define AIFF_AUTHORID "AUTH" /**< @brief ckID for Author Chunk. */ #define AIFF_COPYRIGHTID "(c) " /**< @brief ckID for Copyright Chunk. */ #define AIFF_ANNOTATIONID "ANNO" /**< @brief ckID for Annotation Chunk. */ /** * AIFF name chunk */ typedef struct { AIFF_ID m_ckID; /**< @brief Chunk ID, one of "NAME", "AUTH", "(c) ", "ANNO" */ uint32_t m_ckSize; /**< @brief Size of this chunk - 8 */ //uint8_t text[]; } AIFF_TEXTCHUNK; #pragma pack(pop) #endif // AIFF_H ffmpegfs-2.50/src/ffmpeg_utils.h0000664000175000017500000012277015177713600012364 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_utils.h * @brief Various FFmpegfs utility functions * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_UTILS_H #define FFMPEG_UTILS_H #pragma once #ifdef HAVE_CONFIG_H #include "config.h" #endif #if !defined(EXTRA_VERSION) #define FFMPEFS_VERSION PACKAGE_VERSION /**< @brief FFmpegfs version number */ #else #define FFMPEFS_VERSION PACKAGE_VERSION "-" EXTRA_VERSION /**< @brief FFmpegfs version number */ #endif #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS 1 /**< @brief Force PRId64 defines */ #endif // !__STDC_FORMAT_MACROS #include "ffmpeg_compat.h" #include #include #include #include #include #include #include #ifndef PATH_MAX #include #endif #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #define INVALID_STREAM -1 /**< @brief Denote an invalid stream */ #ifndef LIBAVUTIL_VERSION_MICRO #error "LIBAVUTIL_VERSION_MICRO not defined. Missing include header?" #endif /** * Allow use of av_format_inject_global_side_data when available */ #define HAVE_AV_FORMAT_INJECT_GLOBAL_SIDE_DATA (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 64, 101) && LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(60, 14, 100)) /** * Add av_get_media_type_string function if missing */ #define HAVE_MEDIA_TYPE_STRING (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 34, 101)) #if HAVE_MEDIA_TYPE_STRING /** * Map to av_get_media_type_string function. */ #define get_media_type_string av_get_media_type_string #else /** * @brief av_get_media_type_string is missing, so we provide our own. * @param[in] media_type - Media type to map. * @return Pointer to media type string. */ const char *get_media_type_string(enum AVMediaType media_type); #endif /** * Min. FFmpeg version for VULKAN hardware acceleration support * 2020-02-04 - xxxxxxxxxx - lavu 56.39.100 - hwcontext.h * Add AV_PIX_FMT_VULKAN * Add AV_HWDEVICE_TYPE_VULKAN and implementation. */ #define HAVE_VULKAN_HWACCEL (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(56, 39, 100)) #ifndef AV_ROUND_PASS_MINMAX /** * Ignore if this is missing */ #define AV_ROUND_PASS_MINMAX 0 #endif // These once had a different name #if !defined(AV_CODEC_CAP_TRUNCATED) && defined(CODEC_CAP_TRUNCATED) #define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED /**< @brief AV_CODEC_CAP_TRUNCATED is missing in older FFmpeg versions */ #endif #if !defined(AV_CODEC_FLAG_TRUNCATED) && defined(CODEC_FLAG_TRUNCATED) #define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED /**< @brief AV_CODEC_FLAG_TRUNCATED is missing in older FFmpeg versions */ #endif #ifndef AV_CODEC_FLAG_GLOBAL_HEADER #define AV_CODEC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER /**< @brief AV_CODEC_FLAG_GLOBAL_HEADER is missing in older FFmpeg versions */ #endif #ifndef FF_INPUT_BUFFER_PADDING_SIZE #define FF_INPUT_BUFFER_PADDING_SIZE 256 /**< @brief FF_INPUT_BUFFER_PADDING_SIZE is missing in newer FFmpeg versions */ #endif #ifndef AV_CODEC_CAP_VARIABLE_FRAME_SIZE #define AV_CODEC_CAP_VARIABLE_FRAME_SIZE CODEC_CAP_VARIABLE_FRAME_SIZE /**< @brief AV_CODEC_CAP_VARIABLE_FRAME_SIZE is missing in older FFmpeg versions */ #endif #if (LIBAVUTIL_VERSION_MAJOR > 54) #define BITRATE int64_t /**< @brief For FFmpeg bit rate is an int64. */ #else #define BITRATE int /**< @brief For FFmpeg bit rate is an int. */ #endif #define SAFE_VALUE(p, v, d) (((p) != nullptr) ? (p)->v : d) /**< @brief Access struct/class pointer safely, return default if nullptr */ /** * File types */ enum class FILETYPE { UNKNOWN, MP3, MP4, WAV, OGG, WEBM, MOV, AIFF, OPUS, PRORES, ALAC, PNG, JPG, BMP, TS, HLS, FLAC, MKV, }; /** * MP4/MOV/ALAC profiles */ enum class PROFILE { INVALID = -1, /**< @brief Profile is invalid */ DEFAULT = 0, /**< @brief No specific profile/Don't care */ // MP4 MP4_FF = 1, /**< @brief Firefox */ MP4_EDGE, /**< @brief MS Edge */ MP4_IE, /**< @brief MS Internet Explorer */ MP4_CHROME, /**< @brief Google Chrome */ MP4_SAFARI, /**< @brief Apple Safari */ MP4_OPERA, /**< @brief Opera */ MP4_MAXTHON, /**< @brief Maxthon */ // HLS/ts HLS_DEFAULT = DEFAULT, /**< @brief HLS/ts uses no profile */ // mov MOV_DEFAULT = DEFAULT, /**< @brief MOV uses no profile */ // MOV/ProRes PRORES_DEFAULT = DEFAULT, /**< @brief MOV/ProRes uses no profile */ // MOV/ProRes ALAC_DEFAULT = DEFAULT, /**< @brief Default profile */ ALAC_ITUNES = 10, /**< @brief Optimised for iTunes */ // WebM WEBM_DEFAULT = DEFAULT, /**< @brief WebM uses no profile */ }; /** * Prores levels */ enum class PRORESLEVEL { NONE = -1, /**< @brief No level */ // Prores profiles PRORES_PROXY = 0, /**< @brief Prores Level: PROXY */ PRORES_LT, /**< @brief Prores Level: LT */ PRORES_STANDARD, /**< @brief Prores Level: STANDARD */ PRORES_HQ, /**< @brief Prores Level: HQ */ }; /** * Auto copy options */ enum class AUTOCOPY { OFF = 0, /**< @brief Never copy streams, transcode always. */ MATCH, /**< @brief Copy stream if target supports codec. */ MATCHLIMIT, /**< @brief Same as MATCH, only copy if target not larger transcode otherwise. */ STRICT, /**< @brief Copy stream if codec matches desired target, transcode otherwise. */ STRICTLIMIT, /**< @brief Same as STRICT, only copy if target not larger, transcode otherwise. */ }; /** * Recode to same format options */ enum class RECODESAME { NO = 0, /**< @brief Never recode to same format. */ YES, /**< @brief Always recode to same format. */ }; /** * List of sample formats. * User selection, we don't care about planar or interleaved. */ enum class SAMPLE_FMT { FMT_DONTCARE = -1, /**< @brief Don't care, leave to FFmpegfs to choose */ FMT_8, /**< @brief 8 bit integer */ FMT_16, /**< @brief 16 bit integer */ FMT_24, /**< @brief 24 bit integer */ FMT_32, /**< @brief 32 bit integer */ FMT_64, /**< @brief 64 bit integer */ FMT_F16, /**< @brief 16 bit floating point */ FMT_F24, /**< @brief 24 bit floating point */ FMT_F32, /**< @brief 32 bit floating point */ FMT_F64 /**< @brief 64 bit floating point */ }; /** * Format options: Defines file extension, codecs etc. * for each format. */ struct Format_Options { friend class FFmpegfs_Format; typedef std::vector CODEC_VECT; /**< @brief Vector with valid codec ids for file format */ /** * Format options: Audio/video codecs and sample format */ typedef struct _tagFORMAT { CODEC_VECT m_video_codec; /**< @brief AVCodec used for video encoding */ CODEC_VECT m_audio_codec; /**< @brief AVCodec used for audio encoding */ CODEC_VECT m_subtitle_codec; /**< @brief AVCodec used for subtitle encoding */ AVSampleFormat m_sample_format; /**< @brief AVSampleFormat for audio encoding, may be AV_SAMPLE_FMT_NONE for "don't care" */ } FORMAT; typedef std::map FORMAT_MAP; /**< @brief Map of formats. One entry per format derivative. */ public: /** * @brief Construct Format_Options object with defaults (empty) */ Format_Options(); /** * @brief Construct Format_Options object * @param[in] format_name - Descriptive name of the format, e.g. "Opus Audio", * @param[in] fileext - File extension: mp4, mp3, flac or other * @param[in] format - Format options: Possible audio/video codecs and sample formats * @param[in] albumart_supported - true if album arts are supported (eg. mp3) or false if not (e.g. wav, aiff */ Format_Options(std::string format_name, std::string fileext, FORMAT_MAP format, bool albumart_supported ); /** * @brief Convert destination type to "real" type, i.e., the file extension to be used. * @note Currently "prores" is mapped to "mov". * @return Destination type */ const std::string & format_name() const; /** * @brief Get file extension * @return File extension */ const std::string & fileext() const; /** * @brief Get video codec_id * @return Returns video codec_id */ AVCodecID video_codec() const; /** * @brief Check if video codec/file format combination is supported * @param[in] codec_id - Codec ID to check * @return Returns true if supported, false if not. */ bool is_video_codec_supported(AVCodecID codec_id) const; /** * @brief Create a list of supported audio codecs for current audio codec * @return Returns comma separated list of formats, or empty if not available. */ std::string video_codec_list() const; /** * @brief Get audio codec_id * @return Returns audio codec_id */ AVCodecID audio_codec() const; /** * @brief Check if audio codec/file format combination is supported * @param[in] codec_id - Codec ID to check * @return Returns true if supported, false if not. */ bool is_audio_codec_supported(AVCodecID codec_id) const; /** * @brief Create a list of supported audio codecs for current audio codec * @return Returns comma separated list of formats, or empty if not available. */ std::string audio_codec_list() const; /** * @brief Get sample format (bit width) * @return Returns sample format */ AVSampleFormat sample_format() const; /** * @brief Check if audio codec/sample format combination is supported * @return Returns true if supported, false if not. */ bool is_sample_fmt_supported() const; /** * @brief Create a list of supported sample formats for current audio codec * @return Returns comma separated list of formats, or empty if not available. */ std::string sample_fmt_list() const; /** * @brief Get subtitle codec_id * @param[in] codec_id - Input stream codec ID * @return Returns subtitle codec_id that matches the input stream codec ID, or AV_CODEC_ID_NONE of no match. */ AVCodecID subtitle_codec(AVCodecID codec_id) const; protected: /** * @brief Descriptive name of the format. * Descriptive name of the format, e.g. "opus", "mpegts". * Please note that m_format_name is used to select the FFmpeg container * by passing it to avformat_alloc_output_context2(). * Mostly, but not always, same as m_fileext. */ std::string m_format_name; std::string m_fileext; /**< @brief File extension: mp4, mp3, flac or other. Mostly, but not always, same as m_format_name. */ FORMAT_MAP m_format_map; /**< @brief Format definition (audio/videocodec, sample format) */ bool m_albumart_supported; /**< @brief true if album arts are supported (eg. mp3) or false if not (e.g. wav, aiff) */ }; /** * @brief The #FFmpegfs_Format class */ class FFmpegfs_Format { typedef std::map OPTIONS_MAP; /**< @brief Map of options. One entry per supported destination type. */ public: /** * @brief Construct FFmpegfs_Format object */ FFmpegfs_Format(); /** * @brief Get codecs for the selected destination type. * @param[in] desttype - Destination type (MP4, WEBM etc.). * @return Returns true if format was found; false if not. */ bool init(const std::string & desttype); /** * @brief Convert destination type to "real" type, i.e., the file extension to be used. * @note Currently "prores" is mapped to "mov". * @return Destination type */ const std::string & format_name() const; /** * @brief Get destination type * @return Destination type */ const std::string & desttype() const; /** * @brief Get file extension * @return File extension */ const std::string & fileext() const; /** * @brief Get selected filetype. * @return Returns selected filetype. */ FILETYPE filetype() const; /** * @brief Get video codec_id * @return Returns video codec_id */ AVCodecID video_codec() const; /** * @brief Check if video codec/file format combination is supported * @param[in] codec_id - Codec ID to check * @return Returns true if supported, false if not. */ bool is_video_codec_supported(AVCodecID codec_id) const; /** * @brief Create a list of supported audio codecs for current audio codec * @return Returns comma separated list of formats, or empty if not available. */ std::string video_codec_list() const; /** * @brief Get audio codec_id * @return Returns audio codec_id */ AVCodecID audio_codec() const; /** * @brief Check if audio codec/file format combination is supported * @param[in] codec_id - Codec ID to check * @return Returns true if supported, false if not. */ bool is_audio_codec_supported(AVCodecID codec_id) const; /** * @brief Create a list of supported audio codecs for current audio codec * @return Returns comma separated list of formats, or empty if not available. */ std::string audio_codec_list() const; /** * @brief Get sample format (bit width) * @return Returns sample format */ AVSampleFormat sample_format() const; /** * @brief Check if audio codec/sample format combination is supported * @return Returns true if supported, false if not. */ bool is_sample_fmt_supported() const; /** * @brief Create a list of supported sample formats for current audio codec * @return Returns comma separated list of formats, or empty if not available. */ std::string sample_fmt_list() const; /** * @brief Get subtitle codec_id * @param[in] codec_id - Input stream codec ID * @return Returns subtitle codec_id that matches the input stream codec ID, or AV_CODEC_ID_NONE of no match. */ AVCodecID subtitle_codec(AVCodecID codec_id) const; /** * @brief Check if this is some sort of multi file format * (any of the following: is_frameset() or is_hls()). * @return Returns true for a multi file format. */ bool is_multiformat() const; /** * @brief Check for an export frame format * @return Returns true for formats that export all frames as images. */ bool is_frameset() const; /** * @brief Check for HLS format * @return Returns true for formats that create an HLS set including the m3u file. */ bool is_hls() const; /** * @brief Check if album arts are supported * @return true if album arts are supported or false if not */ bool albumart_supported() const; protected: const Format_Options m_empty_options; /**< @brief Set of empty (invalid) options as default */ const Format_Options * m_cur_opts; /**< @brief Currently selected options. Will never be nullptr */ static const OPTIONS_MAP m_options_map; /**< @brief Map of options. One entry per supported destination type. */ std::string m_desttype; /**< @brief Destination type: mp4, mp3 or other */ FILETYPE m_filetype; /**< @brief File type, MP3, MP4, OPUS etc. */ }; /** * @brief The FORMAT enum */ typedef enum _tagFORMAT // Cannot use enum class, won't work as array index { VIDEO, /**< @brief FFmpegfs_Format info, 0: video file */ AUDIO /**< @brief FFmpegfs_Format info, 1: audio file */ } FORMAT; typedef std::array FFMPEGFS_FORMAT_ARR; /**< @brief Array of FFmpegfs formats. There are two, for audio and video */ /** * @brief Add / to the path if required * @param[in] path - Path to add separator to. * @return Returns constant reference to path. */ const std::string & append_sep(std::string * path); /** * @brief Add filename to path, including / after the path if required * @param[in] path - Path to add filename to. * @param[in] filename - File name to add. * @return Returns constant reference to path. */ const std::string & append_filename(std::string * path, const std::string & filename); /** * @brief Remove / from the path * @param[in] path - Path to remove separator from. * @return Returns constant reference to path. */ const std::string & remove_sep(std::string * path); /** * @brief Remove filename from path. Handy dirname alternative. * @param[in] filepath - Path to remove filename from. * @return Returns constant reference to path. */ const std::string & remove_filename(std::string *filepath); /** * @brief Remove path from filename. Handy basename alternative. * @param[in] filepath - Filename to remove path from. * @return Returns constant reference to filename. */ const std::string & remove_path(std::string *filepath); /** * @brief Remove extension from filename. * @param[in] filepath - Filename to remove path from. * @return Returns constant reference to filename. */ const std::string & remove_ext(std::string *filepath); /** * @brief Find extension in filename, if existing. * @param[in] ext - Extension, if found. * @param[in] filename - Filename to inspect. * @return Returns true if extension was found, false if there was none */ bool find_ext(std::string * ext, const std::string & filename); /** * @brief Check if filename has a certain extension. The check is case sensitive. * @param ext - Extension to check. * @param filename - Filename to check * @return Returns true if extension matches, false if not */ bool check_ext(const std::string & ext, const std::string & filename); /** * @brief Replace extension in filename, taking into account that there might not be an extension already. * @param[in] filepath - Filename to replace extension. * @param[in] ext - Extension to replace. * @return Returns constant reference to filename. */ const std::string & replace_ext(std::string * filepath, const std::string & ext); /** * @brief Append extension to filename. If ext is the same as * @param[in] filepath - Filename to add extension to. * @param[in] ext - Extension to add. * @return Returns constant reference to filename. */ const std::string & append_ext(std::string * filepath, const std::string & ext); /** * @brief strdup() variant taking a std::string as input. * @param[in] str - String to duplicate. * @return Copy of the input string. Remember to delete the allocated memory. */ std::shared_ptr new_strdup(const std::string & str); /** * @brief Get FFmpeg error string for errnum. Internally calls av_strerror(). * @param[in] errnum - FFmpeg error code. * @return Returns std::string with the error defined by errnum. */ std::string ffmpeg_geterror(int errnum); /** * @brief Convert a FFmpeg time from in timebase to outtime base. * * If out time base is omitted, returns standard AV_TIME_BASE fractional seconds * Avoids conversion of AV_NOPTS_VALUE. * * @param[in] ts - Time in input timebase. * @param[in] timebase_in - Input timebase. * @param[in] timebase_out - Output timebase, defaults to AV_TIMEBASE if unset. * @return Returns converted value, or AV_NOPTS_VALUE if ts is AV_NOPTS_VALUE. */ int64_t ffmpeg_rescale_q(int64_t ts, const AVRational & timebase_in, const AVRational & timebase_out = av_get_time_base_q()); /** * @brief Convert a FFmpeg time from in timebase to out timebase with rounding. * * If out time base is omitted, returns standard AV_TIME_BASE fractional seconds * Avoids conversion of AV_NOPTS_VALUE. * * @param[in] ts - Time in input timebase. * @param[in] timebase_in - Input timebase. * @param[in] timebase_out - Output timebase, defaults to AV_TIMEBASE if unset. * @return Returns converted value, or AV_NOPTS_VALUE if ts is AV_NOPTS_VALUE. */ int64_t ffmpeg_rescale_q_rnd(int64_t ts, const AVRational & timebase_in, const AVRational & timebase_out = av_get_time_base_q()); /** * @brief Format numeric value. * @param[in] value - Value to format. * @return Returns std::string with formatted value; if value == AV_NOPTS_VALUE returns "unset"; "unlimited" if value == 0. */ std::string format_number(int64_t value); /** * @brief Format a bit rate. * @param[in] value - Bit rate to format. * @return Returns std::string with formatted value in bit/s, kbit/s or Mbit/s. If value == AV_NOPTS_VALUE returns "unset". */ std::string format_bitrate(BITRATE value); /** * @brief Format a samplerate. * @param[in] value - Sample rate to format. * @return Returns std::string with formatted value in Hz or kHz. If value == AV_NOPTS_VALUE returns "unset". */ std::string format_samplerate(int value); /** * @brief Format a time in format HH:MM:SS.fract * @param[in] value - Time value in AV_TIME_BASE factional seconds. * @param[in] fracs - Fractional digits. * @return Returns std::string with formatted value. If value == AV_NOPTS_VALUE returns "unset". */ std::string format_duration(int64_t value, uint32_t fracs = 3); /** * @brief Format a time in format "w d m s". * @param[in] value - Time value in AV_TIME_BASE factional seconds. * @return Returns std::string with formatted value. If value == AV_NOPTS_VALUE returns "unset". */ std::string format_time(time_t value); /** * @brief Format size. * @param[in] value - Size to format. * @return Returns std::string with formatted value in bytes, KB, MB or TB; if value == AV_NOPTS_VALUE returns "unset"; "unlimited" if value == 0. */ std::string format_size(uint64_t value); /** * @brief Format size. * @param[in] value - Size to format. * @return Returns std::string with formatted value in bytes plus KB, MB or TB; if value == AV_NOPTS_VALUE returns "unset"; "unlimited" if value == 0. */ std::string format_size_ex(uint64_t value); /** * @brief Format size of transcoded file including difference between predicted and resulting size. * @param[in] size_resulting - Resulting size. * @param[in] size_predicted - Predicted size. * @return Returns std::string with formatted value in bytes plus difference; if value == AV_NOPTS_VALUE returns "unset"; "unlimited" if value == 0. */ std::string format_result_size(size_t size_resulting, size_t size_predicted); /** * @brief Format size of transcoded file including difference between predicted and resulting size. * @param[in] size_resulting - Resulting size. * @param[in] size_predicted - Predicted size. * @return Returns std::string with formatted value in bytes plus KB, MB or TB and difference; if value == AV_NOPTS_VALUE returns "unset"; "unlimited" if value == 0. */ std::string format_result_size_ex(size_t size_resulting, size_t size_predicted); /** * @brief Path to FFmpegfs binary. * @param[in] path - Path to FFmpegfs binary. */ void exepath(std::string *path); /** * @brief trim from start * @param[in] s - String to trim. * @return Reference to string s. */ std::string & ltrim(std::string &s); /** * @brief trim from end * @param[in] s - String to trim. * @return Reference to string s. */ std::string & rtrim(std::string &s); /** * @brief trim from both ends * @param[in] s - String to trim. * @return Reference to string s. */ std::string & trim(std::string &s); /** * @brief Same as std::string replace(), but replaces all occurrences. * @param[inout] str - Source string. * @param[in] from - String to replace. * @param[in] to - Replacement string. * @return Source string with all occurrences of from replaced with to. */ std::string replace_all(std::string str, const std::string& from, const std::string& to); /** * @brief Same as std::string replace(), but replaces string in-place. * @param[inout] str - Source string. * @param[in] from - String to replace. * @param[in] to - Replacement string. * @return Source string with all occurrences of from replaced with to. */ std::string replace_all(std::string * str, const std::string& from, const std::string& to = ""); /** * @brief Replace start of string from "from" to "to". * @param[inout] str - Source string. * @param[in] from - String to replace. * @param[in] to - Replacement string. * @return True if from has been replaced, false if not. */ bool replace_start(std::string *str, const std::string& from, const std::string& to = ""); /** * @brief Format a std::string sprintf-like. * @param[out] str - The pointer to std::string object where the resulting string is stored. * @param[in] format - sprintf-like format string. * @param[in] args - Arguments. * @return Returns the formatted string. */ template const std::string & strsprintf(std::string *str, const std::string & format, Args ... args) { size_t size = static_cast(snprintf(nullptr, 0, format.c_str(), args ...)) + 1; // Extra space for '\0' std::unique_ptr buf(new(std::nothrow) char[size]); std::snprintf(buf.get(), size, format.c_str(), args ...); str->clear(); str->insert(0, buf.get(), size - 1); // We don't want the '\0' inside return *str; } /** * @brief strcasecmp() equivalent for std::string. * @param[in] s1 - std:string #1 * @param[in] s2 - std:string #2 * @return Returns same as strcasecmp() for char *. */ int strcasecmp(const std::string & s1, const std::string & s2); /** * @brief Convert string to upper case * @param[inout] input String to convert */ void make_upper(std::string * input); /** * @brief Convert string to lower case * @param[inout] input String to convert */ void make_lower(std::string * input); /** * @brief Get info about the FFmpeg libraries used. * @return std::tring with info about the linked FFmpeg libraries. */ std::string ffmpeg_libinfo(); /** * @brief Lists all supported codecs and devices. * @param[in] device_only - If true lists devices only. * @return On success, returns 0; on error, a negative AVERROR value. */ int show_caps(int device_only); /** * @brief Safe way to get the codec name. Function never fails, will return "unknown" on error. * @param[in] codec_id - ID of codec * @param[in] long_name - If true, gets the long name. * @return Returns the codec name or "unknown" on error. */ const char * get_codec_name(AVCodecID codec_id, bool long_name = false); /** * @brief Check if file type supports album arts. * @param[in] filetype - File type: MP3, MP4 etc. * @return Returns true if album arts are supported, false if not. */ int supports_albumart(FILETYPE filetype); /** * @brief Get the FFmpegfs filetype, desttype must be one of FFmpeg's "official" short names for formats. * @param[in] desttype - Destination type (MP4, WEBM etc.). * @return On success, returns FILETYPE enum; On error, returns FILETYPE::UNKNOWN. */ FILETYPE get_filetype(const std::string & desttype); /** * @brief Convert FILETYPE enum to human readable text. * @param[in] filetype - FILETYPE enum value to convert. * @return FILETYPE enum as text or "INVALID" if not known. */ std::string get_filetype_text(FILETYPE filetype); /** * @brief Get the FFmpegfs filetype, desttypelist must be a comma separated list of FFmpeg's "official" short names for formats. * Will return the first match. Same as get_filetype, but accepts a comma separated list. * @param[in] desttypelist - Destination type list (MP4, WEBM etc.) separated by commas. * @return On success, returns FILETYPE enum; On error, returns FILETYPE::UNKNOWN. */ FILETYPE get_filetype_from_list(const std::string & desttypelist); /** * @brief Print info about an AVStream. * @param[in] stream - Stream to print. * @return On success, returns 0; on error, a negative AVERROR value. */ int print_stream_info(const AVStream* stream); /** * Fill the provided buffer with a string containing a FourCC (four-character * code) representation. * * @param[in] buf - Upon return, filled in with the FourCC representation. * @param[in] fourcc - The fourcc to represent * @return The buffer in input. */ std::string fourcc_make_string(std::string * buf, uint32_t fourcc); /** * @brief Compare value with pattern. * @param[in] value - Value to check. * @param[in] pattern - Regexp pattern to match. * @param[in] flag - On of the flag_type constants, see https://en.cppreference.com/w/cpp/regex/basic_regex for options. Mostly std::regex::icase is used to make matches case insensitive. * @return Returns 0 if pattern matches; 1 if not; -1 if pattern is no valid regex */ int reg_compare(const std::string &value, const std::string &pattern, std::regex::flag_type flag = std::regex::ECMAScript); /** * @brief Expand path, e.g., expand ~/ to home directory. * @param[out] tgt - Expanded source path. * @param[in] src - Path to expand. * @return On success, returns expanded source path. */ const std::string & expand_path(std::string *tgt, const std::string &src); /** * @brief Check if path is a mount. * @param[in] path - Path to check. * @return Returns 1 if path is a mount point; 0 if not. On error, returns -1. Check errorno for details. */ int is_mount(const std::string & path); /** * @brief Make directory tree. * @param[in] path - Path to create * @param[in] mode - Directory mode, see mkdir() function. * @return On success, returns 0; on error, returns non-zero errno value. */ int mktree(const std::string & path, mode_t mode); /** * @brief Get temporary directory. * @param[out] path - Path to temporary directory. */ void tempdir(std::string & path); /** * @brief Split string into an array delimited by a regular expression. * @param[in] input - Input string. * @param[in] regex - Regular expression to match. * @return Returns an array with separate elements. */ std::vector split(const std::string& input, const std::string & regex); /** * Safe countof() implementation: Retuns number of elements in an array. */ template constexpr std::size_t countof(T const (&)[N]) noexcept { return N; } /** * @brief Sanitise file name. Calls realpath() to remove duplicate // or resolve ../.. etc. * @param[in] filepath - File name and path to sanitise. * @return Returns sanitised file name and path. */ std::string sanitise_filepath(const std::string & filepath); /** * @brief Sanitise file name. Calls realpath() to remove duplicate // or resolve ../.. etc. * Changes the path in place. * @param[in] filepath - File name and path to sanitise. * @return Returns sanitised file name and path. */ std::string sanitise_filepath(std::string * filepath); /** * @brief Translate file names from FUSE to the original absolute path. * @param[out] origpath - Upon return, contains the name and path of the original file. * @param[in] path - Filename and relative path of the original file. */ void append_basepath(std::string *origpath, const char* path); /** * @brief Minimal check if codec is an album art. * Requires frame_rate to decide whether this is a video stream if codec_id is * not BMP or PNG (which means its undoubtedly an album art). For MJPEG this may * also be a video stream if the frame rate is high enough. * @param[in] codec_id - ID of codec. * @param[in] frame_rate - Video frame rate, if known. * @return Returns true if codec is for an image; false if not. */ bool is_album_art(AVCodecID codec_id, const AVRational *frame_rate = nullptr); /** * @brief nocasecompare to make std::string find operations case insensitive * @param[in] lhs - left hand string * @param[in] rhs - right hand string * @return -1 if lhs < rhs; 0 if lhs == rhs and 1 if lhs > rhs */ int nocasecompare(const std::string & lhs, const std::string &rhs); /** * @brief The comp struct to make std::string find operations case insensitive */ struct comp { /** * @brief operator () to make std::string find operations case insensitive * @param[in] lhs - left hand string * @param[in] rhs - right hand string * @return true if lhs < rhs; false if lhs >= rhs */ bool operator() (const std::string& lhs, const std::string& rhs) const { return (nocasecompare(lhs, rhs) < 0); } }; /** * @brief Get free disk space. * @param[in] path - Path or file on disk. * @return Returns the free disk space. */ size_t get_disk_free(std::string & path); /** * @brief For use with win_smb_fix=1: Check if this an illegal access offset by Windows * @param[in] size - sizeof of the file * @param[in] offset - offset at which file is accessed * @return If request should be ignored, returns true; otherwise false */ bool check_ignore(size_t size, size_t offset); /** * @brief Make a file name from file number and file extension. * @param[in] file_no - File number 1...n * @param[in] fileext - Extension of file (e.g mp4, webm) * @return Returns the file name. */ std::string make_filename(uint32_t file_no, const std::string &fileext); /** * @brief Check if file exists. * @param[in] filename - File to check. * @return Returns true if file exists, false if not. */ bool file_exists(const std::string & filename); /** Save version of hwdevice_get_type_name: * Get the string name of an AVHWDeviceType. * * @param[in] dev_type - Type from enum AVHWDeviceType. * @return Pointer to a static string containing the name, or "unknown" if the type * is not valid. */ const char * hwdevice_get_type_name(AVHWDeviceType dev_type); /** * Detected encoding types * Note: Muste be > 0! */ enum class ENCODING { ASCII = -1, /**< @brief Some sort of ASCII encoding. */ UTF8_BOM = -2, /**< @brief UTF-8 with bottom mark. */ UTF16LE_BOM = -3, /**< @brief UTF-16 little-endian with bottom mark. */ UTF16BE_BOM = -4, /**< @brief UTF-16 big-endian with bottom mark. */ UTF32LE_BOM = -5, /**< @brief UTF-16 little-endian with bottom mark. */ UTF32BE_BOM = -6, /**< @brief UTF-16 big-endian with bottom mark. */ }; /** * @brief Convert almost any encoding to UTF-8. * To get a list of all possible encodings run "iconv --list". * @param[in] text - Text to be converted * @param[in] encoding - Encoding of input text. * @return Returns 0 if successful and the converted text, * or errno value On error, and text is unchanged. */ int to_utf8(std::string & text, const std::string & encoding); /** * @brief Try to detect the encoding of str. This is relatively realiable, * but may be wrong. * @param[in] str - Text string to be checked. * @param[out] encoding - Detected encoding. * @return Returns 0 if successful, or CHARDET_OUT_OF_MEMORY/CHARDET_MEM_ALLOCATED_FAIL * on error. */ int get_encoding (const char * str, std::string & encoding); /** * @brief Read text file and return in UTF-8 format, no matter in which * encoding the input file is. UTF-8/16/32 with BOM will always return a * correct result. For all other encodings the function tries to detect it, * that may fail. * @param[in] path - Path and filename of input file * @param[out] result - File contents as UTF-8 * @return Returns one of the ENCODING enum values on success, which are always * negative; or errno on error. Basically a return code > 0 means there is an error. */ int read_file(const std::string & path, std::string & result); /** * @brief Properly fill in all size related members in stat struct * @param[inout] st stat structure to update * @param[in] size size value to copy */ void stat_set_size(struct stat *st, size_t size); /** * @brief Detect if we are running under Docker. * @return Returns true, if running under Docker, or false if not. */ bool detect_docker(); /** * @brief Iterate through all elements in map and search for the passed element. * @param[in] mapOfWords - map to search. * @param[in] value - Search value * @return If found, retuns const_iterator to element. Returns mapOfWords.cend() if not. */ template typename std::map::const_iterator search_by_value(const std::map & mapOfWords, T value) { typename std::map::const_iterator it = mapOfWords.cbegin(); while (it != mapOfWords.cend()) { if (it->second == value) { return it; } ++it; } return mapOfWords.cend(); } /** * @brief Check if subtitle codec is a text or graphical codec * @param[in] codec_id - Codec to check, must be one of the subtitle codecs. * @return Returns true if codec_id is a text based codec, false if it is bitmap based. */ bool is_text_codec(AVCodecID codec_id); /** * @brief Get first audio stream * @param[in] format_ctx - Format context of file * @param[out] channels - Number of audio channels in stream * @param[out] samplerate - Audio sample rate of stream * @return Returns stream number (value greater or equal zero) or negative errno value */ int get_audio_props(AVFormatContext *format_ctx, int *channels, int *samplerate); /** * @brief Escape characters that are meaningful to regexp. * @param[in] str - String to escape * @return Returns reference to string. */ const std::string & regex_escape(std::string *str); /** * @brief Find extension in include list, if existing. * @param[in] ext - Extension, if found. * @return Returns true if extension was found, false if there was none */ bool is_selected(const std::string & ext); /** * @brief Check if filename should be hidden from output path * @param[in] filename - Name to check * @return Returns true, if filename is blocked, false if not. */ bool is_blocked(const std::string & filename); typedef std::vector MATCHVEC; /**< @brief Array of strings, sorted/search case insensitive */ /** * @brief Combine array of strings into comma separated list. * @param[in] s - std::set object to combine * @return List of strings, separated by commas. */ template std::string implode(const T &s) { std::ostringstream stream; std::copy(s.begin(), s.end(), std::ostream_iterator(stream, ",")); std::string str(stream.str()); // Remove trailing , if (str.size()) { str.pop_back(); } return str; } /** * @brief Savely delete memory: Pointer will be set to nullptr before deleted is actually called. * @param[inout] p - Pointer to delete */ template void save_delete(T **p) { T * tmp = __atomic_exchange_n(p, nullptr, __ATOMIC_RELEASE); if (tmp != nullptr) { delete tmp; } } /** * @brief Savely free memory: Pointer will be set to nullptr before it is actually freed. * @param[inout] p - Pointer to delete */ void save_free(void **p); /** * @brief Sleep for specified time * @param milliseconds - Milliseconds to sleep */ void mssleep(int milliseconds); /** * @brief Sleep for specified time * @param microseconds - Microseconds to sleep */ void ussleep(int microseconds); /** * @brief Sleep for specified time * @param nanoseconds - Nanoseconds to sleep */ void nssleep(int nanoseconds); #endif ffmpegfs-2.50/src/fileio.cc0000664000175000017500000000566515177713600011310 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file fileio.cc * @brief FileIO class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "fileio.h" #include "ffmpegfs.h" #include "diskio.h" #ifdef USE_LIBVCD #include "vcdio.h" #endif // USE_LIBVCD #ifdef USE_LIBDVD #include "dvdio.h" #endif // USE_LIBDVD #ifdef USE_LIBBLURAY #include "blurayio.h" #endif // USE_LIBBLURAY uint32_t VIRTUALFILE::get_segment_count() const { if (m_duration && params.m_segment_duration) { return static_cast((m_duration - 200000) / params.m_segment_duration) + 1; } else { return 0; } } FileIO::FileIO() : m_virtualfile(nullptr) { } std::shared_ptr FileIO::alloc(VIRTUALTYPE type) { switch (type) { case VIRTUALTYPE::DISK: { return std::make_shared(); } #ifdef USE_LIBVCD case VIRTUALTYPE::VCD: { return std::make_shared(); } #endif // USE_LIBVCD #ifdef USE_LIBDVD case VIRTUALTYPE::DVD: { return std::make_shared(); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY case VIRTUALTYPE::BLURAY: { return std::make_shared(); } #endif // USE_LIBBLURAY default: { return std::make_shared(); } } } void FileIO::set_virtualfile(LPVIRTUALFILE virtualfile) { m_virtualfile = virtualfile; if (virtualfile != nullptr) { // Store path to original file without file name for fast access m_path = m_virtualfile->m_origfile; remove_filename(&m_path); } else { m_path.clear(); } } LPVIRTUALFILE FileIO::virtualfile() { return m_virtualfile; } const std::string & FileIO::path() const { return m_path; } const std::string & FileIO::filename() const { if (m_virtualfile == nullptr) { static const std::string empty; return empty; } return m_virtualfile->m_destfile; } ffmpegfs-2.50/src/ffmpeg_dictionary.h0000664000175000017500000000602315200152616013350 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_dictionary.h * @brief FFmpeg AVDictionary RAII wrapper. * * @ingroup ffmpegfs */ #ifndef FFMPEG_DICTIONARY_H #define FFMPEG_DICTIONARY_H #pragma once struct AVDictionary; /** * @brief RAII wrapper for AVDictionary. * * Owns an AVDictionary created by av_dict_set() / FFmpeg APIs and releases it * with av_dict_free(). The wrapper is movable but not copyable. */ class FFmpeg_Dictionary { public: /** * @brief Construct an empty dictionary wrapper. */ FFmpeg_Dictionary(); /** * @brief Release the owned dictionary, if any. */ ~FFmpeg_Dictionary(); FFmpeg_Dictionary(const FFmpeg_Dictionary&) = delete; FFmpeg_Dictionary& operator=(const FFmpeg_Dictionary&) = delete; /** * @brief Move-construct a dictionary wrapper. * @param[in,out] dict Source wrapper whose dictionary ownership is transferred. */ FFmpeg_Dictionary(FFmpeg_Dictionary&& dict) noexcept; /** * @brief Move-assign a dictionary wrapper. * @param[in,out] dict Source wrapper whose dictionary ownership is transferred. * @return Reference to this wrapper. */ FFmpeg_Dictionary& operator=(FFmpeg_Dictionary&& dict) noexcept; /** * @brief Free the owned dictionary and reset the wrapper to empty. */ void reset(); /** * @brief Check whether the wrapper currently owns a dictionary. * @return true if no dictionary is owned, false otherwise. */ bool empty() const; /** * @brief Get the owned FFmpeg dictionary pointer. * @return Mutable AVDictionary pointer, or nullptr if empty. */ AVDictionary* get(); /** * @brief Get the owned FFmpeg dictionary pointer. * @return Const AVDictionary pointer, or nullptr if empty. */ const AVDictionary* get() const; /** * @brief Get a writable pointer-to-pointer for FFmpeg APIs. * * Any currently owned dictionary is freed first so that FFmpeg can write a * fresh dictionary pointer without leaking the previous one. * * @return Address of the managed dictionary pointer. */ AVDictionary** address(); /** * @brief Release ownership without freeing the dictionary. * @return Previously owned AVDictionary pointer, or nullptr if empty. */ AVDictionary* release(); /** * @brief Convert to the underlying mutable dictionary pointer. */ operator AVDictionary*(); /** * @brief Convert to the underlying const dictionary pointer. */ operator const AVDictionary*() const; private: AVDictionary * m_dict; /**< @brief Pointer to underlying AVDictionary. */ }; #endif // FFMPEG_DICTIONARY_H ffmpegfs-2.50/src/ffmpeg_formatcontext.cc0000664000175000017500000000746315177713600014260 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_formatcontext.cc * @brief FFmpeg_FormatContext class implementation * * @ingroup ffmpegfs */ #ifdef __cplusplus extern "C" { #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #include #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_formatcontext.h" FFmpeg_FormatContext::FFmpeg_FormatContext(TYPE type) : m_context(nullptr), m_type(type), m_custom_io(false) { } FFmpeg_FormatContext::~FFmpeg_FormatContext() { reset(); } FFmpeg_FormatContext::FFmpeg_FormatContext(FFmpeg_FormatContext&& context) noexcept : m_context(context.m_context), m_type(context.m_type), m_custom_io(context.m_custom_io) { context.m_context = nullptr; context.m_custom_io = false; } FFmpeg_FormatContext& FFmpeg_FormatContext::operator=(FFmpeg_FormatContext&& context) noexcept { if (this != &context) { reset(); m_context = context.m_context; m_type = context.m_type; m_custom_io = context.m_custom_io; context.m_context = nullptr; context.m_custom_io = false; } return *this; } int FFmpeg_FormatContext::alloc_input_context() { reset(); m_type = TYPE::INPUT; m_context = avformat_alloc_context(); return (m_context != nullptr) ? 0 : AVERROR(ENOMEM); } int FFmpeg_FormatContext::alloc_output_context(const char *format_name, const char *filename) { reset(); m_type = TYPE::OUTPUT; int ret = avformat_alloc_output_context2(&m_context, nullptr, format_name, filename); if (ret < 0) { return ret; } return (m_context != nullptr) ? 0 : AVERROR(ENOMEM); } void FFmpeg_FormatContext::set_type(TYPE type) { m_type = type; } void FFmpeg_FormatContext::set_custom_io(bool custom_io) { m_custom_io = custom_io; } bool FFmpeg_FormatContext::reset() { bool closed = (m_context != nullptr); if (m_context != nullptr) { free_custom_io(); if (m_type == TYPE::OUTPUT) { avformat_free_context(m_context); m_context = nullptr; } else { avformat_close_input(&m_context); } } m_custom_io = false; return closed; } bool FFmpeg_FormatContext::empty() const { return (m_context == nullptr); } AVFormatContext* FFmpeg_FormatContext::get() { return m_context; } const AVFormatContext* FFmpeg_FormatContext::get() const { return m_context; } AVFormatContext** FFmpeg_FormatContext::address() { return &m_context; } AVFormatContext* FFmpeg_FormatContext::release() { AVFormatContext *context = m_context; m_context = nullptr; m_custom_io = false; return context; } FFmpeg_FormatContext::operator AVFormatContext*() { return m_context; } FFmpeg_FormatContext::operator const AVFormatContext*() const { return m_context; } AVFormatContext* FFmpeg_FormatContext::operator->() { return m_context; } const AVFormatContext* FFmpeg_FormatContext::operator->() const { return m_context; } void FFmpeg_FormatContext::free_custom_io() { if (m_custom_io && m_context != nullptr && m_context->pb != nullptr) { #if (LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 80, 0)) av_freep(&m_context->pb->buffer); avio_context_free(&m_context->pb); #else av_freep(m_context->pb); m_context->pb = nullptr; #endif } } ffmpegfs-2.50/src/ffmpeg_transcoder.cc0000664000175000017500000110632615207101114013507 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_transcoder.cc * @brief FFmpeg_Transcoder class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #include #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_transcoder.h" #include "ffmpeg_dictionary.h" #include "buffer.h" #include "wave.h" #include "aiff.h" #include "logging.h" #include "ffmpegfs.h" #include #include #include #define FRAME_SEEK_THRESHOLD 25 /**< @brief Ignore seek if target is within the next n frames */ const std::vector FFmpeg_Transcoder::m_prores_bitrate = { // SD { 720, 486, { { 24, false } }, { 10, 23, 34, 50, 75, 113 } }, { 720, 486, { { 60, true }, { 30, false } }, { 12, 29, 42, 63, 94, 141 } }, { 720, 576, { { 50, true }, { 25, false } }, { 12, 28, 41, 61, 92, 138 } }, { 960, 720, { { 24, false } }, { 15, 35, 50, 75, 113, 170 } }, { 960, 720, { { 25, false } }, { 16, 36, 52, 79, 118, 177 } }, { 960, 720, { { 30, false } }, { 19, 44, 63, 94, 141, 212 } }, { 960, 720, { { 50, false } }, { 32, 73, 105, 157, 236, 354 } }, { 960, 720, { { 60, false } }, { 38, 87, 126, 189, 283, 424 } }, // HD { 1280, 720, { { 24, false } }, { 18, 41, 59, 88, 132, 198 } }, { 1280, 720, { { 25, false } }, { 19, 42, 61, 92, 138, 206 } }, { 1280, 720, { { 30, false } }, { 23, 51, 73, 110, 165, 247 } }, { 1280, 720, { { 50, false } }, { 38, 84, 122, 184, 275, 413 } }, { 1280, 720, { { 60, false } }, { 45, 101, 147, 220, 330, 495 } }, { 1280, 1080, { { 24, false } }, { 31, 70, 101, 151, 226, 339 } }, { 1280, 1080, { { 60, true }, { 30, false } }, { 38, 87, 126, 189, 283, 424 } }, { 1440, 1080, { { 24, false } }, { 31, 70, 101, 151, 226, 339 } }, { 1440, 1080, { { 50, true }, { 25, false } }, { 32, 73, 105, 157, 236, 354 } }, { 1440, 1080, { { 60, true }, { 30, false } }, { 38, 87, 126, 189, 283, 424 } }, // Full HD { 1920, 1080, { { 24, false } }, { 36, 82, 117, 176, 264, 396 } }, { 1920, 1080, { { 50, true }, { 25, false } }, { 38, 85, 122, 184, 275, 413 } }, { 1920, 1080, { { 60, true }, { 30, false } }, { 45, 102, 147, 220, 330, 495 } }, { 1920, 1080, { { 50, false } }, { 76, 170, 245, 367, 551, 826 } }, { 1920, 1080, { { 60, false } }, { 91, 204, 293, 440, 660, 990 } }, // 2K { 2048, 1080, { { 24, false } }, { 41, 93, 134, 201, 302, 453 } }, { 2048, 1080, { { 25, false } }, { 43, 97, 140, 210, 315, 472 } }, { 2048, 1080, { { 30, false } }, { 52, 116, 168, 251, 377, 566 } }, { 2048, 1080, { { 50, false } }, { 86, 194, 280, 419, 629, 944 } }, { 2048, 1080, { { 60, false } }, { 103, 232, 335, 503, 754, 1131 } }, // 2K { 2048, 1556, { { 24, false } }, { 56, 126, 181, 272, 407, 611 } }, { 2048, 1556, { { 25, false } }, { 58, 131, 189, 283, 425, 637 } }, { 2048, 1556, { { 30, false } }, { 70, 157, 226, 340, 509, 764 } }, { 2048, 1556, { { 50, false } }, { 117, 262, 377, 567, 850, 1275 } }, { 2048, 1556, { { 60, false } }, { 140, 314, 452, 679, 1019, 1528 } }, // QFHD { 3840, 2160, { { 24, false } }, { 145, 328, 471, 707, 1061, 1591 } }, { 3840, 2160, { { 25, false } }, { 151, 342, 492, 737, 1106, 1659 } }, { 3840, 2160, { { 30, false } }, { 182, 410, 589, 884, 1326, 1989 } }, { 3840, 2160, { { 50, false } }, { 303, 684, 983, 1475, 2212, 3318 } }, { 3840, 2160, { { 60, false } }, { 363, 821, 1178, 1768, 2652, 3977 } }, // 4K { 4096, 2160, { { 24, false } }, { 155, 350, 503, 754, 1131, 1697 } }, { 4096, 2160, { { 25, false } }, { 162, 365, 524, 786, 1180, 1769 } }, { 4096, 2160, { { 30, false } }, { 194, 437, 629, 943, 1414, 2121 } }, { 4096, 2160, { { 50, false } }, { 323, 730, 1049, 1573, 2359, 3539 } }, { 4096, 2160, { { 60, false } }, { 388, 875, 1257, 1886, 2828, 4242 } }, // 5K { 5120, 2700, { { 24, false } }, { 243, 547, 786, 1178, 1768, 2652 } }, { 5120, 2700, { { 25, false } }, { 253, 570, 819, 1229, 1843, 2765 } }, { 5120, 2700, { { 30, false } }, { 304, 684, 982, 1473, 2210, 3314 } }, { 5120, 2700, { { 50, false } }, { 507, 1140, 1638, 2458, 3686, 5530 } }, { 5120, 2700, { { 60, false } }, { 608, 1367, 1964, 2946, 4419, 6629 } }, // 6K { 6144, 3240, { { 24, false } }, { 350, 788, 1131, 1697, 2545, 3818 } }, { 6144, 3240, { { 25, false } }, { 365, 821, 1180, 1769, 2654, 3981 } }, { 6144, 3240, { { 30, false } }, { 437, 985, 1414, 2121, 3182, 4772 } }, { 6144, 3240, { { 50, false } }, { 730, 1643, 2359, 3539, 5308, 7962 } }, { 6144, 3240, { { 60, false } }, { 875, 1969, 2828, 4242, 6364, 9545 } }, // 8K { 8192, 4320, { { 24, false } }, { 622, 1400, 2011, 3017, 4525, 6788 } }, { 8192, 4320, { { 25, false } }, { 649, 1460, 2097, 3146, 4719, 7078 } }, { 8192, 4320, { { 30, false } }, { 778, 1750, 2514, 3771, 5657, 8485 } }, { 8192, 4320, { { 50, false } }, { 1298, 2920, 4194, 6291, 9437, 14156 } }, { 8192, 4320, { { 60, false } }, { 1556, 3500, 5028, 7542, 11313, 16970 } } }; const FFmpeg_Transcoder::DEVICETYPE_MAP FFmpeg_Transcoder::m_devicetype_map = { { AV_HWDEVICE_TYPE_VAAPI, AV_PIX_FMT_NV12 }, ///< VAAPI uses the NV12 pix format #if 0 { AV_HWDEVICE_TYPE_CUDA, AV_PIX_FMT_CUDA }, ///< @todo HWACCEL - Cuda pix_fmt: to be added. { AV_HWDEVICE_TYPE_VDPAU, AV_PIX_FMT_YUV420P }, ///< @todo HWACCEL - VDPAU pix_fmt: to be added. { AV_HWDEVICE_TYPE_QSV, AV_PIX_FMT_QSV }, ///< @todo HWACCEL - QSV pix_fmt untested: Seems to be AV_PIX_FMT_P010 or AV_PIX_FMT_QSV. To be added. { AV_HWDEVICE_TYPE_OPENCL, AV_PIX_FMT_OPENCL }, ///< @todo HWACCEL - OpenCL pix_fmt: Seems to be AV_PIX_FMT_OPENCL or AV_PIX_FMT_NV12. To be added. #if HAVE_VULKAN_HWACCEL { AV_HWDEVICE_TYPE_VULKAN, AV_PIX_FMT_VULKAN }, ///< @todo HWACCEL - Vulkan pix_fmt: to be added. #endif // HAVE_VULKAN_HWACCEL #if __APPLE__ { AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX }, ///< Videotoolbox pix_fmt: MacOS acceleration APIs not supported #endif // __APPLE__ #if __ANDROID__ { AV_HWDEVICE_TYPE_MEDIACODEC, AV_PIX_FMT_MEDIACODEC }, ///< Mediacodec pix_fmt: Android acceleration APIs not supported #endif // __ANDROID__ #if _WIN32 { AV_HWDEVICE_TYPE_DRM, AV_PIX_FMT_DRM_PRIME }, ///< DRM prime pix_fmt: Windows acceleration APIs not supported { AV_HWDEVICE_TYPE_DXVA2, AV_PIX_FMT_DXVA2_VLD }, ///< DXVA2 pix_fmt: Windows acceleration APIs not supported { AV_HWDEVICE_TYPE_D3D11VA, AV_PIX_FMT_D3D11VA_VLD }, ///< D3D11VA pix_fmt: Windows acceleration APIs not supported #endif // _WIN32 #endif }; FFmpeg_Transcoder::StreamRef::StreamRef() : m_codec_ctx(nullptr), m_stream(nullptr), m_stream_idx(INVALID_STREAM), m_start_time(0) { } FFmpeg_Transcoder::StreamRef::~StreamRef() { reset(); } template< typename T > struct av_context_deleter /**< @brief Delete helper struct for std::shared_ptr */ { /** * @brief Delete for std::shared_ptr: Deletes the AVCodecContext pointer * @param[in] p - AVCodecContext pointer to delete */ void operator ()( T * p) const { avcodec_free_context(&p); } }; void FFmpeg_Transcoder::StreamRef::set_codec_ctx(AVCodecContext *codec_ctx) { if (codec_ctx != nullptr) { std::shared_ptr sp(codec_ctx, av_context_deleter()); m_codec_ctx = sp; } else { m_codec_ctx.reset(); } } void FFmpeg_Transcoder::StreamRef::reset() { m_codec_ctx.reset(); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" FFmpeg_Transcoder::FFmpeg_Transcoder() : m_fileio(nullptr) , m_last_seek_frame_no(0) , m_have_seeked(false) , m_skip_next_frame(false) , m_is_video(false) , m_cur_sample_fmt(AV_SAMPLE_FMT_NONE) , m_cur_sample_rate(-1) , m_buffer_sink_context(nullptr) , m_buffer_source_context(nullptr) , m_filter_graph(nullptr) , m_pts(AV_NOPTS_VALUE) , m_pos(AV_NOPTS_VALUE) , m_current_segment(1) , m_insert_keyframe(true) , m_copy_audio(false) , m_copy_video(false) , m_cur_audio_ts(0) , m_cur_video_ts(0) , m_current_format(nullptr) , m_buffer(nullptr) , m_reset_pts(0) , m_fake_frame_no(0) , m_hwaccel_enc_mode(HWACCELMODE::NONE) , m_hwaccel_dec_mode(HWACCELMODE::NONE) , m_hwaccel_enable_enc_buffering(false) , m_hwaccel_enable_dec_buffering(false) , m_hwaccel_enc_device_ctx(nullptr) , m_hwaccel_dec_device_ctx(nullptr) , m_enc_hw_pix_fmt(AV_PIX_FMT_NONE) , m_dec_hw_pix_fmt(AV_PIX_FMT_NONE) , m_active_stream_msk(0) , m_inhibit_stream_msk(0) { #pragma GCC diagnostic pop Logging::trace(nullptr, "The FFmpeg trancoder is ready to initialise."); std::memset(&m_mtime, 0, sizeof(m_mtime)); #if LAVU_DEP_OLD_CHANNEL_LAYOUT av_channel_layout_default(&m_cur_ch_layout, params.m_audiochannels); #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT m_cur_channel_layout = static_cast(av_get_default_channel_layout(params.m_audiochannels)); #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT // Initialise ID3v1.1 tag structure init_id3v1(&m_out.m_id3v1); } FFmpeg_Transcoder::~FFmpeg_Transcoder() { // Close files and resample context closeio(); Logging::trace(nullptr, "The FFmpeg transcoder object was destroyed."); } bool FFmpeg_Transcoder::is_video() const { bool is_video = false; if (m_in.m_video.m_codec_ctx != nullptr && m_in.m_video.m_stream != nullptr) { is_video = !is_album_art(m_in.m_video.m_codec_ctx->codec_id, &m_in.m_video.m_stream->r_frame_rate); } return is_video; } bool FFmpeg_Transcoder::is_open() const { return (m_in.m_format_ctx != nullptr); } int FFmpeg_Transcoder::open_input_file(LPVIRTUALFILE virtualfile, std::shared_ptr fio) { FFmpeg_Dictionary opt; int ret; if (virtualfile == nullptr) { Logging::error(filename(), "INTERNAL ERROR: FFmpeg_Transcoder::open_input_file()! virtualfile is NULL."); return AVERROR(EINVAL); } m_virtualfile = virtualfile; m_mtime = m_virtualfile->m_st.st_mtime; m_current_format = params.current_format(m_virtualfile); if (is_open()) { Logging::warning(filename(), "The file is already open."); return 0; } // This allows selecting if the demuxer should consider all streams to be // found after the first PMT and add further streams during decoding or if it rather // should scan all that are within the analyze-duration and other limits ret = dict_set_with_check(opt.address(), "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); if (ret < 0) { return ret; } // avioflags direct: Reduce buffering. //ret = dict_set_with_check(opt.address(), "avioflags", "direct", AV_DICT_DONT_OVERWRITE); //if (ret < 0) //{ // return ret; //} // analyzeduration: Defaults to 5,000,000 microseconds = 5 seconds. ret = dict_set_with_check(opt.address(), "analyzeduration", "15000000", 0); // <<== honored: 15 seconds if (ret < 0) { return ret; } // probesize: 5000000 by default. ret = dict_set_with_check(opt.address(), "probesize", "15000000", 0); // <<== honoured: ~15 MB if (ret < 0) { return ret; } // using own I/O if (fio == nullptr) { // Open new file io m_fileio = FileIO::alloc(m_virtualfile->m_type); } else { // Use already open file io m_fileio = fio; } if (m_fileio == nullptr) { int orgerrno = errno; Logging::error(filename(), "Error opening file: (%1) %2", errno, strerror(errno)); return AVERROR(orgerrno); } ret = m_fileio->openio(m_virtualfile); if (ret) { return AVERROR(ret); } ret = m_in.m_format_ctx.alloc_input_context(); if (ret < 0) { Logging::error(filename(), "Out of memory opening file: format context could not be allocated."); return ret; } unsigned char *iobuffer = static_cast(av_malloc(m_fileio->bufsize() + FF_INPUT_BUFFER_PADDING_SIZE)); if (iobuffer == nullptr) { Logging::error(filename(), "Out of memory opening file: I/O buffer could not be allocated."); m_in.m_format_ctx.reset(); return AVERROR(ENOMEM); } AVIOContext * pb = avio_alloc_context( iobuffer, static_cast(m_fileio->bufsize()), 0, static_cast(m_fileio.get()), input_read, nullptr, // input_write seek); // input_seek if (pb == nullptr) { Logging::error(filename(), "Out of memory opening file: I/O context could not be allocated."); av_freep(&iobuffer); m_in.m_format_ctx.reset(); return AVERROR(ENOMEM); } m_in.m_format_ctx->pb = pb; m_in.m_format_ctx.set_custom_io(); #if IF_DECLARED_CONST const AVInputFormat * infmt = nullptr; #else // !IF_DECLARED_CONST AVInputFormat * infmt = nullptr; #endif // !IF_DECLARED_CONST #ifdef USE_LIBVCD if (m_virtualfile->m_type == VIRTUALTYPE::VCD) { Logging::debug(filename(), "To avoid misdetection, forcing MPEG format for VCD source."); infmt = av_find_input_format("mpeg"); } #endif // USE_LIBVCD #ifdef USE_LIBDVD if (m_virtualfile->m_type == VIRTUALTYPE::DVD) { Logging::debug(filename(), "To avoid misdetection, forcing MPEG format for DVD source."); infmt = av_find_input_format("mpeg"); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY if (m_virtualfile->m_type == VIRTUALTYPE::BLURAY) { Logging::debug(filename(), "To avoid misdetection, forcing MPEGTS format for Blu-ray source."); infmt = av_find_input_format("mpegts"); } #endif // USE_LIBBLURAY /** @bug Fix memory leak: Probably in FFmpeg API av_probe_input_buffer2(), the av_reallocp * is missing a matching free() call... @n * @n * 102,400 bytes in 1 blocks are definitely lost in loss record 248 of 249 @n * in FFmpeg_Transcoder::open_input_file(VIRTUALFILE*, FileIO*) in /home/norbert/dev/prj/ffmpegfs/src/ffmpeg_transcoder.cc:368 @n * 1: realloc in ./coregrind/m_replacemalloc/vg_replace_malloc.c:834 @n * 2: av_realloc_f in /usr/lib/x86_64-linux-gnu/libavutil.so.56.51.100 @n * 3: /usr/lib/x86_64-linux-gnu/libavformat.so.58.45.100 @n * 4: av_probe_input_buffer2 in /usr/lib/x86_64-linux-gnu/libavformat.so.58.45.100 @n * 5: avformat_open_input in /usr/lib/x86_64-linux-gnu/libavformat.so.58.45.100 @n * 6: FFmpeg_Transcoder::open_input_file(VIRTUALFILE*, FileIO*) in /home/norbert/dev/prj/ffmpegfs/src/ffmpeg_transcoder.cc:368 @n * 7: transcoder_predict_filesize(VIRTUALFILE*, Cache_Entry*) in /home/norbert/dev/prj/ffmpegfs/src/transcode.cc:320 @n * 8: transcoder_new(VIRTUALFILE*, bool) in /home/norbert/dev/prj/ffmpegfs/src/transcode.cc:425 @n * 9: ffmpegfs_getattr(char const*, stat*) in /home/norbert/dev/prj/ffmpegfs/src/fuseops.cc:1323 @n * 10: /usr/lib/x86_64-linux-gnu/libfuse.so.2.9.9 @n * 11: /usr/lib/x86_64-linux-gnu/libfuse.so.2.9.9 @n * 12: /usr/lib/x86_64-linux-gnu/libfuse.so.2.9.9 @n * 13: /usr/lib/x86_64-linux-gnu/libfuse.so.2.9.9 @n * 14: start_thread in ./nptl/pthread_create.c:477 @n * 15: clone in ./misc/../sysdeps/unix/sysv/linux/x86_64/clone.S:95 @n */ // Open the input file to read from it. ret = avformat_open_input(m_in.m_format_ctx.address(), filename(), infmt, opt.address()); if (ret < 0) { Logging::error(filename(), "Could not open input file (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } m_in.m_filetype = get_filetype_from_list(m_in.m_format_ctx->iformat->name); #if HAVE_AV_FORMAT_INJECT_GLOBAL_SIDE_DATA av_format_inject_global_side_data(m_in.m_format_ctx); #endif // Get information on the input file (number of streams etc.). ret = avformat_find_stream_info(m_in.m_format_ctx, nullptr); if (ret < 0) { Logging::error(filename(), "Could not find stream info (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } #ifdef USE_LIBDVD if (m_virtualfile->m_type == VIRTUALTYPE::DVD) { // FFmpeg API calculates a wrong duration, so use value from IFO m_in.m_format_ctx->duration = m_fileio->duration(); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY if (m_virtualfile->m_type == VIRTUALTYPE::BLURAY) { // FFmpeg API calculates a wrong duration, so use value from Blu-ray directory m_in.m_format_ctx->duration = m_fileio->duration(); } #endif // USE_LIBBLURAY m_virtualfile->m_duration = m_in.m_format_ctx->duration; ret = open_bestmatch_video(); if (ret < 0) { // Already logged return ret; } #ifdef USE_LIBDVD if (m_virtualfile->m_type == VIRTUALTYPE::DVD && m_in.m_video.m_codec_ctx != nullptr && m_in.m_video.m_stream != nullptr) { // FFmpeg API sometimes cannot detect video size or frame rate, so use value from IFO if (!m_in.m_video.m_codec_ctx->width || !m_in.m_video.m_stream->codecpar->height) { m_in.m_video.m_codec_ctx->width = m_in.m_video.m_stream->codecpar->width = m_virtualfile->m_width; m_in.m_video.m_codec_ctx->height = m_in.m_video.m_stream->codecpar->height = m_virtualfile->m_height; } if (!m_in.m_video.m_stream->avg_frame_rate.den) { m_in.m_video.m_stream->avg_frame_rate = m_virtualfile->m_framerate; } } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY if (m_virtualfile->m_type == VIRTUALTYPE::BLURAY && m_in.m_video.m_codec_ctx != nullptr && m_in.m_video.m_stream != nullptr) { // FFmpeg API sometimes cannot detect video size or frame rate, so use value from Blu-ray directory if (!m_in.m_video.m_codec_ctx->width || !m_in.m_video.m_stream->codecpar->height) { m_in.m_video.m_codec_ctx->width = m_in.m_video.m_stream->codecpar->width = m_virtualfile->m_width; m_in.m_video.m_codec_ctx->height = m_in.m_video.m_stream->codecpar->height = m_virtualfile->m_height; } if (!m_in.m_video.m_stream->avg_frame_rate.den) { m_in.m_video.m_stream->avg_frame_rate = m_virtualfile->m_framerate; } } #endif // USE_LIBBLURAY ret = open_bestmatch_audio(); if (ret < 0) { // Already logged return ret; } if (!stream_exists(m_in.m_audio.m_stream_idx) && !stream_exists(m_in.m_video.m_stream_idx)) { Logging::error(filename(), "The file contains neither a video nor an audio stream."); return AVERROR(EINVAL); } if (!params.m_no_subtitles) { ret = open_subtitles(); if (ret < 0) { // Already logged return ret; } } // Predict size of transcoded file as exact as possible m_virtualfile->m_predicted_size = calculate_predicted_filesize(); // Calculate number or video frames in file based on duration and frame rate if (m_in.m_video.m_stream != nullptr && m_in.m_video.m_stream->avg_frame_rate.den) { // Number of frames: should be quite accurate m_virtualfile->m_video_frame_count = static_cast(ffmpeg_rescale_q(m_in.m_video.m_stream->duration, m_in.m_video.m_stream->time_base, av_inv_q(m_in.m_video.m_stream->avg_frame_rate))); } ret = open_albumarts(); if (ret < 0) { // Already logged return ret; } if (m_virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { // Position to start of cue sheet track ret = av_seek_frame(m_in.m_format_ctx, -1, m_virtualfile->m_cuesheet_track.m_start, 0); if (ret < 0) { Logging::error(filename(), "The track start was nout found (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } } return 0; } int FFmpeg_Transcoder::open_bestmatch_video() { // Issue #80: Open input video codec, but only if target supports video. // Saves resources: no need to decode video frames if not used. if (m_current_format->video_codec() != AV_CODEC_ID_NONE) { int ret; // Open best match video codec AVCodecContext * codec_ctx = nullptr; ret = open_bestmatch_decoder(m_in.m_format_ctx, &codec_ctx, &m_in.m_video.m_stream_idx, AVMEDIA_TYPE_VIDEO); if (ret < 0 && ret != AVERROR_STREAM_NOT_FOUND) // AVERROR_STREAM_NOT_FOUND is not an error { Logging::error(filename(), "The video codec could not be opened (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } m_in.m_video.set_codec_ctx(codec_ctx); if (stream_exists(m_in.m_video.m_stream_idx)) { // We have a video stream // Check to see if encoder hardware acceleration is both requested and supported by codec. std::string hw_encoder_codec_name; if (!get_hw_encoder_name(m_current_format->video_codec(), &hw_encoder_codec_name)) { // API supports hardware frame buffers m_hwaccel_enable_enc_buffering = (params.m_hwaccel_enc_device_type != AV_HWDEVICE_TYPE_NONE); } if (m_hwaccel_enable_enc_buffering) { // Hardware buffers available, enabling encoder hardware accceleration. Logging::trace(virtname(), "Hardware encoder frame buffering %1 is enabled.", get_hwaccel_API_text(params.m_hwaccel_enc_API).c_str()); ret = hwdevice_ctx_create(&m_hwaccel_enc_device_ctx, params.m_hwaccel_enc_device_type, params.m_hwaccel_enc_device); if (ret < 0) { Logging::error(virtname(), "Failed to create a %1 device for encoding (error %2).", get_hwaccel_API_text(params.m_hwaccel_enc_API).c_str(), ffmpeg_geterror(ret).c_str()); return ret; } Logging::info(virtname(), "Hardware encoder acceleration and frame buffering are active using codec '%1'.", hw_encoder_codec_name.c_str()); } else if (params.m_hwaccel_enc_device_type != AV_HWDEVICE_TYPE_NONE) { // No hardware acceleration, fallback to software, Logging::debug(virtname(), "Hardware encoder frame buffering %1 is not supported by codec '%2'. Falling back to software.", get_hwaccel_API_text(params.m_hwaccel_enc_API).c_str(), get_codec_name(m_in.m_video.m_codec_ctx->codec_id, true)); } else if (!hw_encoder_codec_name.empty()) { // No frame buffering (e.g. OpenMAX or MMAL), but hardware acceleration possible. Logging::info(virtname(), "Hardware encoder acceleration is active using codec '%1'.", hw_encoder_codec_name.c_str()); } m_in.m_video.m_stream = m_in.m_format_ctx->streams[m_in.m_video.m_stream_idx]; #ifdef USE_LIBDVD if (m_virtualfile->m_type == VIRTUALTYPE::DVD) { // FFmpeg API calculates a wrong duration, so use value from IFO m_in.m_video.m_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), m_in.m_video.m_stream->time_base); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY if (m_virtualfile->m_type == VIRTUALTYPE::BLURAY) { // FFmpeg API calculates a wrong duration, so use value from Blu-ray m_in.m_video.m_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), m_in.m_video.m_stream->time_base); } #endif // USE_LIBBLURAY video_info(false, m_in.m_format_ctx, m_in.m_video.m_stream); m_is_video = is_video(); #if !LAVC_DEP_FLAG_TRUNCATED #ifdef AV_CODEC_CAP_TRUNCATED if (m_in.m_video.m_codec_ctx->codec->capabilities & AV_CODEC_CAP_TRUNCATED) { m_in.m_video.m_codec_ctx->flags|= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames } #else #warning "Your FFMPEG distribution is missing AV_CODEC_CAP_TRUNCATED flag. Probably requires fixing!" #endif #endif // !LAVC_DEP_FLAG_TRUNCATED } } return 0; } int FFmpeg_Transcoder::open_bestmatch_audio() { int ret; // Open best match audio codec AVCodecContext * codec_ctx = nullptr; ret = open_bestmatch_decoder(m_in.m_format_ctx, &codec_ctx, &m_in.m_audio.m_stream_idx, AVMEDIA_TYPE_AUDIO); if (ret < 0 && ret != AVERROR_STREAM_NOT_FOUND) // AVERROR_STREAM_NOT_FOUND is not an error { Logging::error(filename(), "The audio codec could not be opened (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } m_in.m_audio.set_codec_ctx(codec_ctx); if (stream_exists(m_in.m_audio.m_stream_idx)) { // We have an audio stream m_in.m_audio.m_stream = m_in.m_format_ctx->streams[m_in.m_audio.m_stream_idx]; #ifdef USE_LIBDVD if (m_virtualfile->m_type == VIRTUALTYPE::DVD) { // FFmpeg API calculates a wrong duration, so use value from IFO m_in.m_audio.m_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), m_in.m_audio.m_stream->time_base); } #endif // USE_LIBDVD #ifdef USE_LIBBLURAY if (m_virtualfile->m_type == VIRTUALTYPE::BLURAY) { // FFmpeg API calculates a wrong duration, so use value from Blu-ray directory m_in.m_audio.m_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), m_in.m_audio.m_stream->time_base); } #endif // USE_LIBBLURAY audio_info(false, m_in.m_format_ctx, m_in.m_audio.m_stream); } return 0; } int FFmpeg_Transcoder::open_subtitles() { // If target supports subtitles, check to transcode subtitles for (int stream_idx = 0; stream_idx < static_cast(m_in.m_format_ctx->nb_streams); stream_idx++) { AVStream * stream = m_in.m_format_ctx->streams[stream_idx]; if (avcodec_get_type(stream->codecpar->codec_id) != AVMEDIA_TYPE_SUBTITLE) { continue; } if (m_current_format->subtitle_codec(stream->codecpar->codec_id) == AV_CODEC_ID_NONE) { // No match, no support for this type of subtitle continue; } AVCodecContext * codec_ctx = nullptr; int ret; ret = open_decoder(m_in.m_format_ctx, &codec_ctx, stream_idx, nullptr, AVMEDIA_TYPE_SUBTITLE); if (ret < 0) { Logging::error(filename(), "The subtitle codec could not be opened (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } codec_ctx->pkt_timebase = codec_ctx->time_base = stream->time_base; StreamRef input_streamref; // We have a subtitle stream input_streamref.set_codec_ctx(codec_ctx); input_streamref.m_stream = stream; input_streamref.m_stream_idx = stream_idx; m_in.m_subtitle.emplace(input_streamref.m_stream_idx, input_streamref); subtitle_info(false, m_in.m_format_ctx, input_streamref.m_stream); } return 0; } int FFmpeg_Transcoder::open_albumarts() { // Open album art streams if present and supported by both source and target if (!params.m_noalbumarts && m_in.m_audio.m_stream != nullptr) { for (int stream_idx = 0; stream_idx < static_cast(m_in.m_format_ctx->nb_streams); stream_idx++) { AVStream *input_stream = m_in.m_format_ctx->streams[stream_idx]; if (is_album_art(input_stream->codecpar->codec_id, &input_stream->r_frame_rate)) { StreamRef streamref; AVCodecContext * input_codec_ctx; int ret; Logging::trace(filename(), "Found album art."); ret = open_decoder(m_in.m_format_ctx, &input_codec_ctx, stream_idx, nullptr, AVMEDIA_TYPE_VIDEO); if (ret < 0) { Logging::error(filename(), "The album art codec could not be opened (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } streamref.set_codec_ctx(input_codec_ctx); streamref.m_stream = input_stream; streamref.m_stream_idx = input_stream->index; m_in.m_album_art.push_back(streamref); } } } return 0; } bool FFmpeg_Transcoder::can_copy_stream(const AVStream *stream) const { if (params.m_autocopy == AUTOCOPY::OFF) { // Auto copy disabled return false; } if (stream == nullptr) { // Should normally not happen: Input stream stream unknown, no way to check - no auto copy return false; } AVMediaType codec_type = stream->codecpar->codec_type; AVCodecID codec_in = stream->codecpar->codec_id; std::string codec_type_str; AVCodecID codec_out; int64_t bitrate_out; if (codec_type == AVMEDIA_TYPE_VIDEO) { codec_type_str = "video"; codec_out = m_current_format->video_codec(); bitrate_out = params.m_videobitrate; } else if (codec_type == AVMEDIA_TYPE_AUDIO) { codec_type_str = "audio"; codec_out = m_current_format->audio_codec(); bitrate_out = params.m_audiobitrate; } else { // Codec is not video or audio return false; } if ((params.m_autocopy == AUTOCOPY::MATCH || params.m_autocopy == AUTOCOPY::MATCHLIMIT)) { // Any codec supported by output format OK const AVOutputFormat* oformat = av_guess_format(nullptr, virtname(), nullptr); if (oformat->codec_tag == nullptr || av_codec_get_tag(oformat->codec_tag, stream->codecpar->codec_id) <= 0) { // Codec not supported - no auto copy return false; } } else if ((params.m_autocopy == AUTOCOPY::STRICT || params.m_autocopy == AUTOCOPY::STRICTLIMIT)) { // Output codec must strictly match Logging::debug(virtname(), "Check autocopy strict: %1: %2 -> %3", codec_type_str.c_str(), avcodec_get_name(codec_in), avcodec_get_name(codec_out)); if (codec_in != codec_out) { // Different codecs - no auto copy return false; } } if (params.m_autocopy == AUTOCOPY::MATCHLIMIT || params.m_autocopy == AUTOCOPY::STRICTLIMIT) { BITRATE orig_bit_rate = (stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : m_in.m_format_ctx->bit_rate; if (get_output_bit_rate(orig_bit_rate, bitrate_out)) { // Bit rate changed, no auto copy Logging::info(virtname(), "Because the bit rate has changed, no auto copy is possible."); return false; } } return true; } int FFmpeg_Transcoder::open_output_file(Buffer *buffer) { if (buffer == nullptr) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::open_output_file()! buffer == nullptr in open_output_file()"); return AVERROR(EINVAL); } m_out.m_filetype = m_current_format->filetype(); Logging::debug(virtname(), "Opening the output file."); if (!stream_exists(m_in.m_audio.m_stream_idx) && m_current_format->video_codec() == AV_CODEC_ID_NONE) { Logging::error(virtname(), "Unable to transcode. The source contains no audio stream, but the target just supports audio."); m_virtualfile->m_flags |= VIRTUALFLAG_HIDDEN; // Hide file from now on return AVERROR(ENOENT); // Report file not found } if (!is_frameset()) { // Not a frame set, open regular buffer return open_output(buffer); } else { Logging::debug(virtname(), "Opening frame set type '%1'.", m_current_format->desttype().c_str()); // Open frame set buffer return open_output_frame_set(buffer); } } int FFmpeg_Transcoder::open_bestmatch_decoder(AVFormatContext *format_ctx, AVCodecContext **codec_ctx, int *stream_idx, AVMediaType type) { #if IF_DECLARED_CONST const AVCodec *input_codec = nullptr; #else // !IF_DECLARED_CONST AVCodec *input_codec = nullptr; #endif // !IF_DECLARED_CONST int ret; *codec_ctx = nullptr; *stream_idx = INVALID_STREAM; ret = av_find_best_stream(format_ctx, type, INVALID_STREAM, INVALID_STREAM, &input_codec, 0); if (ret < 0) { if (ret != AVERROR_STREAM_NOT_FOUND) // Not an error { Logging::error(filename(), "Could not find a %1 stream in the input file (error '%2').", get_media_type_string(type), ffmpeg_geterror(ret).c_str()); } return ret; } *stream_idx = ret; return open_decoder(format_ctx, codec_ctx, *stream_idx, input_codec, type); } #if IF_DECLARED_CONST AVPixelFormat FFmpeg_Transcoder::get_hw_pix_fmt(const AVCodec *codec, AVHWDeviceType dev_type, bool use_device_ctx) const #else // !IF_DECLARED_CONST AVPixelFormat FFmpeg_Transcoder::get_hw_pix_fmt(AVCodec *codec, AVHWDeviceType dev_type, bool use_device_ctx) const #endif // !IF_DECLARED_CONST { AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; if (codec != nullptr && dev_type != AV_HWDEVICE_TYPE_NONE) { int method = use_device_ctx ? AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX : AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX; for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i); if (!config) { Logging::error(av_codec_is_decoder(codec) ? filename() : virtname(), "%1 '%2' does not support device type %3.\n", av_codec_is_encoder(codec) ? "Encoder" : "Decoder", codec->name, hwdevice_get_type_name(dev_type)); break; } if ((config->methods & method) && (config->device_type == dev_type)) { hw_pix_fmt = config->pix_fmt; Logging::debug(av_codec_is_decoder(codec) ? filename() : virtname(), "%1 '%2' requests %3 for device type %4.\n", av_codec_is_encoder(codec) ? "Encoder" : "Decoder", codec->name, av_get_pix_fmt_name(hw_pix_fmt), hwdevice_get_type_name(dev_type)); break; } } } return hw_pix_fmt; } #if IF_DECLARED_CONST int FFmpeg_Transcoder::open_decoder(AVFormatContext *format_ctx, AVCodecContext **codec_ctx, int stream_idx, const AVCodec *input_codec, AVMediaType mediatype) #else // !IF_DECLARED_CONST int FFmpeg_Transcoder::open_decoder(AVFormatContext *format_ctx, AVCodecContext **codec_ctx, int stream_idx, AVCodec *input_codec, AVMediaType mediatype) #endif // !IF_DECLARED_CONST { while (true) { AVCodecContext *input_codec_ctx = nullptr; AVStream * input_stream = nullptr; FFmpeg_Dictionary opt; AVCodecID codec_id = AV_CODEC_ID_NONE; int ret; input_stream = format_ctx->streams[stream_idx]; // Init the decoders, with or without reference counting // av_dict_set_with_check(opt.address(), "refcounted_frames", refcount ? "1" : "0", 0); // allocate a new decoding context input_codec_ctx = avcodec_alloc_context3(nullptr); if (input_codec_ctx == nullptr) { Logging::error(filename(), "Decoding context could not be allocated."); return AVERROR(ENOMEM); } // initialise the stream parameters with demuxer information ret = avcodec_parameters_to_context(input_codec_ctx, input_stream->codecpar); if (ret < 0) { return ret; } codec_id = input_stream->codecpar->codec_id; if (params.m_hwaccel_dec_API != HWACCELAPI::NONE) { if (mediatype == AVMEDIA_TYPE_VIDEO) { if (check_hwaccel_dec_blocked(input_stream->codecpar->codec_id, input_stream->codecpar->profile)) { const char *profile = ::avcodec_profile_name(codec_id, input_stream->codecpar->profile); Logging::info(filename(), "Codec '%1' profile '%2' is blocked from hardware decoding. Reverting to software decoder.", ::get_codec_name(codec_id), profile != nullptr ? profile : "unknown"); m_hwaccel_dec_mode = HWACCELMODE::FALLBACK; } } if (mediatype == AVMEDIA_TYPE_VIDEO && m_hwaccel_dec_mode != HWACCELMODE::FALLBACK) { // Decide whether to use a hardware decoder // Check to see if decoder hardware acceleration is both requested and supported by codec. std::string hw_decoder_codec_name; if (!get_hw_decoder_name(input_codec_ctx->codec_id, &hw_decoder_codec_name)) { m_dec_hw_pix_fmt = get_hw_pix_fmt(input_codec, params.m_hwaccel_dec_device_type, true); m_hwaccel_enable_dec_buffering = (params.m_hwaccel_dec_device_type != AV_HWDEVICE_TYPE_NONE && m_dec_hw_pix_fmt != AV_PIX_FMT_NONE); //{ // std::string fourcc2str; // fourcc_make_string(&fourcc2str, input_codec_ctx->codec_tag); // fprintf(stderr, "fourcc2str %s\n", fourcc2str.c_str()); //} } if (m_hwaccel_enable_dec_buffering) { // Hardware buffers available, enabling decoder hardware acceleration. Logging::trace(filename(), "Hardware decoder frame buffering %1 is enabled.", get_hwaccel_API_text(params.m_hwaccel_dec_API).c_str()); ret = hwdevice_ctx_create(&m_hwaccel_dec_device_ctx, params.m_hwaccel_dec_device_type, params.m_hwaccel_dec_device); if (ret < 0) { Logging::error(filename(), "Failed to create a %1 device for decoding (error %2).", get_hwaccel_API_text(params.m_hwaccel_dec_API).c_str(), ffmpeg_geterror(ret).c_str()); return ret; } Logging::info(filename(), "Hardware decoder acceleration and frame buffering are active using codec '%1'.", (input_codec != nullptr) ? input_codec->name : "unknown"); m_hwaccel_dec_mode = HWACCELMODE::ENABLED; // Hardware acceleration active } else if (params.m_hwaccel_dec_device_type != AV_HWDEVICE_TYPE_NONE) { // No hardware acceleration, fallback to software, Logging::debug(filename(), "Hardware decoder frame buffering %1 not supported by codec '%2'. Falling back to software.", get_hwaccel_API_text(params.m_hwaccel_dec_API).c_str(), get_codec_name(input_codec_ctx->codec_id, true)); } else if (!hw_decoder_codec_name.empty()) { // No frame buffering (e.g. OpenMAX or MMAL), but hardware acceleration possible. // Open hw_decoder_codec_name codec here input_codec = avcodec_find_decoder_by_name(hw_decoder_codec_name.c_str()); if (input_codec == nullptr) { Logging::error(filename(), "Decoder '%1' could not be found.", hw_decoder_codec_name.c_str()); return AVERROR(EINVAL); } Logging::info(filename(), "Hardware decoder acceleration is active using codec '%1'.", input_codec->name); m_hwaccel_dec_mode = HWACCELMODE::ENABLED; // Hardware acceleration active } if (m_hwaccel_enable_dec_buffering) { ret = hwdevice_ctx_add_ref(input_codec_ctx); if (ret < 0) { return ret; } } } } if (input_codec == nullptr) { // Find a decoder for the stream. input_codec = avcodec_find_decoder(codec_id); if (input_codec == nullptr) { Logging::error(filename(), "Failed to find %1 input codec '%2'.", get_media_type_string(mediatype), avcodec_get_name(codec_id)); return AVERROR(EINVAL); } } input_codec_ctx->codec_id = input_codec->id; //input_codec_ctx->time_base = input_stream->time_base; ret = avcodec_open2(input_codec_ctx, input_codec, opt.address()); if (ret < 0) { if (m_hwaccel_dec_mode == HWACCELMODE::ENABLED) { Logging::info(filename(), "Unable to use %1 input codec '%2' with hardware acceleration. Falling back to software.", get_media_type_string(mediatype), avcodec_get_name(codec_id)); m_hwaccel_dec_mode = HWACCELMODE::FALLBACK; m_hwaccel_enable_dec_buffering = false; m_dec_hw_pix_fmt = AV_PIX_FMT_NONE; // Free hardware device contexts if open hwdevice_ctx_free(&m_hwaccel_dec_device_ctx); m_in.m_video.reset(); // Try again with a software decoder continue; } Logging::error(filename(), "Failed to open %1 input codec for stream #%1 (error '%2').", get_media_type_string(mediatype), input_stream->index, ffmpeg_geterror(ret).c_str()); return ret; } Logging::debug(filename(), "Opened input codec for stream #%1: %2", input_stream->index, get_codec_name(codec_id, true)); *codec_ctx = input_codec_ctx; return 0; }; } int FFmpeg_Transcoder::open_output_frame_set(Buffer *buffer) { const AVCodec *output_codec = nullptr; AVCodecContext *const input_codec_ctx = m_in.m_video.m_codec_ctx.get(); FFmpeg_Dictionary opt; int ret = 0; const AVCodecID video_codec = m_current_format->video_codec(); m_buffer = buffer; { std::lock_guard lock_seek_to_fifo_mutex(m_seek_to_fifo_mutex); m_seek_to_fifo = {}; } m_have_seeked = false; output_codec = avcodec_find_encoder(video_codec); if (output_codec == nullptr) { Logging::error(virtname(), "Codec not found."); return AVERROR(EINVAL); } std::unique_ptr output_codec_ctx_guard( avcodec_alloc_context3(output_codec), [](AVCodecContext *ctx) { avcodec_free_context(&ctx); }); AVCodecContext *output_codec_ctx = output_codec_ctx_guard.get(); if (output_codec_ctx == nullptr) { Logging::error(virtname(), "The video codec context could not be allocated."); return AVERROR(ENOMEM); } output_codec_ctx->bit_rate = 400000; /** @todo Make frame image compression rate command line settable */ output_codec_ctx->width = input_codec_ctx->width; output_codec_ctx->height = input_codec_ctx->height; output_codec_ctx->time_base = {1, 25}; const AVPixFmtDescriptor *dst_desc = av_pix_fmt_desc_get(input_codec_ctx->pix_fmt); int loss = 0; #if LAVC_USE_SUPPORTED_CFG { const enum AVPixelFormat *pix_list = nullptr; int npix = 0; int ret_cfg = avcodec_get_supported_config(output_codec_ctx, output_codec, AV_CODEC_CONFIG_PIX_FORMAT, 0, (const void**)&pix_list, &npix); if (ret_cfg >= 0 && pix_list && npix > 0) { output_codec_ctx->pix_fmt = avcodec_find_best_pix_fmt_of_list(pix_list, input_codec_ctx->pix_fmt, (dst_desc->flags & AV_PIX_FMT_FLAG_ALPHA) ? 1 : 0, &loss); } else { output_codec_ctx->pix_fmt = input_codec_ctx->pix_fmt; } } #else output_codec_ctx->pix_fmt = avcodec_find_best_pix_fmt_of_list(output_codec->pix_fmts, input_codec_ctx->pix_fmt, (dst_desc->flags & AV_PIX_FMT_FLAG_ALPHA) ? 1 : 0, &loss); #endif if (output_codec_ctx->pix_fmt == AV_PIX_FMT_NONE) { // No best match found, use default switch (video_codec) { case AV_CODEC_ID_MJPEG: { output_codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ444P; break; } case AV_CODEC_ID_PNG: { output_codec_ctx->pix_fmt = AV_PIX_FMT_RGB24; break; } case AV_CODEC_ID_BMP: { output_codec_ctx->pix_fmt = AV_PIX_FMT_BGR24; break; } default: { break; } } Logging::debug(virtname(), "There was no best match output pixel format found, so we used the default: %1", get_pix_fmt_name(output_codec_ctx->pix_fmt).c_str()); } else { Logging::debug(virtname(), "Output pixel format: %1", get_pix_fmt_name(output_codec_ctx->pix_fmt).c_str()); } switch (video_codec) { case AV_CODEC_ID_MJPEG: { // set -strict -1 for JPG dict_set_with_check(opt.address(), "strict", "-1", 0); // Allow the use of unoffical extensions output_codec_ctx->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; break; } case AV_CODEC_ID_PNG: { output_codec_ctx->pix_fmt = AV_PIX_FMT_RGB24; break; } case AV_CODEC_ID_BMP: { output_codec_ctx->pix_fmt = AV_PIX_FMT_BGR24; break; } default: { break; } } //codec_ctx->sample_aspect_ratio = frame->sample_aspect_ratio; //codec_ctx->sample_aspect_ratio = input_codec_ctx->sample_aspect_ratio; ret = avcodec_open2(output_codec_ctx, output_codec, opt.address()); if (ret < 0) { Logging::error(virtname(), "The image codec could not be opened."); return ret; } // Initialise pixel format conversion and rescaling if necessary get_pix_formats(&m_in.m_pix_fmt, &m_out.m_pix_fmt, output_codec_ctx); ret = init_rescaler(m_in.m_pix_fmt, m_in.m_video.m_stream->codecpar->width, m_in.m_video.m_stream->codecpar->height, m_out.m_pix_fmt, output_codec_ctx->width, output_codec_ctx->height); if (ret < 0) { return ret; } if (params.m_deinterlace) { ret = init_deinterlace_filters(output_codec_ctx, m_in.m_pix_fmt, m_in.m_video.m_stream->avg_frame_rate, m_in.m_video.m_stream->time_base); if (ret < 0) { return ret; } } m_out.m_video.set_codec_ctx(output_codec_ctx_guard.release()); m_out.m_video.m_stream_idx = INVALID_STREAM; m_out.m_video.m_stream = nullptr; // No audio m_out.m_audio.set_codec_ctx(nullptr); m_out.m_audio.m_stream_idx = INVALID_STREAM; m_out.m_audio.m_stream = nullptr; // Open for read/write // Pre-allocate the predicted file size to reduce memory reallocations size_t buffsize = predicted_filesize() * video_frame_count(); if (!buffer->open_file(0, CACHE_FLAG_RW, buffsize)) { return AVERROR(EPERM); } return 0; } int FFmpeg_Transcoder::open_output(Buffer *buffer) { int ret = 0; m_buffer = buffer; m_insert_keyframe = false; ret = initialise_hls_output_segment(buffer); if (ret < 0) { return ret; } ret = open_output_with_hwaccel_fallback(buffer); if (ret < 0) { return ret; } ret = initialise_output_audio(); if (ret < 0) { return ret; } log_output_video_and_subtitle_info(); ret = open_regular_output_cache_file(buffer); if (ret < 0) { return ret; } ret = process_output(); if (ret) { return ret; } // process_output() calls avformat_write_header which feels free to change the stream time bases sometimes. // This means we have to do the following calculations here to use the correct values, otherwise this can cause // a lot of havoc. initialise_output_start_times(); return 0; } int FFmpeg_Transcoder::initialise_hls_output_segment(Buffer *buffer) { if (m_out.m_video_pts || !is_hls()) { return 0; } if (buffer == nullptr) { Logging::error(virtname(), "Cannot initialise HLS output segment without an output buffer."); return AVERROR(EINVAL); } if (m_virtualfile == nullptr) { Logging::error(virtname(), "Cannot initialise HLS output segment without a virtual file."); return AVERROR(EINVAL); } const bool hls_segment_preselected = m_current_segment > 1; if (!hls_segment_preselected) { m_current_segment = 1; // The first output open is special: start_new_segment() has not run yet, // so a seek stacked before opening the output would otherwise not be // consumed until after segment 1 had already been opened and encoded. // Consume an initial HLS seek here and position the input before any // output segment is opened. uint32_t initial_seek_segment = 0; { std::lock_guard lock_seek_to_fifo_mutex(m_seek_to_fifo_mutex); while (!m_seek_to_fifo.empty()) { const uint32_t segment_no = m_seek_to_fifo.front(); m_seek_to_fifo.pop(); if (discard_hls_seek_near_beginning(segment_no)) { continue; } if (segment_no > 1) { initial_seek_segment = segment_no; break; } } } if (initial_seek_segment) { int ret = seek_hls_segment(initial_seek_segment, false); if (ret < 0) { return ret; } purge_hls_fifo(); m_current_segment = initial_seek_segment; } } const uint32_t segment_count = m_virtualfile->get_segment_count(); Logging::info(virtname(), "Starting HLS segment no. %1 of %2.", m_current_segment, segment_count); // HLS uses one physical cache file per segment. Select the cache file // for the current segment before opening the output format and before // avformat_write_header()/process_output() can emit any data. This is // especially important for repair runs after a seek, where the first // segment written may be segment 2 or later rather than segment 1. if (!segment_count || !m_current_segment || m_current_segment > segment_count) { return AVERROR(EINVAL); } size_t segment_buffsize = m_virtualfile->m_predicted_size / segment_count; if (!segment_buffsize) { segment_buffsize = static_cast(sysconf(_SC_PAGESIZE)); } if (!buffer->set_segment(m_current_segment, segment_buffsize)) { return AVERROR(errno); } return 0; } int FFmpeg_Transcoder::open_output_with_hwaccel_fallback(Buffer *buffer) { while (true) { // Open the output file for writing. If buffer == nullptr continue using existing buffer. int ret = open_output_filestreams(buffer); if (ret) { if (m_hwaccel_enc_mode == HWACCELMODE::ENABLED) { Logging::info(virtname(), "Unable to use output codec '%1' with hardware acceleration. Falling back to software.", avcodec_get_name(m_current_format->video_codec())); m_hwaccel_enc_mode = HWACCELMODE::FALLBACK; m_hwaccel_enable_enc_buffering = false; m_enc_hw_pix_fmt = AV_PIX_FMT_NONE; // Free hardware device contexts if open hwdevice_ctx_free(&m_hwaccel_enc_device_ctx); close_output_file(); // Try again with a software encoder. continue; } return ret; } break; } return 0; } int FFmpeg_Transcoder::initialise_output_audio() { if (!stream_exists(m_out.m_audio.m_stream_idx)) { return 0; } audio_info(true, m_out.m_format_ctx, m_out.m_audio.m_stream); if (m_out.m_audio.m_codec_ctx != nullptr) { // If not just copying the stream, initialise the audio FIFO buffer to store audio samples to be encoded. int ret = init_audio_fifo(); if (ret) { return ret; } } return 0; } void FFmpeg_Transcoder::log_output_video_and_subtitle_info() { if (stream_exists(m_out.m_video.m_stream_idx)) { video_info(true, m_out.m_format_ctx, m_out.m_video.m_stream); } for (const auto & [key, value] : m_out.m_subtitle) { subtitle_info(true, m_out.m_format_ctx, value.m_stream); } } int FFmpeg_Transcoder::open_regular_output_cache_file(Buffer *buffer) { if (is_hls()) { return 0; } if (buffer == nullptr) { Logging::error(virtname(), "Cannot open regular output cache file without an output buffer."); return AVERROR(EINVAL); } // Open regular output files for read/write. // HLS output uses one cache file per segment; the active segment was // already selected above with Buffer::set_segment(). Opening segment // 0 here would switch the active cache file back to the first segment // and make repair/seek runs append the following output to 000001.ts. size_t buffsize = predicted_filesize(); if (!buffer->open_file(0, CACHE_FLAG_RW, buffsize)) { return AVERROR(EPERM); } return 0; } void FFmpeg_Transcoder::initialise_audio_output_start_time() { if (m_in.m_audio.m_stream != nullptr && m_out.m_audio.m_stream != nullptr && m_in.m_audio.m_stream->start_time != AV_NOPTS_VALUE) { m_in.m_audio.m_start_time = m_in.m_audio.m_stream->start_time; m_out.m_audio.m_start_time = ffmpeg_rescale_q_rnd(m_in.m_audio.m_stream->start_time, m_in.m_audio.m_stream->time_base, m_out.m_audio.m_stream->time_base); m_out.m_audio.m_stream->start_time = m_out.m_audio.m_start_time; } else { m_in.m_audio.m_start_time = 0; m_out.m_audio.m_start_time = 0; if (m_out.m_audio.m_stream) { m_out.m_audio.m_stream->start_time = AV_NOPTS_VALUE; } } } void FFmpeg_Transcoder::initialise_video_output_start_time() { if (m_in.m_video.m_stream != nullptr && m_out.m_video.m_stream != nullptr && m_in.m_video.m_stream->start_time != AV_NOPTS_VALUE) { m_in.m_video.m_start_time = m_in.m_video.m_stream->start_time; m_out.m_video.m_start_time = ffmpeg_rescale_q_rnd(m_in.m_video.m_stream->start_time, m_in.m_video.m_stream->time_base, m_out.m_video.m_stream->time_base); m_out.m_video.m_stream->start_time = m_out.m_video.m_start_time; } else { m_in.m_video.m_start_time = 0; m_out.m_video.m_start_time = 0; if (m_out.m_video.m_stream) { m_out.m_video.m_stream->start_time = AV_NOPTS_VALUE; } } } void FFmpeg_Transcoder::initialise_subtitle_output_start_times() { for (auto & [key, value] : m_out.m_subtitle) { StreamRef * out_streamref = get_out_subtitle_stream(map_in_to_out_stream(key)); if (out_streamref != nullptr) { value.m_start_time = value.m_stream->start_time; out_streamref->m_start_time = ffmpeg_rescale_q_rnd(value.m_stream->start_time, value.m_stream->time_base, out_streamref->m_stream->time_base); out_streamref->m_stream->start_time = out_streamref->m_start_time; } } } void FFmpeg_Transcoder::initialise_output_start_times() { initialise_audio_output_start_time(); initialise_video_output_start_time(); initialise_subtitle_output_start_times(); m_out.m_audio_pts = m_out.m_audio.m_start_time; m_out.m_video_pts = m_out.m_video.m_start_time; m_out.m_last_mux_dts = AV_NOPTS_VALUE; } int FFmpeg_Transcoder::process_output() { // Process metadata. The decoder will call the encoder to set appropriate // tag values for the output file. int ret = process_metadata(); if (ret) { return ret; } // Write the header of the output file container. ret = write_output_file_header(); if (ret) { return ret; } // Process album arts: copy all from source file to target. return process_albumarts(); } bool FFmpeg_Transcoder::get_output_sample_rate(int input_sample_rate, int max_sample_rate, int *output_sample_rate /*= nullptr*/) { if (input_sample_rate > max_sample_rate) { if (output_sample_rate != nullptr) { *output_sample_rate = max_sample_rate; } return true; } else { if (output_sample_rate != nullptr) { *output_sample_rate = input_sample_rate; } return false; } } bool FFmpeg_Transcoder::get_output_bit_rate(BITRATE input_bit_rate, BITRATE max_bit_rate, BITRATE * output_bit_rate /*= nullptr*/) { if (!input_bit_rate || input_bit_rate > max_bit_rate) { if (output_bit_rate != nullptr) { *output_bit_rate = max_bit_rate; } return true; } else { if (output_bit_rate != nullptr) { *output_bit_rate = input_bit_rate; } return false; } } bool FFmpeg_Transcoder::get_aspect_ratio(int width, int height, const AVRational & sar, AVRational *ar) const { // Try to determine display aspect ratio AVRational dar; ::av_reduce(&dar.num, &dar.den, static_cast(width) * sar.num, static_cast(height) * sar.den, 1024 * 1024); ar->num = ar->den = 0; if (dar.num && dar.den) { *ar = dar; } // If that fails, try sample aspect ratio instead if (!ar->den && sar.num != 0 && sar.den != 0) { *ar = sar; } // If even that fails, try to use video size if (!ar->den && height) { ar->num = width; ar->den = height; } if (!ar->den) { // Return false if all above failed return false; } ::av_reduce(&ar->num, &ar->den, ar->num, ar->den, 1024 * 1024); return true; } bool FFmpeg_Transcoder::get_video_size(int *output_width, int *output_height) const { if (!params.m_videowidth && !params.m_videoheight) { // No options, leave as is return false; } int input_width = m_in.m_video.m_stream->codecpar->width; int input_height = m_in.m_video.m_stream->codecpar->height; AVRational sar = m_in.m_video.m_stream->codecpar->sample_aspect_ratio; if (params.m_videowidth && params.m_videoheight) { // Both width/source set. May look strange, but this is an order... *output_width = params.m_videowidth; *output_height = params.m_videoheight; } else if (params.m_videowidth) { // Only video width AVRational ar; *output_width = params.m_videowidth; if (!get_aspect_ratio(input_width, input_height, sar, &ar)) { *output_height = input_height; } else { *output_height = static_cast(params.m_videowidth / av_q2d(ar)); *output_height &= ~(static_cast(0x1)); // height must be multiple of 2 } } else //if (params.m_videoheight) { // Only video height AVRational ar; if (!get_aspect_ratio(input_width, input_height, sar, &ar)) { *output_width = input_width; } else { *output_width = static_cast(params.m_videoheight / av_q2d(ar)); *output_width &= ~(static_cast(0x1)); // width must be multiple of 2 } *output_height = params.m_videoheight; } return (input_width > *output_width || input_height > *output_height); } int FFmpeg_Transcoder::update_codec(void *opt, const PROFILE_OPTION_VEC& profile_option_vec) const { int ret = 0; for (const PROFILE_OPTION & profile_option : profile_option_vec) { if ((m_hwaccel_enable_enc_buffering && profile_option.m_options & OPT_SW_ONLY) || (!m_hwaccel_enable_enc_buffering && profile_option.m_options & OPT_HW_ONLY)) { continue; } Logging::trace(virtname(), "Profile codec option -%1%2%3.", profile_option.m_key, *profile_option.m_value ? " " : "", profile_option.m_value); ret = opt_set_with_check(opt, profile_option.m_key, profile_option.m_value, profile_option.m_flags, virtname()); if (ret < 0) { break; } } return ret; } int FFmpeg_Transcoder::prepare_codec(void *opt, FILETYPE filetype) const { int ret = 0; for (const PROFILE_LIST & profile : m_profile) { if (profile.m_filetype == filetype) { ret = AVERROR_OPTION_NOT_FOUND; // Once we found the file type, we also need to find the profile. Otherwise we have an invalid command line option. if (profile.m_profile == params.m_profile) { ret = update_codec(opt, profile.m_option_codec); break; } } } return ret; } int FFmpeg_Transcoder::init_rescaler(AVPixelFormat in_pix_fmt, int in_width, int in_height, AVPixelFormat out_pix_fmt, int out_width, int out_height) { if (in_pix_fmt != out_pix_fmt || in_width != out_width || in_height != out_height) { // Rescale image if required if (in_pix_fmt != out_pix_fmt) { Logging::trace(virtname(), "Initialising pixel format conversion from %1 to %2.", get_pix_fmt_name(in_pix_fmt).c_str(), get_pix_fmt_name(out_pix_fmt).c_str()); } if (in_width != out_width || in_height != out_height) { Logging::debug(virtname(), "Rescaling video size from %1:%2 to %3:%4.", in_width, in_height, out_width, out_height); } m_sws_ctx.reset(sws_getContext( // Source settings in_width, // width in_height, // height in_pix_fmt, // format // Target settings out_width, // width out_height, // height out_pix_fmt, // format SWS_FAST_BILINEAR, nullptr, nullptr, nullptr)); // Maybe SWS_LANCZOS | SWS_ACCURATE_RND if (m_sws_ctx == nullptr) { Logging::error(virtname(), "Could not allocate a scaling/conversion context."); return AVERROR(ENOMEM); } } return 0; } #if IF_DECLARED_CONST int FFmpeg_Transcoder::find_output_encoder(AVCodecID codec_id, const AVCodec **output_codec) #else // !IF_DECLARED_CONST int FFmpeg_Transcoder::find_output_encoder(AVCodecID codec_id, AVCodec **output_codec) #endif // !IF_DECLARED_CONST { std::string codec_name; if (get_hw_encoder_name(codec_id, &codec_name) || m_hwaccel_enc_mode == HWACCELMODE::FALLBACK) { // find the encoder *output_codec = avcodec_find_encoder(codec_id); if (*output_codec == nullptr) { Logging::error(virtname(), "Could not find encoder '%1'.", avcodec_get_name(codec_id)); return AVERROR(EINVAL); } } else { *output_codec = avcodec_find_encoder_by_name(codec_name.c_str()); if (*output_codec == nullptr) { Logging::error(virtname(), "Could not find encoder '%1'.", codec_name.c_str()); return AVERROR(EINVAL); } //Logging::info(virtname(), "Hardware encoder acceleration active with codec '%1'.", (*output_codec)->name); m_hwaccel_enc_mode = HWACCELMODE::ENABLED; } return 0; } #if IF_DECLARED_CONST int FFmpeg_Transcoder::allocate_output_stream_and_context(AVCodecID codec_id, const AVCodec *output_codec, AVStream **output_stream, AVCodecContext **output_codec_ctx) #else // !IF_DECLARED_CONST int FFmpeg_Transcoder::allocate_output_stream_and_context(AVCodecID codec_id, AVCodec *output_codec, AVStream **output_stream, AVCodecContext **output_codec_ctx) #endif // !IF_DECLARED_CONST { *output_stream = avformat_new_stream(m_out.m_format_ctx, output_codec); if (*output_stream == nullptr) { Logging::error(virtname(), "Could not allocate stream for encoder '%1'.", avcodec_get_name(codec_id)); return AVERROR(ENOMEM); } (*output_stream)->id = static_cast(m_out.m_format_ctx->nb_streams - 1); *output_codec_ctx = avcodec_alloc_context3(output_codec); if (*output_codec_ctx == nullptr) { Logging::error(virtname(), "Could not allocate an encoding context."); return AVERROR(ENOMEM); } return 0; } #if IF_DECLARED_CONST int FFmpeg_Transcoder::configure_audio_output_stream(AVCodecID codec_id, const AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt) #else // !IF_DECLARED_CONST int FFmpeg_Transcoder::configure_audio_output_stream(AVCodecID codec_id, AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt) #endif // !IF_DECLARED_CONST { BITRATE orig_bit_rate; int orig_sample_rate; // Set the basic encoder parameters orig_bit_rate = (m_in.m_audio.m_stream->codecpar->bit_rate != 0) ? m_in.m_audio.m_stream->codecpar->bit_rate : m_in.m_format_ctx->bit_rate; if (get_output_bit_rate(orig_bit_rate, params.m_audiobitrate, &output_codec_ctx->bit_rate)) { // Limit bit rate Logging::trace(virtname(), "Limiting audio bit rate from %1 to %2.", format_bitrate(orig_bit_rate).c_str(), format_bitrate(output_codec_ctx->bit_rate).c_str()); } if (params.m_audiochannels > 0 && get_channels(m_in.m_audio.m_codec_ctx.get()) > params.m_audiochannels) { Logging::trace(virtname(), "Limiting audio channels from %1 to %2.", get_channels(m_in.m_audio.m_codec_ctx.get()), params.m_audiochannels); set_channels(output_codec_ctx, params.m_audiochannels); } else { set_channels(output_codec_ctx, m_in.m_audio.m_codec_ctx.get()); } #if LAVU_DEP_OLD_CHANNEL_LAYOUT av_channel_layout_default(&output_codec_ctx->ch_layout, output_codec_ctx->ch_layout.nb_channels); #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT output_codec_ctx->channel_layout = static_cast(av_get_default_channel_layout(output_codec_ctx->channels)); #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT output_codec_ctx->sample_rate = m_in.m_audio.m_codec_ctx->sample_rate; orig_sample_rate = m_in.m_audio.m_codec_ctx->sample_rate; if (get_output_sample_rate(m_in.m_audio.m_stream->codecpar->sample_rate, params.m_audiosamplerate, &output_codec_ctx->sample_rate)) { // Limit sample rate Logging::trace(virtname(), "Limiting audio sample rate from %1 to %2.", format_samplerate(orig_sample_rate).c_str(), format_samplerate(output_codec_ctx->sample_rate).c_str()); orig_sample_rate = output_codec_ctx->sample_rate; } #if LAVC_USE_SUPPORTED_CFG { const int *supported_samplerates = nullptr; int num_samplerates = 0; int ret_cfg = avcodec_get_supported_config(output_codec_ctx, output_codec, AV_CODEC_CONFIG_SAMPLE_RATE, 0, (const void**)&supported_samplerates, &num_samplerates); if (ret_cfg >= 0 && supported_samplerates && num_samplerates > 0) { bool exact_match = false; for (int n = 0; n < num_samplerates; n++) { if (supported_samplerates[n] == output_codec_ctx->sample_rate) { exact_match = true; break; } } if (!exact_match) { int min_samplerate = 0, max_samplerate = INT_MAX; for (int n = 0; n < num_samplerates; n++) { int s = supported_samplerates[n]; if (min_samplerate <= s && output_codec_ctx->sample_rate >= s) min_samplerate = s; } for (int n = 0; n < num_samplerates; n++) { int s = supported_samplerates[n]; if (max_samplerate >= s && output_codec_ctx->sample_rate <= s) max_samplerate = s; } if (min_samplerate != 0 && max_samplerate != INT_MAX) { if (output_codec_ctx->sample_rate - min_samplerate < max_samplerate - output_codec_ctx->sample_rate) output_codec_ctx->sample_rate = min_samplerate; else output_codec_ctx->sample_rate = max_samplerate; } else if (min_samplerate != 0) { output_codec_ctx->sample_rate = min_samplerate; } else if (max_samplerate != INT_MAX) { output_codec_ctx->sample_rate = max_samplerate; } else { for (int n = 0; n < num_samplerates; n++) if (supported_samplerates[n] == 48000) { output_codec_ctx->sample_rate = 48000; break; } if (output_codec_ctx->sample_rate <= 0) output_codec_ctx->sample_rate = supported_samplerates[0]; } } } } #else if (output_codec->supported_samplerates != nullptr) { // Go through supported sample rates and adjust if necessary bool supported = false; for (int n = 0; output_codec->supported_samplerates[n] != 0; n++) { if (output_codec->supported_samplerates[n] == output_codec_ctx->sample_rate) { // Is supported supported = true; break; } } if (!supported) { int min_samplerate = 0; int max_samplerate = INT_MAX; // Find next lower sample rate in probably unsorted list for (int n = 0; output_codec->supported_samplerates[n] != 0; n++) { if (min_samplerate <= output_codec->supported_samplerates[n] && output_codec_ctx->sample_rate >= output_codec->supported_samplerates[n]) { min_samplerate = output_codec->supported_samplerates[n]; } } // Find next higher sample rate in probably unsorted list for (int n = 0; output_codec->supported_samplerates[n] != 0; n++) { if (max_samplerate >= output_codec->supported_samplerates[n] && output_codec_ctx->sample_rate <= output_codec->supported_samplerates[n]) { max_samplerate = output_codec->supported_samplerates[n]; } } if (min_samplerate != 0 && max_samplerate != INT_MAX) { // set to nearest value if (output_codec_ctx->sample_rate - min_samplerate < max_samplerate - output_codec_ctx->sample_rate) { output_codec_ctx->sample_rate = min_samplerate; } else { output_codec_ctx->sample_rate = max_samplerate; } } else if (min_samplerate != 0) { // No higher sample rate, use next lower output_codec_ctx->sample_rate = min_samplerate; } else if (max_samplerate != INT_MAX) { // No lower sample rate, use higher lower output_codec_ctx->sample_rate = max_samplerate; } else { // Should never happen... There must at least be one. Logging::error(virtname(), "The codec does not support an audio sample rate of %1.", format_samplerate(output_codec_ctx->sample_rate).c_str()); return AVERROR(EINVAL); } Logging::debug(virtname(), "Because the requested value is not supported by codec, the audio sample rate was changed from %1 to %2.", format_samplerate(orig_sample_rate).c_str(), format_samplerate(output_codec_ctx->sample_rate).c_str()); } } #endif // If sample format not pre-defined (not AV_SAMPLE_FMT_NONE), use input file setting. AVSampleFormat in_sample_format = (m_current_format->sample_format() == AV_SAMPLE_FMT_NONE) ? m_in.m_audio.m_codec_ctx->sample_fmt : m_current_format->sample_format(); #if LAVC_USE_SUPPORTED_CFG { // Query supported sample formats via the new API const enum AVSampleFormat *sample_fmts = nullptr; int num_fmts = 0; int ret_cfg = avcodec_get_supported_config(output_codec_ctx, output_codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**)&sample_fmts, &num_fmts); if (ret_cfg >= 0 && sample_fmts && num_fmts > 0) { AVSampleFormat input_fmt_planar = av_get_planar_sample_fmt(in_sample_format); for (int i = 0; i < num_fmts; i++) { AVSampleFormat output_fmt_planar = av_get_planar_sample_fmt(sample_fmts[i]); if (sample_fmts[i] == in_sample_format || (input_fmt_planar != AV_SAMPLE_FMT_NONE && input_fmt_planar == output_fmt_planar)) { output_codec_ctx->sample_fmt = sample_fmts[i]; break; } } if (output_codec_ctx->sample_fmt == AV_SAMPLE_FMT_NONE) { // Fallback: first supported format output_codec_ctx->sample_fmt = sample_fmts[0]; } } else { // If supported sample formats are unknown simply take input format output_codec_ctx->sample_fmt = in_sample_format; } } #else if (output_codec->sample_fmts != nullptr) { // Check if input sample format is supported and if so, use it (avoiding resampling) AVSampleFormat input_fmt_planar = av_get_planar_sample_fmt(in_sample_format); output_codec_ctx->sample_fmt = AV_SAMPLE_FMT_NONE; for (const AVSampleFormat *sample_fmt = output_codec->sample_fmts; *sample_fmt != -1; sample_fmt++) { AVSampleFormat output_fmt_planar = av_get_planar_sample_fmt(*sample_fmt); if (*sample_fmt == in_sample_format || (input_fmt_planar != AV_SAMPLE_FMT_NONE && input_fmt_planar == output_fmt_planar)) { output_codec_ctx->sample_fmt = *sample_fmt; break; } } // If none of the supported formats match use the first supported if (output_codec_ctx->sample_fmt == AV_SAMPLE_FMT_NONE) { output_codec_ctx->sample_fmt = output_codec->sample_fmts[0]; } } else { // If supported sample formats are unknown simply take input format and cross our fingers it'll work... output_codec_ctx->sample_fmt = in_sample_format; } #endif // Set the sample rate for the container. output_stream->time_base.den = output_codec_ctx->sample_rate; output_stream->time_base.num = 1; output_codec_ctx->time_base = output_stream->time_base; // set -strict -2 for aac (required for FFmpeg 2) dict_set_with_check(opt.address(), "strict", "-2", 0); // Allow the use of the experimental AAC encoder output_codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; // Set duration as hint for muxer if (m_in.m_audio.m_stream->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_audio.m_stream->duration, m_in.m_audio.m_stream->time_base, output_stream->time_base); } else if (m_in.m_format_ctx->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), output_stream->time_base); } //av_dict_set_int(&output_stream->metadata, "DURATION", output_stream->duration, AV_DICT_IGNORE_SUFFIX); // Save the encoder context for easier access later. m_out.m_audio.set_codec_ctx(output_codec_ctx); // Save the stream index m_out.m_audio.m_stream_idx = output_stream->index; // Save output audio stream for faster reference m_out.m_audio.m_stream = output_stream; // Update input to output stream map, this is rather boring because currently we only have a single audio stream add_stream_map(m_in.m_audio.m_stream_idx, m_out.m_audio.m_stream_idx); return 0; } #if IF_DECLARED_CONST int FFmpeg_Transcoder::configure_video_output_stream(AVCodecID codec_id, const AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt) #else // !IF_DECLARED_CONST int FFmpeg_Transcoder::configure_video_output_stream(AVCodecID codec_id, AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt) #endif // !IF_DECLARED_CONST { int ret = 0; BITRATE orig_bit_rate; if (m_hwaccel_enable_enc_buffering && m_hwaccel_enc_device_ctx != nullptr) { Logging::debug(virtname(), "Hardware encoder init: Creating a new hardware frame context for the encoder.", get_hwaccel_API_text(params.m_hwaccel_enc_API).c_str()); m_enc_hw_pix_fmt = get_hw_pix_fmt(output_codec, params.m_hwaccel_enc_device_type, false); ret = hwframe_ctx_set(output_codec_ctx, m_in.m_video.m_codec_ctx.get(), m_hwaccel_enc_device_ctx); if (ret < 0) { return ret; } } output_codec_ctx->codec_id = codec_id; // Set the basic encoder parameters orig_bit_rate = (m_in.m_video.m_stream->codecpar->bit_rate != 0) ? m_in.m_video.m_stream->codecpar->bit_rate : m_in.m_format_ctx->bit_rate; if (get_output_bit_rate(orig_bit_rate, params.m_videobitrate, &output_codec_ctx->bit_rate)) { // Limit sample rate Logging::trace(virtname(), "Limiting video bit rate from %1 to %2.", format_bitrate(orig_bit_rate).c_str(), format_bitrate(output_codec_ctx->bit_rate).c_str()); } // output_codec_ctx->rc_min_rate = output_codec_ctx->bit_rate * 75 / 100; // output_codec_ctx->rc_max_rate = output_codec_ctx->bit_rate * 125 / 100; // output_codec_ctx->qmin = 1; // output_codec_ctx->qmax = 31; int width = 0; int height = 0; if (get_video_size(&width, &height)) { Logging::trace(virtname(), "Changing video size from %1/%2 to %3/%4.", output_codec_ctx->width, output_codec_ctx->height, width, height); output_codec_ctx->width = width; output_codec_ctx->height = height; } else { output_codec_ctx->width = m_in.m_video.m_stream->codecpar->width; output_codec_ctx->height = m_in.m_video.m_stream->codecpar->height; } video_stream_setup(output_codec_ctx, output_stream, m_in.m_video.m_codec_ctx.get(), m_in.m_video.m_stream->avg_frame_rate, m_enc_hw_pix_fmt); if (!m_hwaccel_enable_enc_buffering && output_codec_ctx->codec_id == AV_CODEC_ID_H264 && m_out.m_filetype == FILETYPE::HLS && output_codec_ctx->pix_fmt != AV_PIX_FMT_YUV420P) { Logging::info(virtname(), "Forcing HLS/H.264 output pixel format from %1 to yuv420p for browser-compatible 8-bit output.", get_pix_fmt_name(output_codec_ctx->pix_fmt).c_str()); output_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; } AVRational sample_aspect_ratio = m_in.m_video.m_stream->codecpar->sample_aspect_ratio; if (output_codec_ctx->codec_id != AV_CODEC_ID_VP9 && m_out.m_filetype != FILETYPE::MKV) { output_codec_ctx->sample_aspect_ratio = sample_aspect_ratio; output_stream->codecpar->sample_aspect_ratio = sample_aspect_ratio; } else { // WebM and MKV do not respect the aspect ratio and always use 1:1 so we need to rescale "manually". /** * @todo FFmpeg actually *can* transcode while presevering the SAR. * FFmpegfs rescales to fix that problem. * Need to find out what I am doing wrong here... */ output_codec_ctx->sample_aspect_ratio = { 1, 1 }; output_stream->codecpar->sample_aspect_ratio = { 1, 1 }; // Make sure we do not zero width if (sample_aspect_ratio.num && sample_aspect_ratio.den) { output_codec_ctx->width = output_codec_ctx->width * sample_aspect_ratio.num / sample_aspect_ratio.den; } //output_codec_ctx->height *= sample_aspect_ratio.den; } // Set up optimisations switch (output_codec_ctx->codec_id) { case AV_CODEC_ID_H264: { ret = prepare_codec(output_codec_ctx->priv_data, m_out.m_filetype); if (ret < 0) { Logging::error(virtname(), "Could not set profile for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } if (m_hwaccel_enable_enc_buffering) { // For hardware encoding only // Defaults to 20. Set to 40 to create slightly smaller results (the bigger, the smaller the files). // Values seem to range between 1 and 51. // Must be non-zero, otherwise hardware encoding my fail with: // "Driver does not support any RC mode compatible with selected options (supported modes: CQP)." output_codec_ctx->global_quality = 40; } //#define EXPERIMENTAL #ifdef EXPERIMENTAL if (m_hwaccel_enable_enc_buffering) { // For hardware encoding only #if 0 // From libavcodec/vaapi_encode.c: // // Rate control mode selection: // * If the user has set a mode explicitly with the rc_mode option, // use it and fail if it is not available. // * If an explicit QP option has been set, use CQP. // * If the codec is CQ-only, use CQP. // * If the QSCALE avcodec option is set, use CQP. // * If bitrate and quality are both set, try QVBR. // * If quality is set, try ICQ, then CQP. // * If bitrate and maxrate are set and have the same value, try CBR. // * If a bitrate is set, try AVBR, then VBR, then CBR. // * If no bitrate is set, try ICQ, then CQP. ret = av_opt_set(output_codec_ctx->priv_data, "rc_mode", "CQP", AV_OPT_SEARCH_CHILDREN); if (ret < 0) { Logging::error(virtname(), "Could not set 'rc_mode=CQP' for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } // The range of the CRF scale is 0–51, where 0 is lossless (for 8 bit only, for 10 bit use -qp 0), // 23 is the default, and 51 is worst quality possible. A lower value generally leads to higher // quality, and a subjectively sane range is 17–28. Consider 17 or 18 to be visually lossless or // nearly so; it should look the same or nearly the same as the input but it isn't technically lossless. // See https://trac.ffmpeg.org/wiki/Encode/H.264 ret = av_opt_set(output_codec_ctx->priv_data, "qp", "30", AV_OPT_SEARCH_CHILDREN); if (ret < 0) { Logging::error(virtname(), "Could not set 'qp' for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } #endif } else { // Software encoding #if 0 // Affects hard- and software encoding // // The range of the CRF scale is 0–51, where 0 is lossless (for 8 bit only, for 10 bit use -qp 0), // 23 is the default, and 51 is worst quality possible. A lower value generally leads to higher // quality, and a subjectively sane range is 17–28. Consider 17 or 18 to be visually lossless or // nearly so; it should look the same or nearly the same as the input but it isn't technically lossless. // See https://trac.ffmpeg.org/wiki/Encode/H.264 ret = av_opt_set(output_codec_ctx->priv_data, "qp", "30", AV_OPT_SEARCH_CHILDREN); if (ret < 0) { Logging::error(virtname(), "Could not set 'qp' for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } #endif #if 0 // Set constant rate factor to avoid getting huge result files // The default is 23, but values between 30..40 create properly sized results. // Possible values are 0 (lossless) to 51 (very small but ugly results). ret = av_opt_set(output_codec_ctx->priv_data, "crf", "30", AV_OPT_SEARCH_CHILDREN); if (ret < 0) { Logging::error(virtname(), "Could not set 'crf' for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } #endif } #endif // EXPERIMENTAL // Avoid mismatches for H264 and profile uint8_t *out_val; ret = av_opt_get(output_codec_ctx->priv_data, "profile", 0, &out_val); if (!ret) { if (!strcasecmp(reinterpret_cast(out_val), "high")) { switch (output_codec_ctx->pix_fmt) { case AV_PIX_FMT_YUV420P9BE: case AV_PIX_FMT_YUV420P9LE: case AV_PIX_FMT_YUV420P10BE: case AV_PIX_FMT_YUV420P10LE: { ret = av_opt_set(output_codec_ctx->priv_data, "profile", "high10", 0); if (ret < 0) { Logging::error(virtname(), "Could not set profile=high10 for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } break; } case AV_PIX_FMT_YUYV422: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUVJ422P: case AV_PIX_FMT_UYVY422: case AV_PIX_FMT_YUV422P16LE: case AV_PIX_FMT_YUV422P16BE: case AV_PIX_FMT_YUV422P10BE: case AV_PIX_FMT_YUV422P10LE: case AV_PIX_FMT_YUV422P9BE: case AV_PIX_FMT_YUV422P9LE: case AV_PIX_FMT_YUVA422P9BE: case AV_PIX_FMT_YUVA422P9LE: case AV_PIX_FMT_YUVA422P10BE: case AV_PIX_FMT_YUVA422P10LE: case AV_PIX_FMT_YUVA422P16BE: case AV_PIX_FMT_YUVA422P16LE: case AV_PIX_FMT_NV16: case AV_PIX_FMT_NV20LE: case AV_PIX_FMT_NV20BE: case AV_PIX_FMT_YVYU422: case AV_PIX_FMT_YUVA422P: case AV_PIX_FMT_YUV422P12BE: case AV_PIX_FMT_YUV422P12LE: case AV_PIX_FMT_YUV422P14BE: case AV_PIX_FMT_YUV422P14LE: { ret = av_opt_set(output_codec_ctx->priv_data, "profile", "high422", 0); if (ret < 0) { Logging::error(virtname(), "Could not set profile=high422 for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } break; } case AV_PIX_FMT_YUV444P: case AV_PIX_FMT_YUVJ444P: case AV_PIX_FMT_YUV444P16LE: case AV_PIX_FMT_YUV444P16BE: case AV_PIX_FMT_RGB444LE: case AV_PIX_FMT_RGB444BE: case AV_PIX_FMT_BGR444LE: case AV_PIX_FMT_BGR444BE: case AV_PIX_FMT_YUV444P9BE: case AV_PIX_FMT_YUV444P9LE: case AV_PIX_FMT_YUV444P10BE: case AV_PIX_FMT_YUV444P10LE: case AV_PIX_FMT_GBRP: case AV_PIX_FMT_GBRP9BE: case AV_PIX_FMT_GBRP9LE: case AV_PIX_FMT_GBRP10BE: case AV_PIX_FMT_GBRP10LE: case AV_PIX_FMT_GBRP16BE: case AV_PIX_FMT_GBRP16LE: case AV_PIX_FMT_YUVA444P9BE: case AV_PIX_FMT_YUVA444P9LE: case AV_PIX_FMT_YUVA444P10BE: case AV_PIX_FMT_YUVA444P10LE: case AV_PIX_FMT_YUVA444P16BE: case AV_PIX_FMT_YUVA444P16LE: case AV_PIX_FMT_XYZ12LE: case AV_PIX_FMT_XYZ12BE: case AV_PIX_FMT_YUVA444P: case AV_PIX_FMT_GBRAP: case AV_PIX_FMT_GBRAP16BE: case AV_PIX_FMT_GBRAP16LE: case AV_PIX_FMT_YUV444P12BE: case AV_PIX_FMT_YUV444P12LE: case AV_PIX_FMT_YUV444P14BE: case AV_PIX_FMT_YUV444P14LE: case AV_PIX_FMT_GBRP12BE: case AV_PIX_FMT_GBRP12LE: case AV_PIX_FMT_GBRP14BE: case AV_PIX_FMT_GBRP14LE: case AV_PIX_FMT_AYUV64LE: case AV_PIX_FMT_AYUV64BE: { ret = av_opt_set(output_codec_ctx->priv_data, "profile", "high444", 0); if (ret < 0) { Logging::error(virtname(), "Could not set profile=high444 for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } break; } default: { break; } } } av_free(out_val); } break; } case AV_CODEC_ID_VP9: { ret = prepare_codec(output_codec_ctx->priv_data, FILETYPE::WEBM); if (ret < 0) { Logging::error(virtname(), "Could not set profile for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } break; } case AV_CODEC_ID_PRORES: { ret = prepare_codec(output_codec_ctx->priv_data, FILETYPE::PRORES); if (ret < 0) { Logging::error(virtname(), "Could not set profile for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } // 0 = ‘proxy’, // 1 = ‘lt’, // 2 = ‘standard’, // 3 = ‘hq’ output_codec_ctx->profile = static_cast(params.m_level); break; } case AV_CODEC_ID_ALAC: { ret = prepare_codec(output_codec_ctx->priv_data, FILETYPE::ALAC); if (ret < 0) { Logging::error(virtname(), "Could not set profile for %1 output codec %2 (error '%3').", get_media_type_string(output_codec->type), get_codec_name(codec_id), ffmpeg_geterror(ret).c_str()); return ret; } break; } default: { break; } } // Initialise pixel format conversion and rescaling if necessary get_pix_formats(&m_in.m_pix_fmt, &m_out.m_pix_fmt, output_codec_ctx); ret = init_rescaler(m_in.m_pix_fmt, m_in.m_video.m_stream->codecpar->width, m_in.m_video.m_stream->codecpar->height, m_out.m_pix_fmt, output_codec_ctx->width, output_codec_ctx->height); if (ret < 0) { return ret; } // set -strict -2 for aac (required for FFmpeg 2) dict_set_with_check(opt.address(), "strict", "-2", 0); // Allow the use of the experimental AAC encoder output_codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; #ifdef _DEBUG print_stream_info(output_stream); #endif // _DEBUG // Set duration as hint for muxer if (m_in.m_video.m_stream->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_video.m_stream->duration, m_in.m_video.m_stream->time_base, output_stream->time_base); } else if (m_in.m_format_ctx->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), output_stream->time_base); } //av_dict_set_int(&output_stream->metadata, "DURATION", output_stream->duration, AV_DICT_IGNORE_SUFFIX); // Save the encoder context for easier access later. m_out.m_video.set_codec_ctx(output_codec_ctx); // Save the stream index m_out.m_video.m_stream_idx = output_stream->index; // Save output video stream for faster reference m_out.m_video.m_stream = output_stream; // Update input to output stream map, this is rather boring because currently we only have a single video stream add_stream_map(m_in.m_video.m_stream_idx, m_out.m_video.m_stream_idx); return 0; } void FFmpeg_Transcoder::set_output_format_duration_metadata() { // Although docs state this is "Demuxing only", this is actually used by encoders like Matroska/WebM, so we need to set this here. m_out.m_format_ctx->duration = m_in.m_format_ctx->duration; if (m_virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { av_dict_set_int(&m_out.m_format_ctx->metadata, "DURATION", m_virtualfile->m_cuesheet_track.m_duration, AV_DICT_IGNORE_SUFFIX); } else { av_dict_set_int(&m_out.m_format_ctx->metadata, "DURATION", m_out.m_format_ctx->duration, AV_DICT_IGNORE_SUFFIX); } } #if IF_DECLARED_CONST int FFmpeg_Transcoder::open_output_encoder(AVCodecID codec_id, const AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt) #else // !IF_DECLARED_CONST int FFmpeg_Transcoder::open_output_encoder(AVCodecID codec_id, AVCodec *output_codec, AVCodecContext *output_codec_ctx, AVStream *output_stream, FFmpeg_Dictionary &opt) #endif // !IF_DECLARED_CONST { // Some formats want stream headers to be separate. if (m_out.m_format_ctx->oformat->flags & AVFMT_GLOBALHEADER) { output_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } if (!av_dict_get(opt, "threads", nullptr, 0)) { Logging::trace(virtname(), "Setting threads to auto for codec %1.", get_codec_name(output_codec_ctx->codec_id)); dict_set_with_check(opt.address(), "threads", "auto", 0, virtname()); } // Open the encoder for the stream to use it later. int ret = avcodec_open2(output_codec_ctx, output_codec, opt.address()); if (ret < 0) { Logging::error(virtname(), "Could not open %1 output codec %2 for stream #%3 (error '%4').", get_media_type_string(output_codec->type), get_codec_name(codec_id), output_stream->index, ffmpeg_geterror(ret).c_str()); return ret; } Logging::debug(virtname(), "Opened %1 output codec %2 for stream #%3.", get_media_type_string(output_codec->type), get_codec_name(codec_id, true), output_stream->index); ret = avcodec_parameters_from_context(output_stream->codecpar, output_codec_ctx); if (ret < 0) { Logging::error(virtname(), "Could not initialise stream parameters (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } return 0; } int FFmpeg_Transcoder::add_stream(AVCodecID codec_id) { AVCodecContext *output_codec_ctx = nullptr; AVStream * output_stream = nullptr; #if IF_DECLARED_CONST const AVCodec * output_codec = nullptr; #else // !IF_DECLARED_CONST AVCodec * output_codec = nullptr; #endif // !IF_DECLARED_CONST FFmpeg_Dictionary opt; int ret = find_output_encoder(codec_id, &output_codec); if (ret < 0) { return ret; } ret = allocate_output_stream_and_context(codec_id, output_codec, &output_stream, &output_codec_ctx); if (ret < 0) { return ret; } std::unique_ptr output_codec_ctx_guard( output_codec_ctx, [](AVCodecContext *ctx) { avcodec_free_context(&ctx); }); switch (output_codec->type) { case AVMEDIA_TYPE_AUDIO: { ret = configure_audio_output_stream(codec_id, output_codec, output_codec_ctx, output_stream, opt); break; } case AVMEDIA_TYPE_VIDEO: { ret = configure_video_output_stream(codec_id, output_codec, output_codec_ctx, output_stream, opt); break; } default: { Logging::error(virtname(), "Unsupported output codec type '%1' for codec %2.", get_media_type_string(output_codec->type), get_codec_name(codec_id)); ret = AVERROR(EINVAL); break; } } if (ret < 0) { return ret; } // configure_*_output_stream() transfers the encoder context to m_out. output_codec_ctx_guard.release(); set_output_format_duration_metadata(); ret = open_output_encoder(codec_id, output_codec, output_codec_ctx, output_stream, opt); if (ret < 0) { return ret; } return (output_stream->index); } int FFmpeg_Transcoder::add_subtitle_stream(AVCodecID codec_id, StreamRef & input_streamref, const std::optional & language) { AVCodecContext *output_codec_ctx = nullptr; AVStream * output_stream = nullptr; #if IF_DECLARED_CONST const AVCodec * output_codec = nullptr; #else // !IF_DECLARED_CONST AVCodec * output_codec = nullptr; #endif // !IF_DECLARED_CONST FFmpeg_Dictionary opt; int ret; // find the encoder output_codec = avcodec_find_encoder(codec_id); if (output_codec == nullptr) { Logging::error(virtname(), "'%1' encoder could not be found.", avcodec_get_name(codec_id)); return AVERROR(EINVAL); } output_stream = avformat_new_stream(m_out.m_format_ctx, output_codec); if (output_stream == nullptr) { Logging::error(virtname(), "Could not allocate stream for encoder '%1'.", avcodec_get_name(codec_id)); return AVERROR(ENOMEM); } output_stream->id = static_cast(m_out.m_format_ctx->nb_streams - 1); output_codec_ctx = avcodec_alloc_context3(output_codec); if (output_codec_ctx == nullptr) { Logging::error(virtname(), "Could not allocate an encoding context for encoder '%1'.", avcodec_get_name(codec_id)); return AVERROR(ENOMEM); } std::unique_ptr output_codec_ctx_guard( output_codec_ctx, [](AVCodecContext *ctx) { avcodec_free_context(&ctx); }); output_stream->time_base = input_streamref.m_stream->time_base; output_codec_ctx->time_base = output_stream->time_base; // set -strict -2 for aac (required for FFmpeg 2) dict_set_with_check(opt.address(), "strict", "-2", 0); // Allow the use of the experimental AAC encoder output_codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; // Set duration as hint for muxer if (m_in.m_video.m_stream != nullptr && m_in.m_video.m_stream->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_video.m_stream->duration, m_in.m_video.m_stream->time_base, output_stream->time_base); } if (m_in.m_audio.m_stream != nullptr && m_in.m_audio.m_stream->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_audio.m_stream->duration, m_in.m_audio.m_stream->time_base, output_stream->time_base); } else if (m_in.m_format_ctx != nullptr && m_in.m_format_ctx->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_format_ctx->duration, av_get_time_base_q(), output_stream->time_base); } AVCodecContext * input_codec_ctx = input_streamref.m_codec_ctx.get(); if (input_codec_ctx != nullptr && input_codec_ctx->subtitle_header != nullptr) { // ASS code assumes this buffer is null terminated so add extra byte. output_codec_ctx->subtitle_header = static_cast(av_mallocz(static_cast(input_codec_ctx->subtitle_header_size) + 1)); if (output_codec_ctx->subtitle_header == nullptr) { return AVERROR(ENOMEM); } std::memcpy(output_codec_ctx->subtitle_header, input_codec_ctx->subtitle_header, static_cast(input_codec_ctx->subtitle_header_size)); output_codec_ctx->subtitle_header_size = input_codec_ctx->subtitle_header_size; } else if (output_codec_ctx->codec_id == AV_CODEC_ID_WEBVTT || output_codec_ctx->codec_id == AV_CODEC_ID_SUBRIP) { // If source had no header, we create a default one ret = get_script_info(output_codec_ctx, ASS_DEFAULT_PLAYRESX, ASS_DEFAULT_PLAYRESY, ASS_DEFAULT_FONT, ASS_DEFAULT_FONT_SIZE, ASS_DEFAULT_COLOUR, ASS_DEFAULT_COLOUR, ASS_DEFAULT_BACK_COLOUR, ASS_DEFAULT_BACK_COLOUR, ASS_DEFAULT_BOLD, ASS_DEFAULT_ITALIC, ASS_DEFAULT_UNDERLINE, ASS_DEFAULT_BORDERSTYLE, ASS_DEFAULT_ALIGNMENT); if (ret) { Logging::error(virtname(), "Could not create ASS script info for encoder '%1'.", avcodec_get_name(codec_id)); return ret; } } // Open the encoder for the stream to use it later. ret = avcodec_open2(output_codec_ctx, output_codec, opt.address()); if (ret < 0) { Logging::error(virtname(), "Could not open %1 output codec %2 for stream #%3 (error '%4').", get_media_type_string(output_codec->type), get_codec_name(codec_id), output_stream->index, ffmpeg_geterror(ret).c_str()); return ret; } Logging::debug(virtname(), "Opened %1 output codec %2 for stream #%3.", get_media_type_string(output_codec->type), get_codec_name(codec_id, true), output_stream->index); ret = avcodec_parameters_from_context(output_stream->codecpar, output_codec_ctx); if (ret < 0) { Logging::error(virtname(), "Could not initialise stream parameters (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } if (!language) { // Copy language AVDictionaryEntry *tag = nullptr; tag = av_dict_get(input_streamref.m_stream->metadata, "language", nullptr, AV_DICT_IGNORE_SUFFIX); if (tag != nullptr) { av_dict_set(&output_stream->metadata, "language", tag->value, AV_DICT_IGNORE_SUFFIX); } } else { // Use predefined value av_dict_set(&output_stream->metadata, "language", language->c_str(), AV_DICT_IGNORE_SUFFIX); } // Save the encoder context for easier access later. StreamRef output_streamref; output_streamref.set_codec_ctx(output_codec_ctx_guard.release()); // Save the stream index output_streamref.m_stream_idx = output_stream->index; // Save output audio stream for faster reference output_streamref.m_stream = output_stream; m_out.m_subtitle.emplace(output_streamref.m_stream_idx, output_streamref); // Update input to output stream map add_stream_map(input_streamref.m_stream_idx, output_streamref.m_stream_idx); return output_streamref.m_stream_idx; } int FFmpeg_Transcoder::allocate_stream_copy(AVCodecID codec_id, AVStream **output_stream) { *output_stream = avformat_new_stream(m_out.m_format_ctx, nullptr); if (*output_stream == nullptr) { Logging::error(virtname(), "Could not allocate stream for encoder '%1'.", avcodec_get_name(codec_id)); return AVERROR(ENOMEM); } (*output_stream)->id = static_cast(m_out.m_format_ctx->nb_streams - 1); return 0; } int FFmpeg_Transcoder::configure_audio_stream_copy(AVStream *output_stream) { int ret = 0; ret = avcodec_parameters_copy(output_stream->codecpar, m_in.m_audio.m_stream->codecpar); if (ret < 0) { Logging::error(virtname(), "Could not allocate an encoding context (error '%2').", ffmpeg_geterror(ret).c_str()); return ret; } // Set the sample rate for the container. output_stream->time_base = m_in.m_audio.m_stream->time_base; // Set duration as hint for muxer if (m_in.m_audio.m_stream->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_audio.m_stream->duration, m_in.m_audio.m_stream->time_base, output_stream->time_base); } // Save the encoder context for easier access later. m_out.m_audio.set_codec_ctx(nullptr); // Save the stream index m_out.m_audio.m_stream_idx = output_stream->index; // Save output audio stream for faster reference m_out.m_audio.m_stream = output_stream; // Update input to output stream map, this is rather boring because currently we only have a single audio stream add_stream_map(m_in.m_audio.m_stream_idx, m_out.m_audio.m_stream_idx); return 0; } int FFmpeg_Transcoder::configure_video_stream_copy(AVStream *output_stream) { int ret = 0; ret = avcodec_parameters_copy(output_stream->codecpar, m_in.m_video.m_stream->codecpar); if (ret < 0) { Logging::error(virtname(), "Could not allocate an encoding context (error '%2').", ffmpeg_geterror(ret).c_str()); return ret; } output_stream->time_base = m_in.m_video.m_stream->time_base; #ifdef _DEBUG print_stream_info(output_stream); #endif // _DEBUG // Set duration as hint for muxer if (m_in.m_video.m_stream->duration != AV_NOPTS_VALUE) { output_stream->duration = ffmpeg_rescale_q(m_in.m_video.m_stream->duration, m_in.m_video.m_stream->time_base, output_stream->time_base); } // Save the encoder context for easier access later. m_out.m_video.set_codec_ctx(nullptr); // Save the stream index m_out.m_video.m_stream_idx = output_stream->index; // Save output video stream for faster reference m_out.m_video.m_stream = output_stream; // Update input to output stream map, this is rather boring because currently we only have a single video stream add_stream_map(m_in.m_video.m_stream_idx, m_out.m_video.m_stream_idx); return 0; } int FFmpeg_Transcoder::add_stream_copy(AVCodecID codec_id, AVMediaType codec_type) { AVStream *output_stream = nullptr; int ret = allocate_stream_copy(codec_id, &output_stream); if (ret < 0) { return ret; } switch (codec_type) { case AVMEDIA_TYPE_AUDIO: { ret = configure_audio_stream_copy(output_stream); break; } case AVMEDIA_TYPE_VIDEO: { ret = configure_video_stream_copy(output_stream); break; } default: { Logging::error(virtname(), "Unsupported stream-copy media type '%1' for codec %2.", get_media_type_string(codec_type), get_codec_name(codec_id)); ret = AVERROR(EINVAL); break; } } if (ret < 0) { return ret; } output_stream->codecpar->codec_tag = 0; return 0; } int FFmpeg_Transcoder::add_albumart_stream(const AVCodecContext * input_codec_ctx) { AVCodecContext * output_codec_ctx = nullptr; AVStream * output_stream = nullptr; if (input_codec_ctx == nullptr) { Logging::error(virtname(), "Cannot add album art stream without an input codec context."); return AVERROR(EINVAL); } const AVCodecID input_codec_id = input_codec_ctx->codec_id; const AVCodec * output_codec = nullptr; FFmpeg_Dictionary opt; int ret; // find the encoder output_codec = avcodec_find_encoder(input_codec_id); if (output_codec == nullptr) { Logging::error(virtname(), "'%1' encoder could not be found.", avcodec_get_name(input_codec_id)); return AVERROR(EINVAL); } // Must be a video codec if (output_codec->type != AVMEDIA_TYPE_VIDEO) { Logging::error(virtname(), "INTERNAL TROUBLE! Encoder '%1' is not a video codec.", avcodec_get_name(input_codec_id)); return AVERROR(EINVAL); } output_stream = avformat_new_stream(m_out.m_format_ctx, output_codec); if (output_stream == nullptr) { Logging::error(virtname(), "Could not allocate stream for encoder '%1'.", avcodec_get_name(input_codec_id)); return AVERROR(ENOMEM); } output_stream->id = static_cast(m_out.m_format_ctx->nb_streams - 1); output_codec_ctx = avcodec_alloc_context3(output_codec); if (output_codec_ctx == nullptr) { Logging::error(virtname(), "Could not allocate an encoding context."); return AVERROR(ENOMEM); } std::unique_ptr output_codec_ctx_guard( output_codec_ctx, [](AVCodecContext *ctx) { avcodec_free_context(&ctx); }); // Ignore missing width/height when adding album arts #if !IF_DECLARED_CONST m_out.m_format_ctx->oformat->flags |= AVFMT_NODIMENSIONS; #endif // !IF_DECLARED_CONST // This is required for some reason (let encoder decide?) // If not set, write header will fail! //output_codec_ctx->codec_tag = 0; //av_codec_get_tag(of->codec_tag, codec->codec_id); //output_stream->codec->framerate = { 1, 0 }; /** @todo Add support for album arts */ // mp4 album arts do not work with ipod profile. Set mp4. //if (m_out.m_format_ctx->oformat->mime_type != nullptr && (!strcmp(m_out.m_format_ctx->oformat->mime_type, "application/mp4") || !strcmp(m_out.m_format_ctx->oformat->mime_type, "video/mp4"))) //{ // m_out.m_format_ctx->oformat->name = "mp4"; // m_out.m_format_ctx->oformat->mime_type = "application/mp4"; //} // copy disposition // output_stream->disposition = input_stream->disposition; output_stream->disposition = AV_DISPOSITION_ATTACHED_PIC; // copy estimated duration as a hint to the muxer if (output_stream->duration <= 0 && m_in.m_audio.m_stream != nullptr && m_in.m_audio.m_stream->duration > 0) { output_stream->duration = ffmpeg_rescale_q(m_in.m_audio.m_stream->duration, m_in.m_audio.m_stream->time_base, output_stream->time_base); } output_codec_ctx->time_base = { 1, 90000 }; output_stream->time_base = { 1, 90000 }; output_codec_ctx->pix_fmt = input_codec_ctx->pix_fmt; output_codec_ctx->width = input_codec_ctx->width; output_codec_ctx->height = input_codec_ctx->height; // Some formats want stream headers to be separate. if (m_out.m_format_ctx->oformat->flags & AVFMT_GLOBALHEADER) { output_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // Open the encoder for the stream to use it later. ret = avcodec_open2(output_codec_ctx, output_codec, opt.address()); if (ret < 0) { Logging::error(virtname(), "Could not open %1 output codec %2 for stream #%3 (error '%4').", get_media_type_string(output_codec->type), get_codec_name(input_codec_id), output_stream->index, ffmpeg_geterror(ret).c_str()); return ret; } Logging::debug(virtname(), "Opened album art output codec %1 for stream #%2 (dimensions %3x%4).", get_codec_name(input_codec_id, true), output_stream->index, output_codec_ctx->width, output_codec_ctx->height); ret = avcodec_parameters_from_context(output_stream->codecpar, output_codec_ctx); if (ret < 0) { Logging::error(virtname(), "Could not initialise stream parameters stream #%1 (error '%2').", output_stream->index, ffmpeg_geterror(ret).c_str()); return ret; } StreamRef streamref; streamref.set_codec_ctx(output_codec_ctx_guard.release()); streamref.m_stream = output_stream; streamref.m_stream_idx = output_stream->index; m_out.m_album_art.push_back(streamref); return 0; } int FFmpeg_Transcoder::add_albumart_frame(AVStream *output_stream, AVPacket *pkt_in) { FFmpeg_Packet tmp_pkt(pkt_in); int ret = tmp_pkt.res(); if (ret < 0) { Logging::error(virtname(), "Could not write album art packet (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } Logging::trace(virtname(), "Adding album art stream #%1.", output_stream->index); tmp_pkt->stream_index = output_stream->index; tmp_pkt->flags |= AV_PKT_FLAG_KEY; // Contains a single frame, make sure it's a key frame tmp_pkt->pos = 0; tmp_pkt->dts = 0; return store_packet(tmp_pkt, AVMEDIA_TYPE_ATTACHMENT); } int FFmpeg_Transcoder::open_output_filestreams(Buffer *buffer) { m_out.m_filetype = m_current_format->filetype(); Logging::debug(virtname(), "Opening output format '%1'.", m_current_format->desttype().c_str()); // Reset active streams m_active_stream_msk = 0; m_inhibit_stream_msk = 0; // Check if we can copy audio or video. m_copy_audio = can_copy_stream(m_in.m_audio.m_stream); m_copy_video = can_copy_stream(m_in.m_video.m_stream); // Create a new format context for the output container format. int ret = 0; if (m_current_format->format_name() != "m4a") { ret = m_out.m_format_ctx.alloc_output_context(m_current_format->format_name().c_str(), nullptr); } else { ret = m_out.m_format_ctx.alloc_output_context(nullptr, ".m4a"); } if (ret < 0 || m_out.m_format_ctx == nullptr) { Logging::error(virtname(), "Could not allocate an output format context."); return (ret < 0) ? ret : AVERROR(ENOMEM); } if (!m_is_video) { m_in.m_video.m_stream_idx = INVALID_STREAM; } //video_codec = m_out.m_format_ctx->oformat->video_codec; // Open video streams if (stream_exists(m_in.m_video.m_stream_idx) && m_current_format->video_codec() != AV_CODEC_ID_NONE) { m_active_stream_msk |= FFMPEGFS_VIDEO; if (!m_copy_video) { int ret; ret = add_stream(m_current_format->video_codec()); if (ret < 0) { return ret; } if (params.m_deinterlace) { // Init deinterlace filters ret = init_deinterlace_filters(m_in.m_video.m_codec_ctx.get(), m_in.m_pix_fmt, m_in.m_video.m_stream->avg_frame_rate, m_in.m_video.m_stream->time_base); if (ret < 0) { return ret; } } } else { int ret; Logging::info(virtname(), "Copying video stream."); ret = add_stream_copy(m_current_format->video_codec(), AVMEDIA_TYPE_VIDEO); if (ret < 0) { return ret; } } } // Open audio streams if (stream_exists(m_in.m_audio.m_stream_idx) && m_current_format->audio_codec() != AV_CODEC_ID_NONE) { m_active_stream_msk |= FFMPEGFS_AUDIO; if (!m_copy_audio) { int ret; ret = add_stream(m_current_format->audio_codec()); if (ret < 0) { return ret; } } else { int ret; Logging::info(virtname(), "Copying audio stream."); ret = add_stream_copy(m_current_format->audio_codec(), AVMEDIA_TYPE_AUDIO); if (ret < 0) { return ret; } } } if (stream_exists(m_in.m_video.m_stream_idx) && m_current_format->video_codec() != AV_CODEC_ID_NONE) { int ret; // Open subtitle streams if (m_in.m_subtitle.size()) { // No copy option, not worth it for a few frames // Create as many subtitle streams as required ret = add_subtitle_streams(); if (ret < 0) { return ret; } } // Open streams for external subtitle files ret = add_external_subtitle_streams(); if (ret < 0) { return ret; } } if (!params.m_noalbumarts && m_current_format->albumart_supported()) { for (StreamRef & album_art : m_in.m_album_art) { int ret; ret = add_albumart_stream(album_art.m_codec_ctx.get()); if (ret < 0) { return ret; } } } const int buf_size = 5*1024*1024; unsigned char *iobuffer = static_cast(av_malloc(buf_size + FF_INPUT_BUFFER_PADDING_SIZE)); if (iobuffer == nullptr) { Logging::error(virtname(), "Out of memory opening output file: Unable to allocate I/O buffer."); return AVERROR(ENOMEM); } // open the output file m_out.m_format_ctx->pb = avio_alloc_context( iobuffer, buf_size, 1, static_cast(buffer), nullptr, // read not required output_write, // write (m_current_format->audio_codec() != AV_CODEC_ID_OPUS) ? seek : nullptr); // seek if (m_out.m_format_ctx->pb == nullptr) { Logging::error(virtname(), "Out of memory opening output file: Unable to allocate format context."); av_freep(&iobuffer); return AVERROR(ENOMEM); } m_out.m_format_ctx.set_custom_io(); return 0; } int FFmpeg_Transcoder::add_subtitle_streams() { for (auto & [key, value] : m_in.m_subtitle) { AVCodecID codec_id = m_current_format->subtitle_codec(value.m_stream->codecpar->codec_id); // Get matching output codec if (codec_id == AV_CODEC_ID_NONE) { // No matching output codec continue; } if (map_in_to_out_stream(value.m_stream_idx) != INVALID_STREAM) { // Already existing continue; } //m_active_stream_msk |= FFMPEGFS_SUBTITLE; int ret; ret = add_subtitle_stream(codec_id, value); if (ret < 0) { return ret; } } return 0; } int FFmpeg_Transcoder::init_resampler() { // Fail save: if channel layout not known assume mono or stereo #if LAVU_DEP_OLD_CHANNEL_LAYOUT if (!m_in.m_audio.m_codec_ctx->ch_layout.nb_channels) { av_channel_layout_default(&m_in.m_audio.m_codec_ctx->ch_layout, 2); } #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT if (!m_in.m_audio.m_codec_ctx->channel_layout) { m_in.m_audio.m_codec_ctx->channel_layout = static_cast(av_get_default_channel_layout(m_in.m_audio.m_codec_ctx->channels)); } if (!m_in.m_audio.m_codec_ctx->channel_layout) { m_in.m_audio.m_codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; } #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT // Only initialise the resampler if it is necessary, i.e., // if and only if the sample formats differ. #if LAVU_DEP_OLD_CHANNEL_LAYOUT if (m_in.m_audio.m_codec_ctx->sample_fmt == m_out.m_audio.m_codec_ctx->sample_fmt && m_in.m_audio.m_codec_ctx->sample_rate == m_out.m_audio.m_codec_ctx->sample_rate && !av_channel_layout_compare(&m_in.m_audio.m_codec_ctx->ch_layout, &m_out.m_audio.m_codec_ctx->ch_layout)) #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT if (m_in.m_audio.m_codec_ctx->sample_fmt == m_out.m_audio.m_codec_ctx->sample_fmt && m_in.m_audio.m_codec_ctx->sample_rate == m_out.m_audio.m_codec_ctx->sample_rate && m_in.m_audio.m_codec_ctx->channel_layout == m_out.m_audio.m_codec_ctx->channel_layout) #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT { // Formats are same close_resample(); return 0; } if ((m_audio_resample_ctx == nullptr) || (m_cur_sample_fmt != m_in.m_audio.m_codec_ctx->sample_fmt) || (m_cur_sample_rate != m_in.m_audio.m_codec_ctx->sample_rate) || #if LAVU_DEP_OLD_CHANNEL_LAYOUT av_channel_layout_compare(&m_cur_ch_layout, &m_in.m_audio.m_codec_ctx->ch_layout)) #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT (m_cur_channel_layout != m_in.m_audio.m_codec_ctx->channel_layout)) #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT { int ret; #if LAVU_DEP_OLD_CHANNEL_LAYOUT Logging::debug(virtname(), "Creating audio resampler: %1 -> %2 / %3 -> %4 / %5 -> %6.", get_sample_fmt_name(m_in.m_audio.m_codec_ctx->sample_fmt).c_str(), get_sample_fmt_name(m_out.m_audio.m_codec_ctx->sample_fmt).c_str(), format_samplerate(m_in.m_audio.m_codec_ctx->sample_rate).c_str(), format_samplerate(m_out.m_audio.m_codec_ctx->sample_rate).c_str(), get_channel_layout_name(&m_in.m_audio.m_codec_ctx->ch_layout).c_str(), get_channel_layout_name(&m_out.m_audio.m_codec_ctx->ch_layout).c_str()); #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT Logging::debug(virtname(), "Creating audio resampler: %1 -> %2 / %3 -> %4 / %5 -> %6.", get_sample_fmt_name(m_in.m_audio.m_codec_ctx->sample_fmt).c_str(), get_sample_fmt_name(m_out.m_audio.m_codec_ctx->sample_fmt).c_str(), format_samplerate(m_in.m_audio.m_codec_ctx->sample_rate).c_str(), format_samplerate(m_out.m_audio.m_codec_ctx->sample_rate).c_str(), get_channel_layout_name(m_in.m_audio.m_codec_ctx->channels, m_in.m_audio.m_codec_ctx->channel_layout).c_str(), get_channel_layout_name(m_out.m_audio.m_codec_ctx->channels, m_out.m_audio.m_codec_ctx->channel_layout).c_str()); #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT close_resample(); m_cur_sample_fmt = m_in.m_audio.m_codec_ctx->sample_fmt; m_cur_sample_rate = m_in.m_audio.m_codec_ctx->sample_rate; #if LAVU_DEP_OLD_CHANNEL_LAYOUT av_channel_layout_copy(&m_cur_ch_layout, &m_in.m_audio.m_codec_ctx->ch_layout); #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT m_cur_channel_layout = m_in.m_audio.m_codec_ctx->channel_layout; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT // Create a resampler context for the conversion. // Set the conversion parameters. #if SWR_DEP_ALLOC_SET_OPTS ret = swr_alloc_set_opts2(m_audio_resample_ctx.address(), &m_out.m_audio.m_codec_ctx->ch_layout, m_out.m_audio.m_codec_ctx->sample_fmt, m_out.m_audio.m_codec_ctx->sample_rate, &m_in.m_audio.m_codec_ctx->ch_layout, m_in.m_audio.m_codec_ctx->sample_fmt, m_in.m_audio.m_codec_ctx->sample_rate, 0, nullptr); if (ret < 0) { Logging::error(virtname(), "Could not allocate resample context (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } #else m_audio_resample_ctx.reset(swr_alloc_set_opts(nullptr, static_cast(m_out.m_audio.m_codec_ctx->channel_layout), m_out.m_audio.m_codec_ctx->sample_fmt, m_out.m_audio.m_codec_ctx->sample_rate, static_cast(m_in.m_audio.m_codec_ctx->channel_layout), m_in.m_audio.m_codec_ctx->sample_fmt, m_in.m_audio.m_codec_ctx->sample_rate, 0, nullptr)); if (m_audio_resample_ctx == nullptr) { Logging::error(virtname(), "Could not allocate a resample context."); return AVERROR(ENOMEM); } #endif // Open the resampler with the specified parameters. ret = swr_init(m_audio_resample_ctx); if (ret < 0) { Logging::error(virtname(), "Could not open resampler context (error '%1').", ffmpeg_geterror(ret).c_str()); m_audio_resample_ctx.reset(); return ret; } } return 0; } int FFmpeg_Transcoder::init_audio_fifo() { // Create the audio FIFO buffer based on the specified output sample format. int ret = m_audio_fifo.alloc(m_out.m_audio.m_codec_ctx->sample_fmt, get_channels(m_out.m_audio.m_codec_ctx.get()), 1); if (ret < 0) { Logging::error(virtname(), "Could not allocate an audio FIFO."); return ret; } return 0; } int FFmpeg_Transcoder::update_format(AVDictionary** dict, const PROFILE_OPTION_VEC &option_vec) const { int ret = 0; for (const PROFILE_OPTION & option : option_vec) { if ((option.m_options & OPT_AUDIO) && stream_exists(m_out.m_video.m_stream_idx)) { // Option for audio only, but file contains video stream continue; } if ((option.m_options & OPT_VIDEO) && !stream_exists(m_out.m_video.m_stream_idx)) { // Option for video, but file contains no video stream continue; } Logging::trace(virtname(), "Profile format option -%1%2%3.", option.m_key, *option.m_value ? " " : "", option.m_value); ret = dict_set_with_check(dict, option.m_key, option.m_value, option.m_flags, virtname()); if (ret < 0) { break; } } return ret; } int FFmpeg_Transcoder::prepare_format(AVDictionary** dict, FILETYPE filetype) const { int ret = 0; for (const PROFILE_LIST & profile : m_profile) { if (profile.m_filetype == filetype) { ret = AVERROR_OPTION_NOT_FOUND; // Once we found the file type, we also need to find the profile. Otherwise we have an invalid command line option. if (profile.m_profile == params.m_profile) { ret = update_format(dict, profile.m_option_format); break; } } } if (filetype == FILETYPE::MP4 || filetype == FILETYPE::PRORES || filetype == FILETYPE::TS || filetype == FILETYPE::HLS) { // All dict_set_with_check(dict, "flags:a", "+global_header", 0, virtname()); dict_set_with_check(dict, "flags:v", "+global_header", 0, virtname()); } return ret; } int FFmpeg_Transcoder::create_fake_wav_header() const { // Insert fake WAV header (fill in size fields with estimated values instead of setting to -1) AVIOContext * output_io_context = static_cast(m_out.m_format_ctx->pb); Buffer *buffer = static_cast(output_io_context->opaque); size_t current_offset = buffer->tell(); size_t read_offset = 0; WAV_HEADER wav_header; WAV_LIST_HEADER list_header; WAV_DATA_HEADER data_header; buffer->copy(reinterpret_cast(&wav_header), 0, sizeof(WAV_HEADER)); read_offset = sizeof(WAV_HEADER); if (wav_header.m_audio_format == 0xfffe) { //WAV_HEADER_EX wav_header_ex; WAV_FACT wav_fact; //buffer->copy(reinterpret_cast(&wav_header_ex), read_offset, sizeof(WAV_HEADER_EX)); read_offset += sizeof(WAV_HEADER_EX); // Need to skip extension header // Check for presence of "fact" chunk buffer->copy(reinterpret_cast(&wav_fact), read_offset, sizeof(WAV_FACT)); if (!memcmp(&wav_fact.m_chunk_id, "fact", sizeof(wav_fact.m_chunk_id))) { read_offset += sizeof(WAV_FACT); // Also skip fact header } } // Read list header buffer->copy(reinterpret_cast(&list_header), read_offset, sizeof(WAV_LIST_HEADER)); read_offset += sizeof(WAV_LIST_HEADER) + list_header.m_data_bytes - 4; // Read data header buffer->copy(reinterpret_cast(&data_header), read_offset, sizeof(WAV_DATA_HEADER)); // Fill in size fields with predicted size wav_header.m_wav_size = static_cast(predicted_filesize() - 8); data_header.m_data_bytes = static_cast(predicted_filesize() - (read_offset + sizeof(WAV_DATA_HEADER))); #if __BYTE_ORDER == __BIG_ENDIAN wav_header.m_wav_size = __builtin_bswap32(wav_header.m_wav_size); data_header.m_data_bytes = __builtin_bswap32(data_header.m_data_bytes); #endif // Write updated wav header buffer->seek(0, SEEK_SET); buffer->writeio(reinterpret_cast(&wav_header), sizeof(WAV_HEADER)); // Write updated data header buffer->seek(static_cast(read_offset), SEEK_SET); buffer->writeio(reinterpret_cast(&data_header), sizeof(WAV_DATA_HEADER)); // Restore write position buffer->seek(static_cast(current_offset), SEEK_SET); return 0; } int FFmpeg_Transcoder::read_aiff_chunk(Buffer *buffer, size_t *buffoffset, const char *ID, uint8_t *chunk, size_t *size) const { AIFF_CHUNK *p = reinterpret_cast(chunk); size_t buffsize = *size; for (;;) { if (!buffer->copy(chunk, *buffoffset, buffsize)) { return -1; } if (buffer->eof()) { errno = 0; return -1; } if (!memcmp(&p->m_ckID, AIFF_FORMID, sizeof(p->m_ckID))) { // Special case: FormChunk has a fixed size *size = sizeof(AIFF_FORMCHUNK); } else { #if __BYTE_ORDER == __BIG_ENDIAN *size = p->m_ckSize + 8; #else *size = __builtin_bswap32(p->m_ckSize) + 8; #endif } if (!memcmp(&p->m_ckID, ID, sizeof(p->m_ckID))) { // Found break; } // Advance to next potential position *buffoffset += *size; } return 0; } int FFmpeg_Transcoder::create_fake_aiff_header() const { // Insert fake WAV header (fill in size fields with estimated values instead of setting to -1) AVIOContext * output_io_context = static_cast(m_out.m_format_ctx->pb); Buffer *buffer = static_cast(output_io_context->opaque); size_t current_offset = buffer->tell(); size_t read_offset = 0; size_t size; AIFF_FORMCHUNK form_chunk; AIFF_COMMONCHUNK common_chunk; size = sizeof(form_chunk); if (read_aiff_chunk(buffer, &read_offset, AIFF_FORMID, reinterpret_cast(&form_chunk), &size)) { return -1; } read_offset += size; form_chunk.m_ckSize = static_cast(predicted_filesize() - 8); #if __BYTE_ORDER != __BIG_ENDIAN form_chunk.m_ckSize = __builtin_bswap32(form_chunk.m_ckSize); #endif size = sizeof(common_chunk); if (read_aiff_chunk(buffer, &read_offset, AIFF_COMMONID, reinterpret_cast(&common_chunk), &size)) { return -1; } read_offset += size; AIFF_SOUNDDATACHUNK sounddata_chunk; size = sizeof(sounddata_chunk); if (read_aiff_chunk(buffer, &read_offset, AIFF_SOUNDATAID, reinterpret_cast(&sounddata_chunk), &size)) { return -1; } sounddata_chunk.m_ckSize = static_cast(predicted_filesize() - read_offset); #if __BYTE_ORDER != __BIG_ENDIAN sounddata_chunk.m_ckSize = __builtin_bswap32(sounddata_chunk.m_ckSize); #endif // Write updated AIFF header buffer->seek(0, SEEK_SET); buffer->writeio(reinterpret_cast(&form_chunk), sizeof(form_chunk)); // Write updated data header buffer->seek(static_cast(read_offset), SEEK_SET); buffer->writeio(reinterpret_cast(&sounddata_chunk), sizeof(sounddata_chunk)); // Restore write position buffer->seek(static_cast(current_offset), SEEK_SET); return 0; } int FFmpeg_Transcoder::write_output_file_header() { FFmpeg_Dictionary dict; int ret; ret = prepare_format(dict.address(), m_out.m_filetype); if (ret < 0) { return ret; } ret = avformat_write_header(m_out.m_format_ctx, dict.address()); if (ret < 0) { Logging::error(virtname(), "Could not write output file header (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } switch (m_current_format->filetype()) { case FILETYPE::WAV: { ret = create_fake_wav_header(); break; } case FILETYPE::AIFF: { ret = create_fake_aiff_header(); break; } default: { break; } } return ret; } int FFmpeg_Transcoder::alloc_picture(AVFrame *frame, AVPixelFormat pix_fmt, int width, int height) const { int ret; frame->format = pix_fmt; frame->width = width; frame->height = height; // allocate the buffers for the frame data ret = av_frame_get_buffer(frame, 32); if (ret < 0) { Logging::error(virtname(), "Could not allocate frame data (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } return 0; } int FFmpeg_Transcoder::decode(AVCodecContext *codec_ctx, AVFrame *frame, int *got_frame, const AVPacket *pkt) const { int ret; *got_frame = 0; if (pkt != nullptr) { ret = avcodec_send_packet(codec_ctx, pkt); if (ret < 0 && ret != AVERROR_EOF) { // In particular, we don't expect AVERROR(EAGAIN), because we read all // decoded frames with avcodec_receive_frame() until done, so this should be handled // as an error. if (ret == AVERROR(EAGAIN)) { ret = AVERROR_EXTERNAL; } if (is_audio_stream(pkt->stream_index) && stream_exists(m_out.m_audio.m_stream_idx)) { Logging::error(filename(), "Could not send audio packet at PTS=%1 to decoder (error '%2').", ffmpeg_rescale_q(pkt->pts, m_in.m_audio.m_stream->time_base), ffmpeg_geterror(ret).c_str()); } else if (is_video_stream(pkt->stream_index) && stream_exists(m_out.m_video.m_stream_idx)) { Logging::error(filename(), "Could not send video packet at PTS=%1 to decoder (error '%2').", ffmpeg_rescale_q(pkt->pts, m_in.m_video.m_stream->time_base), ffmpeg_geterror(ret).c_str()); } else { // Should never come here, but what the heck... Logging::error(filename(), "Could not send packet at PTS=%1 to decoder (error '%2').", pkt->pts, ffmpeg_geterror(ret).c_str()); } return ret; } } ret = avcodec_receive_frame(codec_ctx, frame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { Logging::error(filename(), "Could not receive packet from decoder (error '%1').", ffmpeg_geterror(ret).c_str()); } /** * @note Only after the first hardware decoded video packet arrived we have a * @note hardware frame context. * @note We should create the output stream now, open a codec etc. and call * @note hwframe_ctx_set. */ *got_frame = (ret >= 0) ? 1 : 0; return ret; } int FFmpeg_Transcoder::decode_audio_frame(AVPacket *pkt, int *decoded) { int data_present = 0; int ret = 0; *decoded = 0; // Decode the audio frame stored in the temporary packet. // The input audio stream decoder is used to do this. // If we are at the end of the file, pass an empty packet to the decoder // to flush it. // Since FFMpeg version >= 3.2 this is deprecated bool again = false; data_present = 0; // read all the output frames (in general there may be any number of them) while (ret >= 0) { FFmpeg_Frame frame(m_out.m_audio.m_stream_idx); ret = frame.res(); if (ret < 0) { Logging::error(filename(), "Could not decode audio frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } ret = decode(m_in.m_audio.m_codec_ctx.get(), frame, &data_present, again ? nullptr : pkt); if (!data_present) { break; } if (ret < 0) { // Anything else is an error, report it! Logging::error(filename(), "Could not decode audio frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } again = true; *decoded += pkt->size; // If there is decoded data, convert and store it if (frame->nb_samples) { // Temporary storage for the converted input samples. uint8_t **converted_input_samples = nullptr; try { int nb_output_samples; // Initialise the resampler to be able to convert audio sample formats. ret = init_resampler(); if (ret) { throw ret; } nb_output_samples = (m_audio_resample_ctx != nullptr) ? swr_get_out_samples(m_audio_resample_ctx, frame->nb_samples) : frame->nb_samples; // Store audio frame // Initialise the temporary storage for the converted input samples. ret = init_converted_samples(&converted_input_samples, nb_output_samples); if (ret < 0) { throw ret; } // Convert the input samples to the desired output sample format. // This requires a temporary storage provided by converted_input_samples. ret = convert_samples(frame->extended_data, frame->nb_samples, converted_input_samples, &nb_output_samples); if (ret < 0) { throw ret; } // Add the converted input samples to the audio FIFO buffer for later processing. ret = add_samples_to_fifo(converted_input_samples, nb_output_samples); if (ret < 0) { throw ret; } ret = 0; } catch (int _ret) { ret = _ret; } if (converted_input_samples != nullptr) { av_freep(&converted_input_samples[0]); av_free(converted_input_samples); } } } return ret; } int FFmpeg_Transcoder::decode_video_frame(AVPacket *pkt, int *decoded) { int data_present; int ret = 0; *decoded = 0; // NOTE1: some codecs are stream based (mpegvideo, mpegaudio) // and this is the only method to use them because you cannot // know the compressed data size before analysing it. // BUT some other codecs (msmpeg4, mpeg4) are inherently frame // based, so you must call them with all the data for one // frame exactly. You must also initialise 'width' and // 'height' before initialising them. // NOTE2: some codecs allow the raw parameters (frame size, // sample rate) to be changed at any frame. We handle this, so // you should also take care of it // Since FFMpeg version >= 3.2 this is deprecated bool again = false; data_present = 0; // read all the output frames (in general there may be any number of them) while (ret >= 0) { FFmpeg_Frame frame(m_out.m_video.m_stream_idx); ret = frame.res(); if (ret < 0) { Logging::error(filename(), "Could not decode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } ret = decode(m_in.m_video.m_codec_ctx.get(), frame, &data_present, again ? nullptr : pkt); if (!data_present) { // No data available break; } if (ret < 0) { // Anything else is an error, report it! Logging::error(filename(), "Could not decode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } if (m_hwaccel_enable_dec_buffering && frame != nullptr) { FFmpeg_Frame sw_frame(m_out.m_video.m_stream_idx); ret = sw_frame.res(); if (ret < 0) { Logging::error(filename(), "Could not decode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } // If decoding is done in hardware, the resulting frame data needs to be copied to software memory //ret = hwframe_copy_from_hw(m_in.m_video.m_codec_ctx.get(), &sw_frame, frame); // retrieve data from GPU to CPU ret = av_hwframe_transfer_data(sw_frame, frame, 0); // hwframe_copy_from_hw if (ret < 0) { Logging::error(filename(), "Error transferring the data to system memory (error '%1').", ffmpeg_geterror(ret).c_str()); break; } frame = sw_frame; } again = true; *decoded += pkt->size; // Sometimes only a few packets contain valid dts/pts/pos data, so we keep it if (pkt->dts != AV_NOPTS_VALUE) { int64_t pkt_dts = pkt->dts; if (pkt_dts > m_pts) { m_pts = pkt_dts; } } else if (pkt->pts != AV_NOPTS_VALUE) { int64_t pkt_pts = pkt->pts; if (pkt_pts > m_pts) { m_pts = pkt_pts; } } if (pkt->pos > -1) { m_pos = pkt->pos; } if (frame != nullptr) { if (!(frame->flags & AV_FRAME_FLAG_CORRUPT || frame->flags & AV_FRAME_FLAG_DISCARD)) { ret = send_filters(&frame, ret); if (ret) { break; } if (m_sws_ctx != nullptr) { FFmpeg_Frame tmp_frame(m_out.m_video.m_stream_idx); ret = tmp_frame.res(); if (ret < 0) { Logging::error(filename(), "Could not decode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } AVCodecContext *output_codec_ctx = m_out.m_video.m_codec_ctx.get(); ret = alloc_picture(tmp_frame, m_out.m_pix_fmt, output_codec_ctx->width, output_codec_ctx->height); if (ret < 0) { break; } sws_scale(m_sws_ctx, static_cast(frame->data), frame->linesize, 0, frame->height, tmp_frame->data, tmp_frame->linesize); tmp_frame->pts = frame->pts; tmp_frame->best_effort_timestamp = frame->best_effort_timestamp; frame = tmp_frame; } int64_t best_effort_timestamp = frame->best_effort_timestamp; if (best_effort_timestamp != AV_NOPTS_VALUE) { frame->pts = best_effort_timestamp; } if (frame->pts == AV_NOPTS_VALUE) { frame->pts = m_pts; } int64_t video_start_time = m_out.m_video.m_start_time; if (m_out.m_video.m_stream != nullptr && frame->pts != AV_NOPTS_VALUE) { if (m_in.m_video.m_stream->time_base.den != m_out.m_video.m_stream->time_base.den || m_in.m_video.m_stream->time_base.num != m_out.m_video.m_stream->time_base.num) { frame->pts = ffmpeg_rescale_q_rnd(frame->pts, m_in.m_video.m_stream->time_base, m_out.m_video.m_stream->time_base); video_start_time = ffmpeg_rescale_q_rnd(video_start_time, m_in.m_video.m_stream->time_base, m_out.m_video.m_stream->time_base); } // Fix for issue #46: bitrate too high. // Solution found here https://stackoverflow.com/questions/11466184/setting-video-bit-rate-through-ffmpeg-api-is-ignored-for-libx264-codec // This is permanently used in the current ffmpeg.c code (see commit: e3fb9af6f1353f30855eaa1cbd5befaf06e303b8 Date:Wed Jan 22 15:52:10 2020 +0100) frame->pts = ffmpeg_rescale_q_rnd(frame->pts, m_out.m_video.m_stream->time_base, m_out.m_video.m_codec_ctx->time_base); video_start_time = ffmpeg_rescale_q_rnd(video_start_time, m_out.m_video.m_stream->time_base, m_out.m_video.m_codec_ctx->time_base); } frame->quality = m_out.m_video.m_codec_ctx->global_quality; #if !LAVU_ADD_NEW_FRAME_FLAGS frame->key_frame = 0; // Leave that decision to encoder #endif frame->pict_type = AV_PICTURE_TYPE_NONE; // other than AV_PICTURE_TYPE_NONE causes warnings if (frame->pts != AV_NOPTS_VALUE) { int64_t tmp_pts = frame->pts - video_start_time; int64_t pos = ffmpeg_rescale_q_rnd(tmp_pts, m_out.m_video.m_codec_ctx->time_base); if (pos < 0) { pos = 0; } if (is_hls()) { // Issue #90: Insert key frame at start of each subsequent HLS segment uint32_t next_segment = get_next_segment(pos); if (goto_next_segment(next_segment) && !m_insert_keyframe) { Logging::debug(virtname(), "Force key frame for next segment no. %1 at PTS=%2 (%3).", next_segment, tmp_pts, format_duration(pos).c_str()); #if LAVU_ADD_NEW_FRAME_FLAGS frame->flags |= AV_FRAME_FLAG_KEY; // This is required to reset the GOP counter (insert the next key frame after gop_size frames) #else // !LAVU_ADD_NEW_FRAME_FLAGS frame->key_frame = 1; // This is required to reset the GOP counter (insert the next key frame after gop_size frames) #endif // !LAVU_ADD_NEW_FRAME_FLAGS frame->pict_type = AV_PICTURE_TYPE_I; m_insert_keyframe = true; } } m_frame_map.emplace(pos, frame); } else { m_frame_map.emplace(0, frame); } } } } return ret; } int FFmpeg_Transcoder::decode_subtitle(AVPacket *pkt, int *decoded) { StreamRef_map::const_iterator it = m_in.m_subtitle.find(pkt->stream_index); *decoded = 0; if (it == m_in.m_subtitle.cend()) { // Should never happen, this should never be called with anything else than subtitle packets. int ret = AVERROR_STREAM_NOT_FOUND; Logging::error(filename(), "INTERNAL ERROR: FFmpeg_Transcoder::decode_subtitle()! Subtitle stream #%1 not found (error '%2').", ffmpeg_geterror(ret).c_str()); return ret; } // Decode the audio frame stored in the temporary packet. // The input audio stream decoder is used to do this. // If we are at the end of the file, pass an empty packet to the decoder // to flush it. // Temporary storage of the input samples of the frame read from the file. int out_stream_idx = map_in_to_out_stream(it->first); if (out_stream_idx == INVALID_STREAM) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::decode_subtitle()! Unable to map input subtitle stream #%1 to output stream.", it->first); throw AVERROR(EINVAL); } return decode_subtitle(it->second.m_codec_ctx.get(), pkt, decoded, out_stream_idx); } int FFmpeg_Transcoder::decode_subtitle(AVCodecContext *codec_ctx, AVPacket *pkt, int *decoded, int out_stream_idx) { FFmpeg_Subtitle subtitle(out_stream_idx); int data_present = 0; int ret = 0; *decoded = 0; ret = subtitle.res(); if (ret < 0) { Logging::error(filename(), "Could not decode subtitle (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } ret = avcodec_decode_subtitle2(codec_ctx, subtitle, &data_present, pkt); if (ret < 0 && ret != AVERROR(EINVAL)) { Logging::error(filename(), "Could not decode subtitle frame (error '%1').", ffmpeg_geterror(ret).c_str()); data_present = 0; } else { *decoded = ret; ret = 0; } if (data_present) { // If there is decoded data, store it // sub->pts is already in AV_TIME_BASE m_frame_map.emplace(subtitle->pts, subtitle); } return ret; } int FFmpeg_Transcoder::store_packet(AVPacket *pkt, AVMediaType mediatype) { int ret; if (is_hls() && pkt->pts != AV_NOPTS_VALUE) { switch (mediatype) { case AVMEDIA_TYPE_AUDIO: { int64_t pos = ffmpeg_rescale_q_rnd(pkt->pts - m_out.m_audio.m_start_time, m_out.m_audio.m_stream->time_base); if (pos < 0) { pos = 0; } uint32_t next_segment = get_next_segment(pos); if (goto_next_segment(next_segment)) { if (!(m_inhibit_stream_msk & FFMPEGFS_AUDIO)) { m_inhibit_stream_msk |= FFMPEGFS_AUDIO; Logging::trace(virtname(), "Buffering audio packets until the next segment no. %1 from position %2 (%3).", next_segment, pos, format_duration(pos).c_str()); } FFmpeg_Packet fifo_pkt(pkt); ret = fifo_pkt.res(); if (ret < 0) { return ret; } m_hls_packet_fifo.push(std::move(fifo_pkt)); return 0; } break; } case AVMEDIA_TYPE_VIDEO: { int64_t pos = ffmpeg_rescale_q_rnd(pkt->pts - m_out.m_video.m_start_time, m_out.m_video.m_stream->time_base); if (pos < 0) { pos = 0; } uint32_t next_segment = get_next_segment(pos); if (goto_next_segment(next_segment)) { if (!(m_inhibit_stream_msk & FFMPEGFS_VIDEO)) { m_inhibit_stream_msk |= FFMPEGFS_VIDEO; Logging::trace(virtname(), "Buffering video packets until the next segment no. %1 from position %2 (%3).", next_segment, pos, format_duration(pos).c_str()); } FFmpeg_Packet fifo_pkt(pkt); ret = fifo_pkt.res(); if (ret < 0) { return ret; } m_hls_packet_fifo.push(std::move(fifo_pkt)); return 0; } break; } case AVMEDIA_TYPE_SUBTITLE: { StreamRef * subtitle = get_out_subtitle_stream(pkt->stream_index); if (subtitle != nullptr && subtitle->m_stream != nullptr) { int64_t pos = ffmpeg_rescale_q_rnd(pkt->pts - subtitle->m_start_time, subtitle->m_stream->time_base); if (pos < 0) { pos = 0; } uint32_t next_segment = get_next_segment(pos); if (goto_next_segment(next_segment)) { FFmpeg_Packet fifo_pkt(pkt); ret = fifo_pkt.res(); if (ret < 0) { return ret; } m_hls_packet_fifo.push(std::move(fifo_pkt)); return 0; } } break; } default: { break; } } } ret = av_write_frame(m_out.m_format_ctx, pkt); if (ret < 0) { const char *type; if (mediatype != AVMEDIA_TYPE_ATTACHMENT) { type = av_get_media_type_string(mediatype); if (type == nullptr) { type = "unknown"; } } else { type = "album art"; } Logging::error(virtname(), "Could not write %1 frame (error '%2').", type, ffmpeg_geterror(ret).c_str()); } return ret; } void FFmpeg_Transcoder::make_pts(AVPacket *pkt, int64_t *cur_ts) const { if (pkt->pts != AV_NOPTS_VALUE) { *cur_ts = pkt->pts + pkt->duration; } else if (pkt->dts != AV_NOPTS_VALUE) { *cur_ts = pkt->dts; } if (pkt->duration) { if (pkt->pts == AV_NOPTS_VALUE) { pkt->pts = *cur_ts; } *cur_ts += pkt->duration; if (pkt->dts == AV_NOPTS_VALUE) { pkt->dts = *cur_ts; } } } int FFmpeg_Transcoder::decode_frame(AVPacket *pkt) { int ret = 0; if (m_in.m_audio.m_stream != nullptr && is_audio_stream(pkt->stream_index) && stream_exists(m_out.m_audio.m_stream_idx)) { if (m_reset_pts & FFMPEGFS_AUDIO && pkt->pts != AV_NOPTS_VALUE) { m_reset_pts &= ~FFMPEGFS_AUDIO; // Clear reset bit int64_t pkt_pts = ffmpeg_rescale_q(pkt->pts, m_in.m_audio.m_stream->time_base); m_out.m_audio_pts = ffmpeg_rescale_q(pkt_pts, av_get_time_base_q(), m_out.m_audio.m_stream->time_base); Logging::debug(virtname(), "Reset the PTS from the audio packet to %1.", format_duration(pkt_pts).c_str()); } if (!m_copy_audio) { int decoded = 0; ret = decode_audio_frame(pkt, &decoded); } else { // Simply copy packet without recoding pkt->stream_index = m_out.m_audio.m_stream_idx; // Rescale packet av_packet_rescale_ts(pkt, m_in.m_audio.m_stream->time_base, m_out.m_audio.m_stream->time_base); // Make PTS/DTS if missing make_pts(pkt, &m_cur_audio_ts); pkt->pos = -1; ret = store_packet(pkt, AVMEDIA_TYPE_AUDIO); } } else if (m_in.m_video.m_stream != nullptr && is_video_stream(pkt->stream_index) && (stream_exists(m_out.m_video.m_stream_idx) || is_frameset())) { if (m_reset_pts & FFMPEGFS_VIDEO && pkt->pts != AV_NOPTS_VALUE) { m_reset_pts &= ~FFMPEGFS_VIDEO; // Clear reset bit int64_t pkt_pts = ffmpeg_rescale_q(pkt->pts, m_in.m_video.m_stream->time_base); m_out.m_video_pts = ffmpeg_rescale_q(pkt_pts, av_get_time_base_q(), m_out.m_video.m_stream->time_base); Logging::debug(virtname(), "Reset PTS from the video packet to %1.", format_duration(pkt_pts).c_str()); } if (!m_copy_video) { int decoded = 0; /** * @todo Calling decode_video_frame until all data has been used, but for * DVDs only. Can someone tell me why this seems required??? If this is not * done some videos become garbled. But only for DVDs... @n * @n * With fix: all DVDs OK, some Blurays (e.g. Phil Collins) not... @n * With fix: all DVDs shitty, but Blurays OK. @n * @n * Applying fix for DVDs only. */ #ifndef USE_LIBDVD ret = decode_video_frame(pkt, &decoded); #else //USE_LIBDVD if (m_virtualfile->m_type != VIRTUALTYPE::DVD) { ret = decode_video_frame(pkt, &decoded); } else { int lastret = 0; do { // Decode one frame. ret = decode_video_frame(pkt, &decoded); if ((ret == AVERROR(EAGAIN) && ret == lastret) || ret == AVERROR_EOF) { // If EAGAIN reported twice or stream at EOF // quit loop, but this is not an error // (must process all streams). break; } if (ret < 0 && ret != AVERROR(EAGAIN)) { Logging::error(filename(), "Could not decode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } lastret = ret; pkt->data += decoded; pkt->size -= decoded; } while (pkt->size > 0 && (ret == 0 || ret == AVERROR(EAGAIN))); ret = 0; } #endif // USE_LIBDVD } else { // Simply copy packet without recoding pkt->stream_index = m_out.m_video.m_stream_idx; // Rescale packet av_packet_rescale_ts(pkt, m_in.m_video.m_stream->time_base, m_out.m_video.m_stream->time_base); // Make PTS/DTS if missing make_pts(pkt, &m_cur_video_ts); pkt->pos = -1; ret = store_packet(pkt, AVMEDIA_TYPE_VIDEO); } } else if (is_subtitle_stream(pkt->stream_index)) { // Decode subtitle. No copy option available. int decoded = 0; ret = decode_subtitle(pkt, &decoded); } else { // Finally process album arts switch (m_in.m_format_ctx->streams[pkt->stream_index]->codecpar->codec_type) { case AVMEDIA_TYPE_VIDEO: { for (size_t n = 0; n < m_in.m_album_art.size() && n < m_out.m_album_art.size(); n++) { AVStream *input_stream = m_in.m_album_art.at(n).m_stream; // AV_DISPOSITION_ATTACHED_PIC streams already processed in process_albumarts() if (pkt->stream_index == input_stream->index && !(input_stream->disposition & AV_DISPOSITION_ATTACHED_PIC)) { AVStream *output_stream = m_out.m_album_art.at(n).m_stream; ret = add_albumart_frame(output_stream, pkt); break; } } break; } default: { break; } } } // If decoding errors should be ignored by command line, reset return code. // Exemptions that need to be passed on anyway: // * EAGAIN tells the caller to retry, this is not an error // * AVERROR_EXTERNAL is an unrecoverable error and should be handled if (!params.m_decoding_errors && ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EXTERNAL) { // Ignore error, go on with transcoding ret = 0; } return ret; } int FFmpeg_Transcoder::init_converted_samples(uint8_t ***converted_input_samples, int frame_size) { int ret; // Allocate as many pointers as there are audio channels. // Each pointer will later point to the audio samples of the corresponding // channels (although it may be nullptr for interleaved formats). *converted_input_samples = static_cast(av_calloc(static_cast(get_channels(m_out.m_audio.m_codec_ctx.get())), sizeof(**converted_input_samples))); if (*converted_input_samples == nullptr) { Logging::error(virtname(), "Could not allocate converted input sample pointers."); return AVERROR(ENOMEM); } // Allocate memory for the samples of all channels in one consecutive // block for convenience. ret = av_samples_alloc(*converted_input_samples, nullptr, get_channels(m_out.m_audio.m_codec_ctx.get()), frame_size, m_out.m_audio.m_codec_ctx->sample_fmt, 0); if (ret < 0) { Logging::error(virtname(), "Could not allocate converted input samples (error '%1').", ffmpeg_geterror(ret).c_str()); av_freep(&(*converted_input_samples)[0]); av_free(*converted_input_samples); return ret; } return 0; } int FFmpeg_Transcoder::convert_samples(uint8_t **input_data, int in_samples, uint8_t **converted_data, int *out_samples) { if (m_audio_resample_ctx != nullptr) { int ret; // Convert the samples using the resampler. ret = swr_convert(m_audio_resample_ctx, converted_data, *out_samples, const_cast(input_data), in_samples); if (ret < 0) { Logging::error(virtname(), "Could not convert input samples (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } *out_samples = ret; } else { *out_samples = in_samples; // No resampling, just copy samples if (!av_sample_fmt_is_planar(m_out.m_audio.m_codec_ctx->sample_fmt)) { // Interleaved format int samples = in_samples * av_get_bytes_per_sample(m_out.m_audio.m_codec_ctx->sample_fmt) * get_channels(m_in.m_audio.m_codec_ctx.get()); std::memcpy(converted_data[0], input_data[0], static_cast(samples)); } else { // Planar format int samples = in_samples * av_get_bytes_per_sample(m_out.m_audio.m_codec_ctx->sample_fmt); for (int n = 0; n < get_channels(m_out.m_audio.m_codec_ctx.get()); n++) { std::memcpy(converted_data[n], input_data[n], static_cast(samples)); } } } return 0; } int FFmpeg_Transcoder::add_samples_to_fifo(uint8_t **converted_input_samples, int frame_size) { int ret; // Make the audio FIFO as large as it needs to be to hold both, // the old and the new samples. ret = m_audio_fifo.realloc(m_audio_fifo.size() + frame_size); if (ret < 0) { Logging::error(virtname(), "Could not reallocate the audio FIFO."); return ret; } // Store the new samples in the audio FIFO buffer. ret = m_audio_fifo.write(reinterpret_cast(converted_input_samples), frame_size); if (ret < frame_size) { if (ret < 0) { Logging::error(virtname(), "Could not write data to audio FIFO (error '%1').", ffmpeg_geterror(ret).c_str()); } else { Logging::error(virtname(), "Could not write data to audio FIFO."); } return AVERROR_EXIT; } return 0; } int FFmpeg_Transcoder::flush_frames_all(bool use_flush_packet) { int ret = 0; if (m_in.m_audio.m_codec_ctx != nullptr) { int ret2 = flush_frames_single(m_in.m_audio.m_stream_idx, use_flush_packet); if (ret2 < 0) { ret = ret2; } } if (m_in.m_video.m_codec_ctx != nullptr) { int ret2 = flush_frames_single(m_in.m_video.m_stream_idx, use_flush_packet); if (ret2 < 0) { ret = ret2; } } return ret; } int FFmpeg_Transcoder::flush_frames_single(int stream_idx, bool use_flush_packet) { int ret = 0; if (stream_exists(stream_idx)) { int (FFmpeg_Transcoder::*decode_frame_ptr)(AVPacket *pkt, int *decoded) = nullptr; if (!m_copy_audio && is_audio_stream(stream_idx) && stream_exists(m_out.m_audio.m_stream_idx)) { decode_frame_ptr = &FFmpeg_Transcoder::decode_audio_frame; } else if (!m_copy_video && is_video_stream(stream_idx) && (stream_exists(m_out.m_video.m_stream_idx) || is_frameset())) { decode_frame_ptr = &FFmpeg_Transcoder::decode_video_frame; } if (decode_frame_ptr != nullptr) { FFmpeg_Packet flush_packet(stream_idx); AVPacket *flush_packet_ptr = nullptr; if (flush_packet.res() < 0) { return flush_packet.res(); } if (use_flush_packet) { flush_packet->data = nullptr; flush_packet->size = 0; flush_packet->stream_index = stream_idx; flush_packet_ptr = flush_packet; } // cppcheck-suppress knownConditionTrueFalse for (int decoded = 1; decoded;) { ret = (this->*decode_frame_ptr)(flush_packet_ptr, &decoded); if (ret < 0 && ret != AVERROR(EAGAIN)) { break; } } } } return ret; } int FFmpeg_Transcoder::read_decode_convert_and_store(int *finished) { // Packet used for temporary storage. FFmpeg_Packet pkt; int ret = pkt.res(); if (ret < 0) { return ret; } try { // Read one frame from the input file into a temporary packet. ret = av_read_frame(m_in.m_format_ctx, pkt); if (ret < 0) { if (ret == AVERROR_EOF) { // If we are the the end of the file, flush the decoder below. *finished = 1; Logging::trace(virtname(), "Read input file to EOF."); } else { Logging::error(virtname(), "Could not read frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } } if (m_virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { // Check for end of cue sheet track ///<* @todo Cue sheet track: Must check video stream, too and end if both all video and audio packets arrived. Discard packets exceeding duration. if (is_audio_stream(pkt->stream_index)) { int64_t pkt_pts = ffmpeg_rescale_q(pkt->pts, m_in.m_audio.m_stream->time_base); if (pkt_pts > m_virtualfile->m_cuesheet_track.m_start + m_virtualfile->m_cuesheet_track.m_duration) { Logging::trace(virtname(), "Read to end of track."); *finished = 1; ret = AVERROR_EOF; } } } if (!*finished) { // Decode one packet, at least with the old API (!LAV_NEW_PACKET_INTERFACE) // it seems a packet can contain more than one frame so loop around it // if necessary... ret = decode_frame(pkt); if (ret < 0 && ret != AVERROR(EAGAIN)) { throw ret; } } else { // Flush cached frames, ignoring any errors flush_frames_all(true); } ret = 0; // Errors will be reported by exception } catch (int _ret) { ret = _ret; } return ret; } int FFmpeg_Transcoder::init_audio_output_frame(AVFrame *frame, int frame_size) const { int ret; // // Set the frame's parameters, especially its size and format. // av_frame_get_buffer needs this to allocate memory for the // audio samples of the frame. // Default channel layouts based on the number of channels // are assumed for simplicity. frame->nb_samples = frame_size; #if LAVU_DEP_OLD_CHANNEL_LAYOUT ret = av_channel_layout_copy(&frame->ch_layout, &m_out.m_audio.m_codec_ctx->ch_layout); if (ret < 0) { Logging::error(virtname(), "Unable to copy channel layout (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT frame->channel_layout = m_out.m_audio.m_codec_ctx->channel_layout; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT frame->format = m_out.m_audio.m_codec_ctx->sample_fmt; frame->sample_rate = m_out.m_audio.m_codec_ctx->sample_rate; // Allocate the samples of the created frame. This call will make // sure that the audio frame can hold as many samples as specified. // 29.05.2021: Let API decide about alignment. Should be properly set for the current CPU. ret = av_frame_get_buffer(frame, 0); if (ret < 0) { Logging::error(virtname(), "Could allocate output frame samples (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } return 0; } void FFmpeg_Transcoder::produce_audio_dts(AVPacket *pkt) { if (pkt->pts == AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE) { // Normally we have already added the PTS to the frame when it was created. Just in case // this failed, and there are no valid PTS/DTS values, we add it here. int64_t pkt_duration; // Some encoders to not produce dts/pts. // So we make some up. if (pkt->duration) { pkt_duration = pkt->duration; #if !LAVC_DEP_TICKSPERFRAME // This has probably long since been fixed in FFmpeg, so we remove this completly // instead of replacing it with updated code. if (m_out.m_audio.m_codec_ctx->codec_id == AV_CODEC_ID_OPUS || m_current_format->filetype() == FILETYPE::TS || m_current_format->filetype() == FILETYPE::HLS) { /** @todo Is this a FFmpeg bug or am I too stupid? @n * OPUS is a bit strange. Whatever we feed into the encoder, the result will always be floating point planar * at 48 K sampling rate. @n * For some reason the duration calculated by the FFMpeg API is wrong. We have to rescale it to the correct value. * Same applies to mpegts, so let's rescale. */ if (pkt_duration > 0 && m_out.m_audio.m_stream->codecpar->sample_rate > 0) { pkt->duration = pkt_duration = static_cast(av_rescale(pkt_duration, static_cast(m_out.m_audio.m_stream->time_base.den) * m_out.m_audio.m_codec_ctx->ticks_per_frame, m_out.m_audio.m_stream->codecpar->sample_rate * static_cast(m_out.m_audio.m_stream->time_base.num))); } } #endif } else { pkt_duration = 1; } pkt->dts = m_out.m_audio_pts - 1; pkt->pts = m_out.m_audio_pts; m_out.m_audio_pts += pkt_duration; } } int FFmpeg_Transcoder::encode_audio_frame(const AVFrame *frame, int *data_present) { // Packet used for temporary storage. FFmpeg_Packet pkt; int ret = pkt.res(); if (ret < 0) { return ret; } try { // Encode the audio frame and store it in the temporary packet. // The output audio stream encoder is used to do this. *data_present = 0; // send the frame for encoding ret = avcodec_send_frame(m_out.m_audio.m_codec_ctx.get(), frame); if (ret < 0 && ret != AVERROR_EOF) { Logging::error(virtname(), "Could not encode audio frame (error %1').", ffmpeg_geterror(ret).c_str()); throw ret; } // read all the available output packets (in general there may be any number of them) while (ret >= 0) { *data_present = 0; ret = avcodec_receive_packet(m_out.m_audio.m_codec_ctx.get(), pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { throw ret; } else if (ret < 0) { Logging::error(virtname(), "Could not encode audio frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } *data_present = 1; // Write one audio frame from the temporary packet to the output buffer. { pkt->stream_index = m_out.m_audio.m_stream_idx; const bool packet_has_timestamps = pkt->pts != AV_NOPTS_VALUE || pkt->dts != AV_NOPTS_VALUE; produce_audio_dts(pkt); /* * AVFrame::pts passed to the encoder is in codec time base. * Therefore packet timestamps returned by the encoder are in * codec time base as well and must be converted to the output * stream time base before muxing/storing. * * If produce_audio_dts() had to invent timestamps, it used the * existing m_out.m_audio_pts fallback in output stream time base; * do not rescale those invented values a second time. */ if (packet_has_timestamps) { av_packet_rescale_ts(pkt, m_out.m_audio.m_codec_ctx->time_base, m_out.m_audio.m_stream->time_base); } ret = store_packet(pkt, AVMEDIA_TYPE_AUDIO); if (ret < 0) { throw ret; } } } pkt.unref(); } catch (int _ret) { pkt.unref(); ret = _ret; } return ret; } int FFmpeg_Transcoder::encode_image_frame(const AVFrame *frame, int *data_present) { *data_present = 0; if (frame == nullptr || m_skip_next_frame) { // This called internally to flush frames. We do not have a cache to flush, so simply ignore that. // After seek oprations we need to skip the first frame. m_skip_next_frame = false; return 0; } if (m_current_format == nullptr) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::encode_image_frame()! Missing format."); return AVERROR(EINVAL); } if (m_buffer == nullptr) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::encode_image_frame()! Cache not open."); return AVERROR(EINVAL); } FFmpeg_Packet pkt; int ret = pkt.res(); if (ret < 0) { return ret; } try { FFmpeg_Frame cloned_frame(frame); // Clone frame. Does not copy data but references it, only the properties are copied. Not a big memory impact. ret = cloned_frame.res(); if (ret < 0) { Logging::error(virtname(), "Could not encode image frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } uint32_t frame_no = pts_to_frame(m_in.m_video.m_stream, frame->pts); if (m_current_format->video_codec() == AV_CODEC_ID_MJPEG) { // The MJEPG codec requires monotonically growing PTS values so we fake some to avoid them going backwards after seeks cloned_frame->pts = frame_to_pts(m_in.m_video.m_stream, ++m_fake_frame_no); } *data_present = 0; // send the frame for encoding ret = avcodec_send_frame(m_out.m_video.m_codec_ctx.get(), cloned_frame); if (ret < 0 && ret != AVERROR_EOF) { Logging::error(virtname(), "Could not encode image frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } // read all the available output packets (in general there may be any number of them while (ret >= 0) { *data_present = 0; ret = avcodec_receive_packet(m_out.m_video.m_codec_ctx.get(), pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { pkt.unref(); break; } else if (ret < 0) { Logging::error(virtname(), "Could not encode image frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } *data_present = 1; // Write one video frame from the temporary packet to the output buffer. { // Store current video PTS if (pkt->pts != AV_NOPTS_VALUE) { m_out.m_video_pts = pkt->pts; } m_buffer->write_frame(pkt->data, static_cast(pkt->size), frame_no); if (m_last_seek_frame_no == frame_no) // Skip frames until seek pos { m_last_seek_frame_no = 0; } } pkt.unref(); } } catch (int _ret) { pkt.unref(); ret = _ret; } return ret; } int FFmpeg_Transcoder::encode_video_frame(const AVFrame *frame, int *data_present) { if (m_out.m_video.m_stream == nullptr) { return 0; // ignore, avoid crash } // Packet used for temporary storage. if (frame != nullptr) { #if LAVU_ADD_NEW_FRAME_FLAGS if (frame->flags & AV_FRAME_FLAG_INTERLACED) { if (m_out.m_video.m_codec_ctx->codec->id == AV_CODEC_ID_MJPEG) { m_out.m_video.m_stream->codecpar->field_order = (frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST) ? AV_FIELD_TT : AV_FIELD_BB; } else { m_out.m_video.m_stream->codecpar->field_order = (frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST) ? AV_FIELD_TB : AV_FIELD_BT; } } #else // !LAVU_ADD_NEW_FRAME_FLAGS if (frame->interlaced_frame) { if (m_out.m_video.m_codec_ctx->codec->id == AV_CODEC_ID_MJPEG) { m_out.m_video.m_stream->codecpar->field_order = frame->top_field_first ? AV_FIELD_TT : AV_FIELD_BB; } else { m_out.m_video.m_stream->codecpar->field_order = frame->top_field_first ? AV_FIELD_TB : AV_FIELD_BT; } } #endif // !LAVU_ADD_NEW_FRAME_FLAGS else { m_out.m_video.m_stream->codecpar->field_order = AV_FIELD_PROGRESSIVE; } } FFmpeg_Frame *hw_frame = nullptr; FFmpeg_Packet pkt; int ret = pkt.res(); if (ret < 0) { return ret; } try { if (m_hwaccel_enable_enc_buffering && frame != nullptr) { hw_frame = new (std::nothrow) FFmpeg_Frame(m_out.m_video.m_stream_idx); if (hw_frame == nullptr) { ret = AVERROR(ENOMEM); Logging::error(virtname(), "Could not encode video frame at PTS=%1 (error %2').", ffmpeg_rescale_q(frame->pts, m_in.m_video.m_stream->time_base), ffmpeg_geterror(ret).c_str()); throw ret; } ret = hw_frame->res(); if (ret < 0) { Logging::error(virtname(), "Could not encode video frame at PTS=%1 (error %2').", ffmpeg_rescale_q(frame->pts, m_in.m_video.m_stream->time_base), ffmpeg_geterror(ret).c_str()); throw ret; } // If encoding is done in hardware, the resulting frame data needs to be copied to hardware ret = hwframe_copy_to_hw(m_out.m_video.m_codec_ctx.get(), hw_frame, frame); if (ret < 0) { throw ret; } frame = *hw_frame; // Copy, not clone! } // Encode the video frame and store it in the temporary packet. // The output video stream encoder is used to do this. *data_present = 0; // send the frame for encoding ret = avcodec_send_frame(m_out.m_video.m_codec_ctx.get(), frame); if (ret < 0 && ret != AVERROR_EOF) { Logging::error(virtname(), "Could not encode video frame (error %1').", ffmpeg_geterror(ret).c_str()); throw ret; } // read all the available output packets (in general there may be any number of them while (ret >= 0) { *data_present = 0; ret = avcodec_receive_packet(m_out.m_video.m_codec_ctx.get(), pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { pkt.unref(); break; } else if (ret < 0) { Logging::error(virtname(), "Could not encode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } *data_present = 1; // Write one video frame from the temporary packet to the output buffer. { // Fix for issue #46: bitrate too high. av_packet_rescale_ts(pkt, m_out.m_video.m_codec_ctx->time_base, m_out.m_video.m_stream->time_base); if (!(m_out.m_format_ctx->oformat->flags & AVFMT_NOTIMESTAMPS)) { if (pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE && pkt->dts > pkt->pts && m_out.m_last_mux_dts != AV_NOPTS_VALUE) { Logging::warning(virtname(), "Invalid DTS=%1 PTS=%2 in video output, replacing by guess.", pkt->dts, pkt->pts); pkt->pts = pkt->dts = pkt->pts + pkt->dts + m_out.m_last_mux_dts + 1 - FFMIN3(pkt->pts, pkt->dts, m_out.m_last_mux_dts + 1) - FFMAX3(pkt->pts, pkt->dts, m_out.m_last_mux_dts + 1); } if (pkt->dts != AV_NOPTS_VALUE && m_out.m_last_mux_dts != AV_NOPTS_VALUE) { int64_t max = m_out.m_last_mux_dts + !(m_out.m_format_ctx->oformat->flags & AVFMT_TS_NONSTRICT); // AVRational avg_frame_rate = { m_out.m_video.m_stream->avg_frame_rate.den, m_out.m_video.m_stream->avg_frame_rate.num }; // int64_t max = m_out.m_last_mux_dts + ffmpeg_rescale_q(1, avg_frame_rate, m_out.m_video.m_stream->time_base); if (pkt->dts < max) { Logging::trace(virtname(), "Non-monotonous DTS in video output stream; previous: %1, current: %2; changing to %3. This may result in incorrect timestamps in the output.", m_out.m_last_mux_dts, pkt->dts, max); if (pkt->pts >= pkt->dts) { pkt->pts = FFMAX(pkt->pts, max); } pkt->dts = max; } } } if (frame != nullptr && !pkt->duration) { #if !LAVU_DEP_PKT_DURATION pkt->duration = frame->pkt_duration; #else pkt->duration = frame->duration; #endif } if (pkt->pts != AV_NOPTS_VALUE) { m_out.m_video_pts = pkt->pts; m_out.m_last_mux_dts = (pkt->dts != AV_NOPTS_VALUE) ? pkt->dts : (pkt->pts - pkt->duration); } // Write packet to buffer ret = store_packet(pkt, AVMEDIA_TYPE_VIDEO); if (ret < 0) { throw ret; } } pkt.unref(); } } catch (int _ret) { pkt.unref(); ret = _ret; } delete hw_frame; return ret; } int FFmpeg_Transcoder::encode_subtitle(const AVSubtitle *sub, int out_stream_idx, int *data_present) { StreamRef * out_streamref = get_out_subtitle_stream(out_stream_idx); *data_present = 0; if (out_streamref == nullptr) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::encode_subtitle()! Invalid stream index #%1.", out_stream_idx); return AVERROR(EINVAL); } // Packet used for temporary storage. FFmpeg_Packet pkt; int ret = pkt.res(); if (ret < 0) { return ret; } AVSubtitle subtmp; // Make a local copy, we have to modify it std::memcpy(&subtmp, sub, sizeof(AVSubtitle)); try { int nb; int64_t sub_pts; if (subtmp.pts == AV_NOPTS_VALUE) { Logging::error(virtname(), "Subtitle packets must have a PTS (stream index #%1).", out_streamref->m_stream_idx); throw AVERROR(EINVAL); } // Allocate a packet with 1KB buffer, hope that's sufficient ret = av_new_packet(pkt, 1024 * 1024); if (ret < 0) { Logging::error(virtname(), "Failed to allocate new packet (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } // Note: DVB subtitles need one packet to draw them and one other packet to clear them if (out_streamref->m_codec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { nb = 2; } else { nb = 1; } // shift timestamp sub_pts = subtmp.pts; //if (out_streamref->m_stream->start_time != AV_NOPTS_VALUE) //{ // pts -= ffmpeg_rescale_q(out_streamref->m_stream->start_time, out_streamref->m_stream->time_base); //} for (int i = 0; i < nb; i++) { unsigned save_num_rects = subtmp.num_rects; subtmp.pts = sub_pts; // Some decoders may return end_display_time as UINT32_MAX, this causes strange results. if (subtmp.end_display_time == UINT32_MAX) { subtmp.end_display_time = 0; } // start_display_time is required to be 0 subtmp.pts += ffmpeg_rescale_q(subtmp.start_display_time, AVRational({ 1, 1000 })); subtmp.end_display_time -= subtmp.start_display_time; subtmp.start_display_time = 0; if (i == 1) { subtmp.num_rects = 0; } // The avcodec_encode_subtitle seems to be not completely ready ret = avcodec_encode_subtitle(out_streamref->m_codec_ctx.get(), pkt->data, pkt->size, &subtmp); if (i == 1) { subtmp.num_rects = save_num_rects; } if (ret < 0) { Logging::error(virtname(), "Could not encode subtitle frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } pkt->size = ret; pkt->pts = ffmpeg_rescale_q(subtmp.pts, av_get_time_base_q(), out_streamref->m_stream->time_base); pkt->duration = ffmpeg_rescale_q(subtmp.end_display_time, AVRational({ 1, 1000 }), out_streamref->m_stream->time_base); pkt->stream_index = out_streamref->m_stream_idx; if (out_streamref->m_codec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE) { // the pts correction is handled here. Maybe handling it in the codec would be better if (i) { pkt->pts += ffmpeg_rescale_q(subtmp.end_display_time, AVRational({ 1, 1000 }), out_streamref->m_stream->time_base); } } pkt->dts = pkt->pts; ret = store_packet(pkt, AVMEDIA_TYPE_SUBTITLE); if (ret < 0) { throw ret; } *data_present = 1; } pkt.unref(); } catch (int _ret) { pkt.unref(); ret = _ret; } return ret; } int FFmpeg_Transcoder::create_audio_frame(int frame_size) { // Temporary storage of the output samples of the frame written to the file. FFmpeg_Frame output_frame(m_out.m_audio.m_stream_idx); int ret = 0; ret = output_frame.res(); if (ret < 0) { Logging::error(virtname(), "Could not read data from the audio FIFO (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } // Use the maximum number of possible samples per frame. // If there is less than the maximum possible frame size in the audio FIFO // buffer use this number. Otherwise, use the maximum possible frame size frame_size = FFMIN(m_audio_fifo.size(), frame_size); // Initialise temporary storage for one output frame. ret = init_audio_output_frame(output_frame, frame_size); if (ret < 0) { return ret; } // Read as many samples from the FIFO buffer as required to fill the frame. // The samples are stored in the frame temporarily. ret = m_audio_fifo.read(reinterpret_cast(output_frame->data), frame_size); if (ret < frame_size) { if (ret < 0) { Logging::error(virtname(), "Could not read data from the audio FIFO (error '%1').", ffmpeg_geterror(ret).c_str()); } else { Logging::error(virtname(), "Could not read data from the audio FIFO."); ret = AVERROR_EXIT; } return ret; } /* * Build the output frame PTS. * * Keep m_out.m_audio_pts in the output stream time base because that is * what the cache/frame map and muxing side use. However, AVFrame::pts * handed to avcodec_send_frame() must use the encoder codec time base. * * This mirrors the video path: frames are passed to the encoder in codec * time base, and encoded packets are converted back to stream time base * before they are stored. */ const int64_t stream_pts = m_out.m_audio_pts; if (output_frame->sample_rate) { // Not used for encoding, but keep it meaningful for diagnostics. output_frame->best_effort_timestamp = ffmpeg_rescale_q(stream_pts, m_out.m_audio.m_stream->time_base, m_in.m_audio.m_stream->time_base); output_frame->pts = ffmpeg_rescale_q(stream_pts, m_out.m_audio.m_stream->time_base, m_out.m_audio.m_codec_ctx->time_base); // duration = `a * b / c` = AV_TIME_BASE * output_frame->nb_samples / output_frame->sample_rate; int64_t sample_duration = av_rescale(AV_TIME_BASE, output_frame->nb_samples, output_frame->sample_rate); sample_duration = ffmpeg_rescale_q(sample_duration, av_get_time_base_q(), m_out.m_audio.m_stream->time_base); m_out.m_audio_pts += sample_duration; } int64_t pos = ffmpeg_rescale_q_rnd(stream_pts - m_out.m_audio.m_start_time, m_out.m_audio.m_stream->time_base); m_frame_map.emplace(pos, output_frame); return ret; } int FFmpeg_Transcoder::write_output_file_trailer() { int ret; ret = av_write_trailer(m_out.m_format_ctx); if (ret < 0) { Logging::error(virtname(), "Could not write output file trailer (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } return 0; } time_t FFmpeg_Transcoder::mtime() const { return m_mtime; } template const char * FFmpeg_Transcoder::tagcpy(char (&out) [ size ], const std::string & in) const // NOLINT(modernize-avoid-c-arrays) { std::memset(out, ' ', size); std::memcpy(out, in.c_str(), std::min(size, in.size())); return out; } template const T & FFmpeg_Transcoder::tagcpy(T & out, const std::string & in) const { out.fill(' '); std::memcpy(out.data(), in.c_str(), std::min(out.size(), in.size())); // std::memset(out, ' ', size); // std::memcpy(out, in.c_str(), std::min(size, in.size())); return out; } void FFmpeg_Transcoder::copy_metadata(AVDictionary **metadata_out, const AVDictionary *metadata_in, bool contentstream) { AVDictionaryEntry *tag = nullptr; while ((tag = av_dict_get(metadata_in, "", tag, AV_DICT_IGNORE_SUFFIX)) != nullptr) { std::string value(tag->value); if (contentstream && m_virtualfile != nullptr && m_virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { // Replace tags with cue sheet values if (!strcasecmp(tag->key, "ARTIST")) { value = m_virtualfile->m_cuesheet_track.m_artist; } else if (!strcasecmp(tag->key, "TITLE")) { value = m_virtualfile->m_cuesheet_track.m_title; } else if (!strcasecmp(tag->key, "TRACK")) { strsprintf(&value, "%i", m_virtualfile->m_cuesheet_track.m_trackno); } } dict_set_with_check(metadata_out, tag->key, value.c_str(), 0, virtname()); if (contentstream && m_out.m_filetype == FILETYPE::MP3) { // For MP3 fill in ID3v1 structure if (!strcasecmp(tag->key, "ARTIST")) { tagcpy(m_out.m_id3v1.m_artist, value); } else if (!strcasecmp(tag->key, "TITLE")) { tagcpy(m_out.m_id3v1.m_title, value); } else if (!strcasecmp(tag->key, "ALBUM")) { tagcpy(m_out.m_id3v1.m_album, value); } else if (!strcasecmp(tag->key, "COMMENT")) { tagcpy(m_out.m_id3v1.m_comment, value); } else if (!strcasecmp(tag->key, "YEAR") || !strcasecmp(tag->key, "DATE")) { tagcpy(m_out.m_id3v1.m_year, value); } else if (!strcasecmp(tag->key, "TRACK")) { m_out.m_id3v1.m_title_no = static_cast(std::stoi(value)); } } } } int FFmpeg_Transcoder::process_metadata() { Logging::trace(virtname(), "Processing metadata."); if (m_in.m_audio.m_stream != nullptr && m_in.m_audio.m_stream->codecpar->codec_id == AV_CODEC_ID_VORBIS) { // For some formats (namely ogg) FFmpeg returns the tags, odd enough, with streams... copy_metadata(&m_out.m_format_ctx->metadata, m_in.m_audio.m_stream->metadata); } copy_metadata(&m_out.m_format_ctx->metadata, m_in.m_format_ctx->metadata); if (m_out.m_audio.m_stream != nullptr && m_in.m_audio.m_stream != nullptr) { // Copy audio stream meta data copy_metadata(&m_out.m_audio.m_stream->metadata, m_in.m_audio.m_stream->metadata); } if (m_out.m_video.m_stream != nullptr && m_in.m_video.m_stream != nullptr) { // Copy video stream meta data copy_metadata(&m_out.m_video.m_stream->metadata, m_in.m_video.m_stream->metadata); } // Also copy album art meta tags for (size_t n = 0; n < m_in.m_album_art.size() && n < m_out.m_album_art.size(); n++) { AVStream *input_stream = m_in.m_album_art.at(n).m_stream; AVStream *output_stream = m_out.m_album_art.at(n).m_stream; copy_metadata(&output_stream->metadata, input_stream->metadata, is_audio_stream(input_stream->index) || is_video_stream(input_stream->index)); } if (m_virtualfile != nullptr && m_virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { dict_set_with_check(&m_out.m_format_ctx->metadata, "TRACKTOTAL", m_virtualfile->m_cuesheet_track.m_tracktotal, 0, virtname()); dict_set_with_check(&m_out.m_format_ctx->metadata, "TRACK", m_virtualfile->m_cuesheet_track.m_trackno, 0, virtname(), true); dict_set_with_check(&m_out.m_format_ctx->metadata, "ARTIST", m_virtualfile->m_cuesheet_track.m_artist.c_str(), 0, virtname(), true); if (av_dict_get(m_out.m_format_ctx->metadata, "ALBUM_ARTIST", nullptr, 0) == nullptr) { // Issue #78: duplicate ARTIST tag to ALBUM_ARTIST, if target is empty. dict_set_with_check(&m_out.m_format_ctx->metadata, "ALBUM_ARTIST", m_virtualfile->m_cuesheet_track.m_artist.c_str(), 0, virtname(), true); } dict_set_with_check(&m_out.m_format_ctx->metadata, "TITLE", m_virtualfile->m_cuesheet_track.m_title.c_str(), 0, virtname(), true); dict_set_with_check(&m_out.m_format_ctx->metadata, "ALBUM", m_virtualfile->m_cuesheet_track.m_album.c_str(), 0, virtname(), true); dict_set_with_check(&m_out.m_format_ctx->metadata, "GENRE", m_virtualfile->m_cuesheet_track.m_genre.c_str(), 0, virtname(), true); dict_set_with_check(&m_out.m_format_ctx->metadata, "DATE", m_virtualfile->m_cuesheet_track.m_date.c_str(), 0, virtname(), true); } return 0; } int FFmpeg_Transcoder::process_albumarts() { int ret = 0; for (size_t n = 0; n < m_in.m_album_art.size() && n < m_out.m_album_art.size(); n++) { AVStream *input_stream = m_in.m_album_art.at(n).m_stream; if (input_stream->disposition & AV_DISPOSITION_ATTACHED_PIC) { AVStream *output_stream = m_out.m_album_art.at(n).m_stream; ret = add_albumart_frame(output_stream, &input_stream->attached_pic); if (ret < 0) { break; } } } return ret; } void FFmpeg_Transcoder::flush_buffers() { if (m_in.m_audio.m_codec_ctx != nullptr) { avcodec_flush_buffers(m_in.m_audio.m_codec_ctx.get()); } if (m_in.m_video.m_codec_ctx != nullptr) { avcodec_flush_buffers(m_in.m_video.m_codec_ctx.get()); } } int FFmpeg_Transcoder::do_seek_frame(uint32_t frame_no) { m_have_seeked = true; // Note that we have seeked, thus skipped frames. We need to start transcoding over to fill any gaps. //m_skip_next_frame = true; /**< @todo Take deinterlace into account. If deinterlace is on the frame number is decreased by one. */ if (m_skip_next_frame) { --frame_no; } int64_t vstream_pts = frame_to_pts(m_in.m_video.m_stream, frame_no); if (m_in.m_video.m_stream->start_time != AV_NOPTS_VALUE) { vstream_pts += m_in.m_video.m_stream->start_time; } return av_seek_frame(m_in.m_format_ctx, m_in.m_video.m_stream_idx, vstream_pts, AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME); } int FFmpeg_Transcoder::skip_decoded_frames(uint32_t frame_no, bool forced_seek) { int ret = 0; uint32_t next_frame_no = frame_no; // Seek next undecoded frame for (; m_buffer->have_frame(next_frame_no); next_frame_no++) { std::this_thread::yield(); } if (next_frame_no > m_virtualfile->m_video_frame_count) { // Reached end of file // Set PTS to end of file m_out.m_video_pts = m_in.m_video.m_stream->duration; if (m_in.m_video.m_stream->start_time != AV_NOPTS_VALUE) { m_out.m_video_pts += m_in.m_video.m_stream->start_time; } // Seek to end of file to force AVERROR_EOF from next av_read_frame() call. Ignore errrors. av_seek_frame(m_in.m_format_ctx, m_in.m_video.m_stream_idx, m_out.m_video_pts, AVSEEK_FLAG_ANY); return 0; } uint32_t last_frame_no = pts_to_frame(m_in.m_video.m_stream, m_out.m_video_pts); // Ignore seek if target is within the next FRAME_SEEK_THRESHOLD frames if (next_frame_no >= last_frame_no /*+ 1*/ && next_frame_no <= last_frame_no + FRAME_SEEK_THRESHOLD) { return 0; } if (forced_seek || (frame_no != next_frame_no && next_frame_no > 1)) { // If frame changed, skip to it ret = do_seek_frame(next_frame_no); if (ret < 0) { Logging::error(virtname(), "Could not encode audio frame: Seek to frame #%1 failed (error '%2').", next_frame_no, ffmpeg_geterror(ret).c_str()); } } return ret; } int FFmpeg_Transcoder::flush_delayed_audio() { int ret = 0; if (m_out.m_audio.m_codec_ctx != nullptr) { // Flush the encoder as it may have delayed frames. int data_written = 0; do { ret = encode_audio_frame(nullptr, &data_written); if (ret == AVERROR_EOF) { // Not an error break; } if (ret < 0 && ret != AVERROR(EAGAIN)) { Logging::error(virtname(), "Could not encode audio frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } } while (data_written); } return ret; } int FFmpeg_Transcoder::flush_delayed_video() { int ret = 0; if (m_out.m_video.m_codec_ctx != nullptr) { // Flush the encoder as it may have delayed frames. int data_written = 0; do { if (!is_frameset()) { // Encode regular frame ret = encode_video_frame(nullptr, &data_written); } else { // Encode seperate image frame ret = encode_image_frame(nullptr, &data_written); } if (ret == AVERROR_EOF) { // Not an error break; } if (ret < 0 && ret != AVERROR(EAGAIN)) { Logging::error(virtname(), "Could not encode video frame (error '%1').", ffmpeg_geterror(ret).c_str()); break; } } while (data_written); } return ret; } int FFmpeg_Transcoder::flush_delayed_subtitles() /** @todo Implement flush_delayed_subtitles() if required */ { return 0; } int FFmpeg_Transcoder::copy_audio_to_frame_buffer(int *finished) { int output_frame_size; if (m_out.m_audio.m_codec_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) { // Encode supports variable frame size, use an arbitrary value output_frame_size = 10000; } else { // Use the encoder's desired frame size for processing. output_frame_size = m_out.m_audio.m_codec_ctx->frame_size; } // Make sure that there is one frame worth of samples in the audio FIFO // buffer so that the encoder can do its work. // Since the decoder's and the encoder's frame size may differ, we // need to FIFO buffer to store as many frames worth of input samples // that they make up at least one frame worth of output samples. while (m_audio_fifo.size() < output_frame_size) { int ret = 0; // Decode one frame worth of audio samples, convert it to the // output sample format and put it into the audio FIFO buffer. ret = read_decode_convert_and_store(finished); if (ret < 0) { return ret; } // If we are at the end of the input file, we continue // encoding the remaining audio samples to the output file. if (*finished) { break; } } // If we have enough samples for the encoder, we encode them. // At the end of the file, we pass the remaining samples to // the encoder. while (m_audio_fifo.size() >= output_frame_size || (*finished && m_audio_fifo.size() > 0)) { int ret = 0; // Take one frame worth of audio samples from the audio FIFO buffer, // create a frame and store in audio frame buffer. ret = create_audio_frame(output_frame_size); if (ret < 0) { return ret; } } return 0; } int FFmpeg_Transcoder::process_single_fr(DECODER_STATUS *status) { int finished = 0; *status = DECODER_STATUS::DEC_SUCCESS; try { if (m_in.m_video.m_stream != nullptr && is_frameset()) { int ret = 0; // Direct access handling for frame sets: seek to frame if requested. ret = seek_frame(); if (ret == AVERROR_EOF) { *status = DECODER_STATUS::DEC_EOF; // Report EOF, but return no error throw 0; } if (ret < 0) { throw ret; } } if (!m_copy_audio && stream_exists(m_out.m_audio.m_stream_idx)) { int ret = 0; // Copy audio FIFO into frame buffer ret = copy_audio_to_frame_buffer(&finished); if (ret < 0) { throw ret; } } else { int ret = 0; // If we have no audio stream, we'll only get video data // or we simply copy audio and/or video frames into the packet queue ret = read_decode_convert_and_store(&finished); if (ret < 0) { throw ret; } if (finished) { *status = DECODER_STATUS::DEC_EOF; // Report EOF } } do { if (!is_frameset()) { int64_t delay; // The following values are arbitrarily chosen and seem to work. if (is_hls()) { delay = params.m_segment_duration * 75 / 100; // 75% of the segment duration } else { delay = 6 * AV_TIME_BASE; // 6 seconds } if (!finished && (!m_frame_map.empty() && m_frame_map.cbegin()->first + delay > m_frame_map.crbegin()->first)) { return 0; } while (!m_frame_map.empty()) { // Take first entry in map MULTIFRAME_MAP::const_iterator it = m_frame_map.cbegin(); if (is_hls()) { uint32_t next_segment = get_next_segment(it->first); if (goto_next_segment(next_segment)) { // Reached next segment, end current now, and force new. m_inhibit_stream_msk = m_active_stream_msk; break; } } // Get multiframe from map MULTIFRAME multiframe = it->second; // Drop key from map, we've got a clone in multiframe now. m_frame_map.erase(it); if (std::holds_alternative(multiframe)) { // Object is an audio/video frame const FFmpeg_Frame & frame = std::get(multiframe); int stream_idx = frame.m_stream_idx; if (!stream_exists(stream_idx)) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::process_single_fr()! Invalid stream index in audio/video buffer skipped."); continue; } if (stream_idx == m_out.m_audio.m_stream_idx) { // Encode audio int ret = 0; int data_written; // Encode one frame worth of audio samples. ret = encode_audio_frame(frame, &data_written); if (ret < 0 && ret != AVERROR(EAGAIN)) { throw ret; } } else if (stream_idx == m_out.m_video.m_stream_idx) { // Encode video int ret = 0; int data_written = 0; ret = encode_video_frame(frame, &data_written); if (ret < 0 && ret != AVERROR(EAGAIN)) { throw ret; } } } else if (std::holds_alternative(multiframe)) { // Object is a subtitle const FFmpeg_Subtitle & subtitle = std::get(multiframe); int stream_idx = subtitle.m_stream_idx; if (!stream_exists(stream_idx)) { Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::process_single_fr()! Invalid stream index in subtitle buffer skipped."); continue; } // Encode subtitles int ret = 0; int data_written = 0; ret = encode_subtitle(subtitle, stream_idx, &data_written); if (ret < 0 && ret != AVERROR(EAGAIN)) { throw ret; } } } } else { // Frame sets: no audio, no subtitles, no output stream (index) while (!m_frame_map.empty()) { // Although I abhore "auto", it seems that nodes are declared slightly different throughout // several std::map implementations. To avoid #ifdef orgies, I resign: Using "auto" here // is much more coherent. auto nh = m_frame_map.extract(m_frame_map.cbegin()); const MULTIFRAME & multiframe = nh.mapped(); int ret = 0; int data_written = 0; // Encode video ret = encode_image_frame(std::get(multiframe), &data_written); if (ret < 0 && ret != AVERROR(EAGAIN)) { throw ret; } } } if (is_hls() && m_active_stream_msk == m_inhibit_stream_msk) { // Start new HLS segment int ret = 0; ret = start_new_segment(); if (ret < 0) { throw ret; } } } while (finished && !m_frame_map.empty()); // Ensure we've processed all frames in our buffer // If we are at the end of the input file and have encoded // all remaining samples, we can exit this loop and finish. if (finished && m_frame_map.empty()) { flush_delayed_audio(); flush_delayed_video(); flush_delayed_subtitles(); *status = DECODER_STATUS::DEC_EOF; // Report EOF } } catch (int _ret) { *status = (_ret != AVERROR_EOF ? DECODER_STATUS::DEC_ERROR : DECODER_STATUS::DEC_EOF); // If _ret == AVERROR_EOF, simply signal EOF return _ret; } return 0; } int FFmpeg_Transcoder::seek_frame() { if (!m_last_seek_frame_no) { // No current seek frame, check if new seek frame was stacked. { std::lock_guard lock_seek_to_fifo_mutex(m_seek_to_fifo_mutex); while (!m_seek_to_fifo.empty()) { uint32_t frame_no = m_seek_to_fifo.front(); m_seek_to_fifo.pop(); if (!m_buffer->have_frame(frame_no)) { // Frame not yet decoded, so skip to it. m_last_seek_frame_no = frame_no; break; } } } if (m_last_seek_frame_no) { int ret = 0; // The first frame that FFmpeg API returns after av_seek_frame is wrong (the last frame before seek). // We are unable to detect that because the pts seems correct (the one that we requested). // So we position before the frame requested, and simply throw the first away. //#define PRESCAN_FRAMES 3 uint32_t seek_frame_no = m_last_seek_frame_no.exchange(0); #ifdef PRESCAN_FRAMES if (seek_frame_no > PRESCAN_FRAMES) { seek_frame_no -= PRESCAN_FRAMES; //m_skip_next_frame = true; /**< @todo Take deinterlace into account */ } else { seek_frame_no = 1; } #endif ret = skip_decoded_frames(seek_frame_no, true); if (ret < 0) { return ret; } } } return 0; } bool FFmpeg_Transcoder::discard_hls_seek_near_beginning(uint32_t segment_no) const { // No check if m_segment_duration == 0, values <= 0 not accepted. // Cast is OK here, the result will always be small enough for an int32. const uint32_t min_seek_segments = static_cast(params.m_min_seek_time_diff / params.m_segment_duration); const uint32_t prebuffer_segments = static_cast(params.m_prebuffer_time * AV_TIME_BASE / params.m_segment_duration); if (min_seek_segments && segment_no <= min_seek_segments + prebuffer_segments + 1) { if (segment_no > 1) { Logging::info(virtname(), "Discarding seek request to HLS segment no. %1, less than %2 seconds (%3 segments) from the beginning; starting from segment no. 1 instead.", segment_no, params.m_min_seek_time_diff / AV_TIME_BASE, min_seek_segments + prebuffer_segments); } return true; } return false; } int FFmpeg_Transcoder::seek_hls_segment(uint32_t segment_no, bool require_output_streams) { int ret = 0; const int64_t pos = (segment_no - 1) * params.m_segment_duration; m_reset_pts = FFMPEGFS_AUDIO | FFMPEGFS_VIDEO; m_have_seeked = true; Logging::info(virtname(), "Performing seek request to HLS segment no. %1.", segment_no); const bool can_seek_video = require_output_streams ? (m_in.m_video.m_stream_idx && stream_exists(m_out.m_video.m_stream_idx) && m_in.m_video.m_stream != nullptr) : (stream_exists(m_in.m_video.m_stream_idx) && m_in.m_video.m_stream != nullptr); const bool can_seek_audio = require_output_streams ? (m_in.m_audio.m_stream_idx && stream_exists(m_out.m_audio.m_stream_idx) && m_in.m_audio.m_stream != nullptr) : (stream_exists(m_in.m_audio.m_stream_idx) && m_in.m_audio.m_stream != nullptr); if (can_seek_video) { int64_t vstream_pts = ffmpeg_rescale_q(pos, av_get_time_base_q(), m_in.m_video.m_stream->time_base); if (m_in.m_video.m_stream->start_time != AV_NOPTS_VALUE) { vstream_pts += m_in.m_video.m_stream->start_time; } ret = av_seek_frame(m_in.m_format_ctx, m_in.m_video.m_stream_idx, vstream_pts, AVSEEK_FLAG_BACKWARD); } else if (can_seek_audio) { int64_t astream_pts = ffmpeg_rescale_q(pos, av_get_time_base_q(), m_in.m_audio.m_stream->time_base); if (m_in.m_audio.m_stream->start_time != AV_NOPTS_VALUE) { astream_pts += m_in.m_audio.m_stream->start_time; } ret = av_seek_frame(m_in.m_format_ctx, m_in.m_audio.m_stream_idx, astream_pts, AVSEEK_FLAG_BACKWARD); } if (ret < 0) { Logging::error(virtname(), "Seek failed on input file (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } flush_buffers(); return 0; } int FFmpeg_Transcoder::start_new_segment() { bool opened = false; encode_finish(); // Go to next requested segment... uint32_t next_segment = m_current_segment + 1; // ...or process any stacked seek requests. // No check if m_segment_duration == 0, values <= 0 not accepted. // Cast is OK here, the result will always be small enough for an int32. const uint32_t min_seek_segments = static_cast(params.m_min_seek_time_diff / params.m_segment_duration); while (!m_seek_to_fifo.empty()) { uint32_t segment_no = m_seek_to_fifo.front(); m_seek_to_fifo.pop(); if (discard_hls_seek_near_beginning(segment_no)) { continue; } if (min_seek_segments && segment_no >= next_segment && segment_no <= next_segment + min_seek_segments) { Logging::info(virtname(), "Discarding seek request to HLS segment no. %1, less than %2 seconds (%3 segments) away.", segment_no, params.m_min_seek_time_diff / AV_TIME_BASE, min_seek_segments); continue; } if (!m_buffer->segment_exists(segment_no) || !m_buffer->tell(segment_no)) // NOT EXIST or NO DATA YET { int ret = seek_hls_segment(segment_no, true); if (ret < 0) { return ret; } close_output_file(); purge_hls_fifo(); // We do not need the packets for the next frame, we start a new one at another position! // open_output() selects the active HLS cache file before // process_output()/avformat_write_header() can emit data. Make // the requested seek segment visible before reopening the output, // otherwise a mid-stream repair can reopen the previous segment. m_current_segment = segment_no; ret = open_output(m_buffer); if (ret < 0) { return ret; } next_segment = segment_no; opened = true; break; } Logging::info(virtname(), "Discarded seek request to HLS segment no. %1.", segment_no); } // Set current segment m_current_segment = next_segment; m_inhibit_stream_msk = 0; m_insert_keyframe = false; Logging::info(virtname(), "Starting HLS segment no. %1 of %2.", m_current_segment, m_virtualfile->get_segment_count()); if (!m_buffer->set_segment(m_current_segment, m_virtualfile->m_predicted_size / m_virtualfile->get_segment_count())) /** @todo Set reasonable size here */ { return AVERROR(errno); } if (!opened) { int ret = 0; // Process output file, already done by open_output() if file has been newly opened. ret = process_output(); if (ret) { return ret; } } // Flush delayed packets to disk, if any while (!m_hls_packet_fifo.empty()) { int ret = 0; FFmpeg_Packet pkt(std::move(m_hls_packet_fifo.front())); m_hls_packet_fifo.pop(); ret = av_write_frame(m_out.m_format_ctx, pkt); if (ret < 0) { Logging::error(virtname(), "Could not write frame (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } } return 0; } BITRATE FFmpeg_Transcoder::get_prores_bitrate(int width, int height, const AVRational &framerate, bool interleaved, PRORESLEVEL profile) { unsigned int mindist; size_t match = UINT_MAX; // Find best match resolution mindist = UINT_MAX; for (size_t i = 0; i < m_prores_bitrate.size(); i++) { unsigned int x = static_cast(width - m_prores_bitrate[i].m_width); unsigned int y = static_cast(height - m_prores_bitrate[i].m_height); unsigned int dist = (x * x) + (y * y); if (dist < mindist) { mindist = dist; match = i; } if (!dist) { // Exact match, won't find a better one. break; } } if (match == UINT_MAX) { return 0; } width = m_prores_bitrate[match].m_width; height = m_prores_bitrate[match].m_height; // Find best match framerate double framerateX = av_q2d(framerate); mindist = UINT_MAX; for (size_t i = match; width == m_prores_bitrate[i].m_width && height == m_prores_bitrate[i].m_height; i++) { unsigned int dist = UINT_MAX; for (size_t j = 0; j < MAX_PRORES_FRAMERATE && m_prores_bitrate[i].m_framerate[j].m_framerate; j++) { unsigned int x = static_cast(framerateX - m_prores_bitrate[i].m_framerate[j].m_framerate); unsigned int y = static_cast(interleaved - m_prores_bitrate[i].m_framerate[j].m_interleaved); dist = (x * x) + (y * y); if (dist < mindist) { mindist = dist; match = i; } if (!dist) { // Exact match, won't find a better one. break; } } if (!dist) { // Exact match, won't find a better one. break; } } if (match == UINT_MAX) { return 0; } return m_prores_bitrate[match].m_bitrate[static_cast(profile)] * (1000 * 1000); } bool FFmpeg_Transcoder::audio_size(size_t *filesize, AVCodecID codec_id, BITRATE bit_rate, int64_t duration, int channels, int sample_rate, AVSampleFormat sample_format) { BITRATE output_audio_bit_rate; int output_sample_rate; bool success = true; get_output_bit_rate(bit_rate, params.m_audiobitrate, &output_audio_bit_rate); get_output_sample_rate(sample_rate, params.m_audiosamplerate, &output_sample_rate); switch (codec_id) { case AV_CODEC_ID_AAC: { // Try to predict the size of the AAC stream (this is fairly accurate, sometimes a bit larger, sometimes a bit too small *filesize += static_cast(duration * output_audio_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1025 * (*filesize) / 1000); // add overhead (empirically determined value) break; } case AV_CODEC_ID_MP3: { // Kbps = bits per second / 8 = Bytes per second x 60 seconds = Bytes per minute x 60 minutes = Bytes per hour // This is the sum of the size of // ID3v2, ID3v1, and raw MP3 data. This is theoretically only approximate // but in practice gives excellent answers, usually exactly correct. // Cast to 64-bit int to avoid overflow. *filesize += static_cast(duration * output_audio_bit_rate / (8LL * AV_TIME_BASE)) + ID3V1_TAG_LENGTH; break; } case AV_CODEC_ID_PCM_U8: case AV_CODEC_ID_PCM_S8: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_U8); // Unsigned/signed 8 have the same width // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_S8_PLANAR: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_U8P); // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_U16LE: case AV_CODEC_ID_PCM_U16BE: case AV_CODEC_ID_PCM_S16LE: case AV_CODEC_ID_PCM_S16BE: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); // Unsigned/signed 16 have the same width // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_S16LE_PLANAR: case AV_CODEC_ID_PCM_S16BE_PLANAR: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P); // Unsigned/signed 16 have the same width // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_U24LE: // U/S24 uses U/S32 storage case AV_CODEC_ID_PCM_U24BE: case AV_CODEC_ID_PCM_S24LE: case AV_CODEC_ID_PCM_S24BE: case AV_CODEC_ID_PCM_U32LE: case AV_CODEC_ID_PCM_U32BE: case AV_CODEC_ID_PCM_S32LE: case AV_CODEC_ID_PCM_S32BE: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S32); // Unsigned/signed 32 have the same width // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_S24LE_PLANAR: // S24 uses S32 storage case AV_CODEC_ID_PCM_S32LE_PLANAR: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S32P); // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_S64LE: case AV_CODEC_ID_PCM_S64BE: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S64); // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_PCM_F16LE: case AV_CODEC_ID_PCM_F24LE: case AV_CODEC_ID_PCM_F32BE: case AV_CODEC_ID_PCM_F32LE: case AV_CODEC_ID_PCM_F64BE: case AV_CODEC_ID_PCM_F64LE: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_FLT); // File size: // file duration * sample rate (HZ) * channels * bytes per sample // + WAV_HEADER + DATA_HEADER + (with FFMpeg always) LIST_HEADER // The real size of the list header is unkown as we don't know the contents (meta tags) *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); break; } case AV_CODEC_ID_VORBIS: { // Kbps = bits per second / 8 = Bytes per second x 60 seconds = Bytes per minute x 60 minutes = Bytes per hour *filesize += static_cast(duration * output_audio_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(900 * (*filesize) / 1000); // OGG files seem to be rather smaller than expected (empirically determined value) break; } case AV_CODEC_ID_OPUS: { // Kbps = bits per second / 8 = Bytes per second x 60 seconds = Bytes per minute x 60 minutes = Bytes per hour *filesize += static_cast(duration * output_audio_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1020 * (*filesize) / 1000); // add overhead (empirically determined value) break; } case AV_CODEC_ID_ALAC: { int bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); // File size: // Apple Lossless Audio Coding promises a compression rate of 60-70%. The actual result ranges // between 30 and over 50%, heavily depending on the input. *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); *filesize = static_cast(620 * (*filesize) / 1000); // Estimate 38% compression rate (empirically determined value) break; } case AV_CODEC_ID_AC3: { // Kbps = bits per second / 8 = Bytes per second x 60 seconds = Bytes per minute x 60 minutes = Bytes per hour *filesize += static_cast(duration * output_audio_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1025 * (*filesize) / 1000); // add overhead (empirically determined value) break; } case AV_CODEC_ID_FLAC: { int bytes_per_sample = av_get_bytes_per_sample(sample_format != AV_SAMPLE_FMT_NONE ? sample_format : AV_SAMPLE_FMT_S16); // File size: // file duration * sample rate (HZ) * channels * bytes per sample // We do not add the overhead for headers, as this is an estimation and not exact anyways. *filesize += static_cast(duration * sample_rate * (channels >= 2 ? 2 : 1) * bytes_per_sample / AV_TIME_BASE); *filesize = static_cast(600 * (*filesize) / 1000); // Estimate 40% compression rate (empirically determined value) break; } case AV_CODEC_ID_NONE: { break; } default: { success = false; break; } } return success; } bool FFmpeg_Transcoder::video_size(size_t *filesize, AVCodecID codec_id, BITRATE bit_rate, int64_t duration, int width, int height, bool interleaved, const AVRational &framerate) { BITRATE out_video_bit_rate; bool success = true; get_output_bit_rate(bit_rate, params.m_videobitrate, &out_video_bit_rate); switch (codec_id) { case AV_CODEC_ID_MPEG1VIDEO: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1020 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_MPEG2VIDEO: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1020 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_H264: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1050 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_H265: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1250 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_THEORA: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1025 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_VP8: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1020 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_VP9: { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); *filesize = static_cast(1450 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_AV1: /** @todo AV1 prediction has not been tested yet */ { *filesize += static_cast(duration * out_video_bit_rate / (8LL * AV_TIME_BASE)); //*filesize = static_cast(1150 * (*filesize) / 1000); // add overhead break; } case AV_CODEC_ID_PRORES: { *filesize += static_cast(duration * get_prores_bitrate(width, height, framerate, interleaved, params.m_level) / (8LL * AV_TIME_BASE)); break; } case AV_CODEC_ID_PNG: case AV_CODEC_ID_BMP: case AV_CODEC_ID_MJPEG: { // Max. file size = (pixel dimensions x bit depth) / 8 for an uncompressed BMP, // more than sufficient for JPG/PNG as they should never get this large. *filesize += static_cast(width * height * 24 / 8); // Get the max. size break; } case AV_CODEC_ID_NONE: { break; } default: { success = false; break; } } return success; } bool FFmpeg_Transcoder::total_overhead(size_t *filesize, FILETYPE filetype) { bool success = true; switch (filetype) { // // Audio only // case FILETYPE::MP3: { // The FFmpeg API always adds an IDv2 header, size of which is unknown and // really hard to determine. So we simply add something reasonable (empirically determined value). *filesize += 250000; break; } case FILETYPE::WAV: { // This could actually precise, but yet the FFmpeg API always adds an IDv2 header, // size of which is unknown and really hard to determine. The header is small, ~30 bytes, // so we ignore that deliberatly. *filesize += sizeof(WAV_HEADER) + sizeof(WAV_LIST_HEADER) + sizeof(WAV_DATA_HEADER); break; } case FILETYPE::AIFF: { // These two "chunks" will always be there, others may be there, may be not. Their // size is hard to precalculate and small (~100 bytes), so we disregard the rest. *filesize += sizeof(AIFF_FORMCHUNK) + sizeof(AIFF_COMMONCHUNK); break; } case FILETYPE::OPUS: case FILETYPE::ALAC: case FILETYPE::FLAC: { break; } // // Video // case FILETYPE::TS: case FILETYPE::HLS: { *filesize += 1600000; // empirically determined value break; } case FILETYPE::MP4: case FILETYPE::OGG: case FILETYPE::WEBM: case FILETYPE::MOV: case FILETYPE::PRORES: case FILETYPE::MKV: { break; } // // Stills // case FILETYPE::PNG: case FILETYPE::JPG: case FILETYPE::BMP: { break; } // // Invalid // case FILETYPE::UNKNOWN: { success = false; break; } } return success; } size_t FFmpeg_Transcoder::calculate_predicted_filesize() const { if (m_in.m_format_ctx == nullptr) { return 0; } if (m_current_format == nullptr) { // Should ever happen, but better check this to avoid crashes. return 0; } size_t filesize = 0; int64_t file_duration = m_in.m_format_ctx->duration != AV_NOPTS_VALUE ? m_in.m_format_ctx->duration : 0; BITRATE input_audio_bit_rate = 0; int input_sample_rate = 0; BITRATE input_video_bit_rate = 0; if (m_fileio->duration() != AV_NOPTS_VALUE) { file_duration = m_fileio->duration(); } if (stream_exists(m_in.m_audio.m_stream_idx)) { input_sample_rate = m_in.m_audio.m_stream->codecpar->sample_rate; input_audio_bit_rate = (m_in.m_audio.m_stream->codecpar->bit_rate != 0) ? m_in.m_audio.m_stream->codecpar->bit_rate : m_in.m_format_ctx->bit_rate; } if (stream_exists(m_in.m_video.m_stream_idx)) { input_video_bit_rate = (m_in.m_video.m_stream->codecpar->bit_rate != 0) ? m_in.m_video.m_stream->codecpar->bit_rate : m_in.m_format_ctx->bit_rate; } if (input_audio_bit_rate) { int channels = get_channels(m_in.m_audio.m_codec_ctx.get()); if (!audio_size(&filesize, m_current_format->audio_codec(), input_audio_bit_rate, file_duration, channels, input_sample_rate, m_cur_sample_fmt)) { Logging::warning(filename(), "Unsupported audio codec '%1' for format %2.", get_codec_name(m_current_format->audio_codec()), m_current_format->desttype().c_str()); } } if (input_video_bit_rate) { if (m_is_video) { int width = m_in.m_video.m_stream->codecpar->width; int height = m_in.m_video.m_stream->codecpar->height; bool interleaved = params.m_deinterlace ? false : (m_in.m_video.m_stream->codecpar->field_order != AV_FIELD_PROGRESSIVE); // Deinterlace only if source is interlaced AVRational framerate = m_in.m_video.m_stream->avg_frame_rate; if (!video_size(&filesize, m_current_format->video_codec(), input_video_bit_rate, file_duration, width, height, interleaved, framerate)) { Logging::warning(filename(), "Unsupported video codec '%1' for format %2.", get_codec_name(m_current_format->video_codec()), m_current_format->desttype().c_str()); } } // else /** @todo Feature #2260: Add picture size */ // { // } } // Support #2654: Test Code // add total overhead total_overhead(&filesize, m_current_format->filetype()); return filesize; } int64_t FFmpeg_Transcoder::duration() const { return SAFE_VALUE(m_virtualfile, m_duration, 0); } size_t FFmpeg_Transcoder::predicted_filesize() const { return SAFE_VALUE(m_virtualfile, m_predicted_size, 0); } uint32_t FFmpeg_Transcoder::video_frame_count() const { return SAFE_VALUE(m_virtualfile, m_video_frame_count, 0); } uint32_t FFmpeg_Transcoder::segment_count() const { return SAFE_VALUE(m_virtualfile, get_segment_count(), 0); } int FFmpeg_Transcoder::encode_finish() { int ret = 0; if (!is_frameset()) { // If not a frame set, write trailer // Write the trailer of the output file container. ret = write_output_file_trailer(); } if (is_hls()) { m_buffer->finished_segment(); // Get segment VIRTUALFILE object std::string seg_filename(m_buffer->virtualfile()->m_destfile + "/" + make_filename(m_current_segment, params.current_format(m_buffer->virtualfile())->fileext())); LPVIRTUALFILE virtualfile = find_file(seg_filename); if (virtualfile != nullptr) { virtualfile->m_predicted_size = m_buffer->buffer_watermark(m_current_segment); stat_set_size(&virtualfile->m_st, virtualfile->m_predicted_size); } } else //if (m_virtualfile->m_flags & VIRTUALFLAG_CUESHEET) { // Save actual result size of the file stat_set_size(&m_virtualfile->m_st, m_buffer->buffer_watermark()); } return ret; } const ID3v1 * FFmpeg_Transcoder::id3v1tag() const { return &m_out.m_id3v1; } int FFmpeg_Transcoder::input_read(void * opaque, unsigned char * data, int size) { FileIO * io = static_cast(opaque); if (io == nullptr) { Logging::error(nullptr, "input_read(): Internal error: FileIO is NULL!"); return AVERROR(EINVAL); } if (io->eof()) { // At EOF return AVERROR_EOF; } int read = static_cast(io->readio(reinterpret_cast(data), static_cast(size))); if (read != size && io->error()) { // Read failed return AVERROR(io->error()); } return read; } #if LAVF_WRITEPACKET_CONST int FFmpeg_Transcoder::output_write(void * opaque, const uint8_t * data, int size) #else int FFmpeg_Transcoder::output_write(void * opaque, unsigned char * data, int size) #endif { Buffer * buffer = static_cast(opaque); if (buffer == nullptr) { Logging::error(nullptr, "input_write(): Internal error: FileIO is NULL!"); return AVERROR(EINVAL); } #if LAVF_WRITEPACKET_CONST int written = static_cast(buffer->writeio(data, static_cast(size))); #else int written = static_cast(buffer->writeio(static_cast(data), static_cast(size))); #endif if (written != size) { // Write error return (AVERROR(errno)); } return written; } int64_t FFmpeg_Transcoder::seek(void * opaque, int64_t offset, int whence) { FileIO * io = static_cast(opaque); int64_t res_offset = 0; if (io == nullptr) { Logging::error(nullptr, "seek(): Internal error: FileIO is NULL!"); return AVERROR(EINVAL); } if (whence & AVSEEK_SIZE) { // Return file size res_offset = static_cast(io->size()); } else { whence &= ~(AVSEEK_SIZE | AVSEEK_FORCE); if (!io->seek(offset, whence)) { // OK: Return position res_offset = offset; } else { // Error res_offset = AVERROR(errno); } } return res_offset; } bool FFmpeg_Transcoder::close_resample() { return m_audio_resample_ctx.reset(); } int FFmpeg_Transcoder::purge_audio_fifo() { int audio_samples_left = 0; if (!m_audio_fifo.empty()) { audio_samples_left = m_audio_fifo.size(); m_audio_fifo.reset(); } return audio_samples_left; } size_t FFmpeg_Transcoder::purge_multiframe_map() { size_t frames_left = m_frame_map.size(); m_frame_map.clear(); return frames_left; } size_t FFmpeg_Transcoder::purge_hls_fifo() { size_t hls_packets_left = m_hls_packet_fifo.size(); while (!m_hls_packet_fifo.empty()) { m_hls_packet_fifo.pop(); } return hls_packets_left; } void FFmpeg_Transcoder::purge() { std::string outfile; int audio_samples_left = purge_audio_fifo(); size_t frames_left = purge_multiframe_map(); size_t hls_packets_left = purge_hls_fifo(); if (m_out.m_format_ctx != nullptr && m_out.m_format_ctx->url != nullptr) { outfile = m_out.m_format_ctx->url; } else if (m_virtualfile != nullptr) { outfile = m_virtualfile->m_virtfile; } const char *p = outfile.empty() ? nullptr : outfile.c_str(); if (audio_samples_left) { Logging::warning(p, "%1 audio samples left in buffer and not written to target file!", audio_samples_left); } if (frames_left) { Logging::warning(p, "%1 frames left in buffer and not written to target file!", frames_left); } if (hls_packets_left) { Logging::warning(p, "%1 HLS packets left in buffer and not written to target file!", hls_packets_left); } } bool FFmpeg_Transcoder::close_output_file() { bool closed = false; purge(); close_resample(); // The deinterlace filter graph belongs to the current output/transcoding // pipeline. HLS seeks and output reopens can rebuild that pipeline without // closing the input file, so do not leave an old AVFilterGraph attached // until close_input_file(). free_filters(); m_sws_ctx.reset(); // Close output file m_out.m_audio.reset(); m_out.m_video.reset(); m_out.m_album_art.clear(); m_out.m_subtitle.clear(); closed = m_out.m_format_ctx.reset(); return closed; } bool FFmpeg_Transcoder::close_input_file() { bool closed = false; m_in.m_audio.reset(); m_in.m_video.reset(); m_in.m_album_art.clear(); m_in.m_subtitle.clear(); if (m_in.m_format_ctx != nullptr) { if (m_fileio.use_count() <= 1 && m_fileio != nullptr) { m_fileio->closeio(); } m_fileio.reset(); closed = m_in.m_format_ctx.reset(); } free_filters(); return closed; } void FFmpeg_Transcoder::closeio() { bool closed = false; // Close input file closed |= close_input_file(); // Close output file closed |= close_output_file(); // Free hardware device contexts if open hwdevice_ctx_free(&m_hwaccel_dec_device_ctx); hwdevice_ctx_free(&m_hwaccel_enc_device_ctx); // Closed anything (anything had been open to be closed in the first place)... if (closed) { Logging::trace(nullptr, "FFmpeg transcoder closed."); } } const char *FFmpeg_Transcoder::filename() const { return (m_virtualfile != nullptr ? m_virtualfile->m_origfile.c_str() : ""); } const char *FFmpeg_Transcoder::destname() const { return (m_virtualfile != nullptr ? m_virtualfile->m_destfile.c_str() : ""); } const char *FFmpeg_Transcoder::virtname() const { return (m_virtualfile != nullptr ? m_virtualfile->m_virtfile.c_str() : ""); } // create int FFmpeg_Transcoder::init_deinterlace_filters(AVCodecContext *codec_ctx, AVPixelFormat pix_fmt, const AVRational & framerate, const AVRational & time_base) { // Defensive cleanup: this function may be called again when the output // pipeline is rebuilt, for example after an HLS seek. Do not overwrite the // old filter pointers with nullptr before the old AVFilterGraph has been // freed, otherwise the graph and its internal buffers become unreachable. free_filters(); const AVFilter * buffer_src = avfilter_get_by_name("buffer"); const AVFilter * buffer_sink = avfilter_get_by_name("buffersink"); AVFilterInOut * outputs = avfilter_inout_alloc(); AVFilterInOut * inputs = avfilter_inout_alloc(); int ret = 0; try { if (!framerate.den && !framerate.num) { // No framerate, so this video "stream" has only one picture throw static_cast(AVERROR(EINVAL)); // Einzelbild-"Stream" } m_filter_graph = avfilter_graph_alloc(); if (outputs == nullptr || inputs == nullptr || m_filter_graph == nullptr) { throw static_cast(AVERROR(ENOMEM)); } // --- buffersrc (Quelle) direkt mit Args erstellen --- std::string args; strsprintf(&args, "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", codec_ctx->width, codec_ctx->height, pix_fmt, time_base.num, time_base.den, codec_ctx->sample_aspect_ratio.num, FFMAX(codec_ctx->sample_aspect_ratio.den, 1)); ret = avfilter_graph_create_filter(&m_buffer_source_context, buffer_src, "in", args.c_str(), nullptr, m_filter_graph); if (ret < 0) { Logging::error(virtname(), "Cannot create buffer source (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } // --- buffersink (Senke) mit Pre-Init-Optionen --- #if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(10, 6, 100) && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(59, 36, 100) // Neuer Weg: Array-Optionen (pixel_formats) + av_opt_set_array() m_buffer_sink_context = avfilter_graph_alloc_filter(m_filter_graph, buffer_sink, "out"); if (!m_buffer_sink_context) { throw static_cast(AVERROR(ENOMEM)); } // Nicht-Runtime-Optionen vor dem Init setzen { const enum AVPixelFormat pf = pix_fmt; // Wir ersetzen (ab Index 0) 1 Element in "pixel_formats" ret = av_opt_set_array(m_buffer_sink_context, "pixel_formats", AV_OPT_SEARCH_CHILDREN | AV_OPT_ARRAY_REPLACE, 0, /* start_elem */ 1, /* nb_elems */ AV_OPT_TYPE_PIXEL_FMT, &pf); if (ret < 0) { Logging::error(virtname(), "Cannot set buffersink pixel_formats (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } } ret = avfilter_init_str(m_buffer_sink_context, nullptr); if (ret < 0) { Logging::error(virtname(), "Cannot init buffer sink (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } #else // Alter Weg: int-list-Option (pix_fmts) vor Init setzen m_buffer_sink_context = avfilter_graph_alloc_filter(m_filter_graph, buffer_sink, "out"); if (!m_buffer_sink_context) { throw static_cast(AVERROR(ENOMEM)); } // Liste inkl. Terminator, wie in älteren Beispielen enum AVPixelFormat pixel_fmts[] = { pix_fmt, AV_PIX_FMT_NONE }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" ret = av_opt_set_int_list(m_buffer_sink_context, "pix_fmts", pixel_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); #pragma GCC diagnostic pop if (ret < 0) { Logging::error(virtname(), "Cannot set output pixel format (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } ret = avfilter_init_str(m_buffer_sink_context, nullptr); if (ret < 0) { Logging::error(virtname(), "Cannot init buffer sink (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } #endif // Endpunkte für Graph outputs->name = av_strdup("in"); outputs->filter_ctx = m_buffer_source_context; outputs->pad_idx = 0; outputs->next = nullptr; inputs->name = av_strdup("out"); inputs->filter_ctx = m_buffer_sink_context; inputs->pad_idx = 0; inputs->next = nullptr; // args "null" passthrough (dummy) filter for video // args "null" passthrough (dummy) filter for audio // --- Deinterlace-Filterkette // Deinterlace using the Bob Weaver Filter const char * filters = "bwdif=mode=send_frame:parity=auto:deint=all"; ret = avfilter_graph_parse_ptr(m_filter_graph, filters, &inputs, &outputs, nullptr); if (ret < 0) { Logging::error(virtname(), "avfilter_graph_parse_ptr failed (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } ret = avfilter_graph_config(m_filter_graph, nullptr); if (ret < 0) { Logging::error(virtname(), "avfilter_graph_config failed (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } Logging::debug(virtname(), "Deinterlacing initialised with filters '%1'.", filters); } catch (int _ret) { ret = _ret; // If graph creation/configuration failed halfway through, release the // partial graph immediately. Otherwise the next retry/seek would start // with stale filter pointers and retained filter buffers. free_filters(); } if (inputs != nullptr) { avfilter_inout_free(&inputs); } if (outputs != nullptr) { avfilter_inout_free(&outputs); } return ret; } // Aug 26 21:41:22 plattenlurch ffmpegfs[1999231]: WARNING: FILTER [out @ 0x7f99a4193bc0] The "pix_fmts" option is deprecated: set the supported pixel formats // Aug 26 21:41:22 plattenlurch ffmpegfs[1999231]: WARNING: FILTER [in @ 0x7f99a40f8700] Changing video frame properties on the fly is not supported by all filters. // Aug 26 21:41:22 plattenlurch ffmpegfs[1999231]: WARNING: FILTER [in @ 0x7f99a40f8700] filter context - w: 720 h: 480 fmt: 0 csp: unknown range: unknown, incoming frame - w: 720 h: 480 fmt: 0 csp: unknown range: tv pts_time: 1117.2298 // Aug 26 21:41:22 plattenlurch ffmpegfs[1999231]: WARNING: ENCODER [libx264 @ 0x7f99a40c9080] non-strictly-monotonic PTS int FFmpeg_Transcoder::send_filters(FFmpeg_Frame *srcframe, int & ret) { ret = 0; if (m_buffer_source_context != nullptr) { try { FFmpeg_Frame filterframe(srcframe->m_stream_idx); ret = filterframe.res(); if (ret < 0) { Logging::error(virtname(), "Unable to allocate filter frame (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } // push the decoded frame into the filtergraph ret = av_buffersrc_add_frame_flags(m_buffer_source_context, *srcframe, AV_BUFFERSRC_FLAG_KEEP_REF); if (ret < 0) { Logging::warning(virtname(), "Error while feeding the frame to filtergraph (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } // pull filtered frames from the filtergraph ret = av_buffersink_get_frame(m_buffer_sink_context, filterframe); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // Not an error, go on ret = 0; } else if (ret < 0) { Logging::error(virtname(), "Error while getting frame from filtergraph (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } else { // All OK; copy filtered frame and unref original filterframe->pts = (*srcframe)->pts; filterframe->best_effort_timestamp = (*srcframe)->best_effort_timestamp; *srcframe = filterframe; } } catch (int _ret) { ret = _ret; } } return ret; } // free void FFmpeg_Transcoder::free_filters() { // Filter contexts are owned by the graph once they have been attached to it. // Freeing the AVFilterGraph releases the source/sink contexts and all // buffers owned by the filter pipeline in one place. The member pointers // must be reset afterwards because they become dangling pointers. if (m_filter_graph != nullptr) { avfilter_graph_free(&m_filter_graph); } m_buffer_source_context = nullptr; m_buffer_sink_context = nullptr; m_filter_graph = nullptr; } int FFmpeg_Transcoder::stack_seek_frame(uint32_t frame_no) { if (frame_no > 0 && frame_no <= video_frame_count()) { std::lock_guard lock_seek_to_fifo_mutex(m_seek_to_fifo_mutex); m_seek_to_fifo.push(frame_no); // Seek to this frame next decoding operation return 0; } else { errno = EINVAL; Logging::error(virtname(), "stack_seek_frame() failed: Frame %1 was requested, but is out of range (1...%2)", frame_no, video_frame_count() + 1); return AVERROR(EINVAL); } } int FFmpeg_Transcoder::stack_seek_segment(uint32_t segment_no) { if (segment_no > 0 && segment_no <= segment_count()) { std::lock_guard lock_seek_to_fifo_mutex(m_seek_to_fifo_mutex); m_seek_to_fifo.push(segment_no); // Seek to this segment next decoding operation return 0; } else { errno = EINVAL; Logging::error(virtname(), "stack_seek() failed: Segment no. %1 was requested, but is out of range (1...%2)", segment_no, video_frame_count() + 1); return AVERROR(EINVAL); } } bool FFmpeg_Transcoder::is_multiformat() const { if (m_current_format == nullptr) { return false; } else { return m_current_format->is_multiformat(); } } bool FFmpeg_Transcoder::is_frameset() const { if (m_current_format == nullptr) { return false; } else { return m_current_format->is_frameset(); } } bool FFmpeg_Transcoder::is_hls() const { if (m_current_format == nullptr) { return false; } else { return m_current_format->is_hls(); } } bool FFmpeg_Transcoder::have_seeked() const { return m_have_seeked; } enum AVPixelFormat FFmpeg_Transcoder::get_format_static(AVCodecContext *input_codec_ctx, const enum AVPixelFormat *pix_fmts) { FFmpeg_Transcoder * pThis = static_cast(input_codec_ctx->opaque); return pThis->get_format(input_codec_ctx, pix_fmts); } enum AVPixelFormat FFmpeg_Transcoder::get_format(__attribute__((unused)) AVCodecContext *input_codec_ctx, const enum AVPixelFormat *pix_fmts) const { if (params.m_hwaccel_dec_device_type == AV_HWDEVICE_TYPE_NONE) { // We should never happen to end up here... Logging::error(filename(), "Unable to decode this file using hardware acceleration: Internal error! No hardware device type set."); return AV_PIX_FMT_NONE; } AVPixelFormat pix_fmt_expected = m_dec_hw_pix_fmt; for (const AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { if (*p == pix_fmt_expected) { return pix_fmt_expected; } } Logging::error(filename(), "Unable to decode this file using hardware acceleration. Expected format '%1' not supported.", get_pix_fmt_name(pix_fmt_expected).c_str()); return AV_PIX_FMT_NONE; } int FFmpeg_Transcoder::hwdevice_ctx_create(AVBufferRef ** hwaccel_enc_device_ctx, AVHWDeviceType dev_type, const std::string & device) const { std::string active_device(device); int ret; if (active_device == "AUTO" && dev_type == AV_HWDEVICE_TYPE_VAAPI) { active_device = "/dev/dri/renderD128"; //** @todo HWACCEL - Try to autodetect rendering device } ret = av_hwdevice_ctx_create(hwaccel_enc_device_ctx, dev_type, !active_device.empty() ? active_device.c_str() : nullptr, nullptr, 0); if (ret < 0) { Logging::error(virtname(), "Failed to create a %1 device (error '%2').", hwdevice_get_type_name(dev_type), ffmpeg_geterror(ret).c_str()); return ret; } return 0; } int FFmpeg_Transcoder::hwdevice_ctx_add_ref(AVCodecContext *input_codec_ctx) { if (m_hwaccel_dec_device_ctx == nullptr) { int ret = AVERROR(EINVAL); Logging::error(virtname(), "INTERNAL ERROR: FFmpeg_Transcoder::hwdevice_ctx_add_ref()! HW decoder device context is NULL (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } input_codec_ctx->hw_device_ctx = av_buffer_ref(m_hwaccel_dec_device_ctx); if (input_codec_ctx->hw_device_ctx == nullptr) { int ret = AVERROR(ENOMEM); Logging::error(virtname(), "A hardware device reference create failed (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } input_codec_ctx->opaque = static_cast(this); input_codec_ctx->get_format = &FFmpeg_Transcoder::get_format_static; return 0; } void FFmpeg_Transcoder::hwdevice_ctx_free(AVBufferRef **hwaccel_device_ctx) { if (*hwaccel_device_ctx != nullptr) { av_buffer_unref(hwaccel_device_ctx); *hwaccel_device_ctx = nullptr; } } int FFmpeg_Transcoder::hwframe_ctx_set(AVCodecContext *output_codec_ctx, AVCodecContext *input_codec_ctx, AVBufferRef *hw_device_ctx) const { AVBufferRef *hw_new_frames_ref; AVHWFramesContext *frames_ctx = nullptr; int ret = 0; hw_new_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx); if (hw_new_frames_ref == nullptr) { ret = AVERROR(ENOMEM); Logging::error(virtname(), "hwframe_ctx_set(): Failed to create hwframe context (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } frames_ctx = reinterpret_cast(hw_new_frames_ref->data); frames_ctx->format = m_enc_hw_pix_fmt; frames_ctx->sw_format = /*input_codec_ctx->sw_pix_fmt; */find_sw_fmt_by_hw_type(params.m_hwaccel_enc_device_type); frames_ctx->width = input_codec_ctx->width; frames_ctx->height = input_codec_ctx->height; frames_ctx->initial_pool_size = 20; // Driver default seems to be 17 ret = av_hwframe_ctx_init(hw_new_frames_ref); if (ret < 0) { Logging::error(virtname(), "hwframe_ctx_set(): Failed to initialise hwframe context (error '%1').", ffmpeg_geterror(ret).c_str()); av_buffer_unref(&hw_new_frames_ref); return ret; } output_codec_ctx->hw_frames_ctx = av_buffer_ref(hw_new_frames_ref); if (output_codec_ctx->hw_frames_ctx == nullptr) { ret = AVERROR(ENOMEM); Logging::error(virtname(), "hwframe_ctx_set(): A hardware frame reference create failed (error '%1').", ffmpeg_geterror(ret).c_str()); } av_buffer_unref(&hw_new_frames_ref); return ret; } //int FFmpeg_Transcoder::hwframe_ctx_set(AVCodecContext *output_codec_ctx, AVCodecContext *input_codec_ctx, AVBufferRef *hw_device_ctx) //{ // // If the decoder runs in hardware, we should use the decoder's frames context. This will save us from // // having to transfer frames from hardware to software and vice versa. // // If the decoder runs in software, create a new frames context. // if (input_codec_ctx->hw_frames_ctx != nullptr) // { // Logging::debug(virtname(), "Hardware encoder init: Hardware decoder active, using decoder hw_frames_ctx for encoder."); // /* we need to ref hw_frames_ctx of decoder to initialize encoder's codec. // Only after we get a decoded frame, can we obtain its hw_frames_ctx */ // output_codec_ctx->hw_frames_ctx = av_buffer_ref(input_codec_ctx->hw_frames_ctx); // if (!output_codec_ctx->hw_frames_ctx) // { // int ret = AVERROR(ENOMEM); // Logging::error(virtname(), "A hardware frame reference create failed (error '%1').", ffmpeg_geterror(ret).c_str()); // return ret; // } // m_hwaccel_dec = true; /* Doing decoding in hardware */ // } // else { // Logging::debug(virtname(), "Hardware encoder init: Software decoder active, creating new hw_frames_ctx for encoder."); // AVBufferRef *hw_new_frames_ref; // AVHWFramesContext *frames_ctx = nullptr; // int ret = 0; // if (!(hw_new_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) // { // ret = AVERROR(ENOMEM); // Logging::error(virtname(), "Failed to create hwframe context (error '%1').", ffmpeg_geterror(ret).c_str()); // return ret; // } // frames_ctx = (AVHWFramesContext *)(hw_new_frames_ref->data); // frames_ctx->format = m_hw_pix_fmt; // frames_ctx->sw_format = find_sw_fmt_by_hw_type(params.m_hwaccel_enc_device_type); // frames_ctx->width = input_codec_ctx->width; // frames_ctx->height = input_codec_ctx->height; // frames_ctx->initial_pool_size = 20; // Driver default: 17 // if ((ret = av_hwframe_ctx_init(hw_new_frames_ref)) < 0) // { // Logging::error(virtname(), "Failed to initialise hwframe context (error '%1').", ffmpeg_geterror(ret).c_str()); // av_buffer_unref(&hw_new_frames_ref); // return ret; // } // output_codec_ctx->hw_frames_ctx = av_buffer_ref(hw_new_frames_ref); // if (!output_codec_ctx->hw_frames_ctx) // { // Logging::error(virtname(), "A hardware frame reference create failed (error '%1').", ffmpeg_geterror(AVERROR(ENOMEM))); // ret = AVERROR(ENOMEM); // } // av_buffer_unref(&hw_new_frames_ref); // m_hwaccel_dec = false; /* Doing decoding in software */ // } // return 0; //} int FFmpeg_Transcoder::hwframe_copy_from_hw(AVCodecContext * /*ctx*/, FFmpeg_Frame *sw_frame, const AVFrame * hw_frame) const { int ret; ret = av_frame_copy_props(*sw_frame, hw_frame); if (ret < 0) { Logging::error(filename(), "Failed to copy frame properties (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } ret = av_hwframe_transfer_data(*sw_frame, hw_frame, 0); if (ret < 0) { Logging::error(filename(), "Error while transferring frame data from surface (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } return 0; } int FFmpeg_Transcoder::hwframe_copy_to_hw(AVCodecContext *output_codec_ctx, FFmpeg_Frame *hw_frame, const AVFrame * sw_frame) const { int ret; ret = av_frame_copy_props(*hw_frame, sw_frame); if (ret < 0) { Logging::error(virtname(), "Failed to copy frame properties (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } ret = av_hwframe_get_buffer(output_codec_ctx->hw_frames_ctx, *hw_frame, 0); if (ret < 0) { Logging::error(virtname(), "Failed to copy frame buffers to hardware memory (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } if ((*hw_frame)->hw_frames_ctx == nullptr) { ret = AVERROR(ENOMEM); Logging::error(virtname(), "Failed to copy frame buffers to hardware memory (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } ret = av_hwframe_transfer_data(*hw_frame, sw_frame, 0); if (ret < 0) { Logging::error(virtname(), "Error while transferring frame data to surface (error '%1').", ffmpeg_geterror(ret).c_str()); return ret; } return 0; } /** * @todo HWACCEL - Supported formats * * Tested and working: VA-API, MMAL and OMX. * * Some VA-API formats do not yet work, see "fixit" * * V4LM2M: implemented, but untested * NIVIDA/CUDA: implemented, but untested * */ int FFmpeg_Transcoder::get_hw_decoder_name(AVCodecID codec_id, std::string *codec_name) const { std::string codec_name_buf; int ret = 0; switch (params.m_hwaccel_dec_API) { case HWACCELAPI::VAAPI: { ret = get_hw_vaapi_codec_name(codec_id, &codec_name_buf); break; } case HWACCELAPI::MMAL: { ret = get_hw_mmal_decoder_name(codec_id, &codec_name_buf); break; } //case HWACCELAPI::V4L2M2M: //{ // ret = get_hw_v4l2m2m_decoder_name(codec_id, &codec_name_buf); // break; //} case HWACCELAPI::NONE: default: { ret = AVERROR_DECODER_NOT_FOUND; break; } } if (codec_name != nullptr) { if (!ret) { *codec_name = codec_name_buf; } else { codec_name->clear(); } } return ret; } int FFmpeg_Transcoder::get_hw_encoder_name(AVCodecID codec_id, std::string *codec_name) const { std::string codec_name_buf; int ret = 0; switch (params.m_hwaccel_enc_API) { case HWACCELAPI::VAAPI: { ret = get_hw_vaapi_codec_name(codec_id, &codec_name_buf); break; } case HWACCELAPI::OMX: { ret = get_hw_omx_encoder_name(codec_id, &codec_name_buf); break; } //case HWACCELAPI::V4L2M2M: //{ // ret = get_hw_v4l2m2m_encoder_name(codec_id, &codec_name_buf); // break; //} case HWACCELAPI::NONE: default: { ret = AVERROR_DECODER_NOT_FOUND; break; } } if (codec_name != nullptr) { if (!ret) { *codec_name = codec_name_buf; } else { codec_name->clear(); } } return ret; } int FFmpeg_Transcoder::get_hw_vaapi_codec_name(AVCodecID codec_id, std::string *codec_name) const { int ret = 0; /** * *** Intel VAAPI de/encoder *** * * h264_vaapi H.264/AVC (VAAPI) (codec h264) * hevc_vaapi H.265/HEVC (VAAPI) (codec hevc) * mjpeg_vaapi MJPEG (VAAPI) (codec mjpeg) * mpeg2_vaapi MPEG-2 (VAAPI) (codec mpeg2video) * vp1_vaapi VC1 (VAAPI) (codec vc1) seems to be possible on my hardware * vp8_vaapi VP8 (VAAPI) (codec vp8) * vp9_vaapi VP9 (VAAPI) (codec vp9) * */ switch (codec_id) { case AV_CODEC_ID_H264: { *codec_name = "h264_vaapi"; break; } /** * @todo fixit, MPEG-1 decoding does not work... * * Program terminated with signal SIGSEGV, Segmentation fault. * #0 __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:383 * 383 ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S: Datei oder Verzeichnis nicht gefunden. * [Current thread is 1 (Thread 0x7f95a24d4700 (LWP 16179))] * (gdb) bt * #0 __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:383 * #1 0x00007f95903c4e26 in ?? () from /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so * #2 0x00007f95aaae80b8 in vaGetImage () from /lib/x86_64-linux-gnu/libva.so.2 * #3 0x00007f95af524bb5 in ?? () from /lib/x86_64-linux-gnu/libavutil.so.56 * #4 0x00007f95af5250fb in ?? () from /lib/x86_64-linux-gnu/libavutil.so.56 * #5 0x00007f95af51c37f in av_hwframe_transfer_data () from /lib/x86_64-linux-gnu/libavutil.so.56 * #6 0x00007f95af51c406 in av_hwframe_transfer_data () from /lib/x86_64-linux-gnu/libavutil.so.56 * #7 0x0000555da4fde146 in FFmpeg_Transcoder::decode_video_frame (this=0x7f9598002e90, pkt=0x7f95a24d2f90, decoded=0x7f95a24d2ec4) at ffmpeg_transcoder.cc:2655 * #8 0x0000555da4fde5cd in FFmpeg_Transcoder::decode_frame (this=0x7f9598002e90, pkt=0x7f95a24d2f90) at ffmpeg_transcoder.cc:2852 * #9 0x0000555da4fdea4b in FFmpeg_Transcoder::read_decode_convert_and_store (this=0x7f9598002e90, finished=0x7f95a24d3030) at ffmpeg_transcoder.cc:3189 * #10 0x0000555da4fdfa73 in FFmpeg_Transcoder::process_single_fr (this=this\@entry=0x7f9598002e90, status=@0x7f95a24d3134: 0) at ffmpeg_transcoder.cc:3987 * #11 0x0000555da4f8c997 in transcoder_thread (arg=optimized out) at transcode.cc:874 * #12 0x0000555da4fc54ef in thread_pool::loop_function (this=0x7f959c002b40) at thread_pool.cc:78 * #13 0x00007f95aeaf4c10 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 * #14 0x00007f95ae9f0ea7 in start_thread (arg=optimized out) at pthread_create.c:477 * #15 0x00007f95ae920d4f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 * */ //case AV_CODEC_ID_MJPEG: //{ // *codec_name = "mjpeg_vaapi"; // break; //} case AV_CODEC_ID_MPEG2VIDEO: { *codec_name = "mpeg2_vaapi"; break; } case AV_CODEC_ID_HEVC: { *codec_name = "hevc_vaapi"; break; } case AV_CODEC_ID_VC1: { *codec_name = "vc1_vaapi"; break; } case AV_CODEC_ID_VP8: { *codec_name = "vp9_vaapi"; break; } case AV_CODEC_ID_VP9: { *codec_name = "vp9_vaapi"; break; } default: { ret = AVERROR_DECODER_NOT_FOUND; break; } } return ret; } int FFmpeg_Transcoder::get_hw_mmal_decoder_name(AVCodecID codec_id, std::string *codec_name) const { int ret = 0; /** * *** MMAL decoder *** * * h264_mmal h264 (mmal) (codec h264) * mpeg2_mmal mpeg2 (mmal) (codec mpeg2video) * mpeg4_mmal mpeg4 (mmal) (codec mpeg4) * vc1_mmal vc1 (mmal) (codec vc1) * */ switch (codec_id) { case AV_CODEC_ID_H264: { *codec_name = "h264_mmal"; break; } /** * @todo mmal MPEG1 hardware acceleration not working. Probably because I have not bought a key... @n * @n * INFO : [/root/test/in/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).mpeg] Transcoding to ts. @n * INFO : [/root/test/in/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).mpeg] Hardware decoder acceleration active using codec 'mpeg2_mmal'. @n * INFO : [/root/test/in/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).mpeg] Hardware decoder acceleration enabled. Codec 'mpeg2_mmal'. @n * mmal: mmal_vc_port_info_set: failed to set port info (2:0): EINVAL @n * mmal: mmal_vc_port_set_format: mmal_vc_port_info_set failed 0x6b985440 (EINVAL) @n * mmal: mmal_port_disable: port vc.ril.video_decode:in:0(MP2V)(0x6b985440) is not enabled @n * mmal: mmal_port_disable: port vc.ril.video_decode:out:0(0x6b985890) is not enabled @n * mmal: mmal_port_disable: port vc.ril.video_decode:ctr:0(0x6b94db90) is not enabled @n * ERROR : [/root/test/in/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).mpeg] Failed to open video input codec for stream video (error '0'). @n * ERROR : [/root/test/in/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).mpeg] Failed to open video codec (error 'Unknown error occurred'). @n * ERROR : [/root/test/out/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).ts] Transcoding exited with error. @n * ERROR : [/root/test/out/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).ts] System error: (5) Input/output error @n * ERROR : [/root/test/out/En Vogue - Don-t Let Go (Love) (Official Music Video) (mpeg2).ts] FFMpeg error: (-1313558101) Unknown error occurred */ case AV_CODEC_ID_MPEG2VIDEO: { *codec_name = "mpeg2_mmal"; break; } case AV_CODEC_ID_MPEG4: { *codec_name = "mpeg4_mmal"; break; } /** * @todo mmal VC1 hardware acceleration not working. Probably because I have not bought a key... @n * @n * INFO : [/root/test/in/Test_1440x576_WVC1_6Mbps.wmv] Hardware decoder acceleration active using codec 'vc1_mmal'. @n * INFO : [/root/test/in/Test_1440x576_WVC1_6Mbps.wmv] Hardware decoder acceleration enabled. Codec 'vc1_mmal'. @n * mmal: mmal_vc_port_info_set: failed to set port info (2:0): EINVAL @n * mmal: mmal_vc_port_set_format: mmal_vc_port_info_set failed 0x6e54c560 (EINVAL) @n * mmal: mmal_port_disable: port vc.ril.video_decode:in:0(WVC1)(0x6e54c560) is not enabled @n * mmal: mmal_port_disable: port vc.ril.video_decode:out:0(0x6e546660) is not enabled @n * mmal: mmal_port_disable: port vc.ril.video_decode:ctr:0(0x6e54c240) is not enabled @n * ERROR : [/root/test/in/Test_1440x576_WVC1_6Mbps.wmv] Failed to open video input codec for stream video (error '0'). @n * ERROR : [/root/test/in/Test_1440x576_WVC1_6Mbps.wmv] Failed to open video codec (error 'Unknown error occurred'). */ case AV_CODEC_ID_VC1: { *codec_name = "vc1_mmal"; break; } default: { ret = AVERROR_DECODER_NOT_FOUND; break; } } return ret; } //int FFmpeg_Transcoder::get_hw_v4l2m2m_decoder_name(AVCodecID codec_id, std::string *codec_name) const //{ // int ret = 0; // /** // * *** v4l2m2m (Video2linux) decoder *** // * // * h263_v4l2m2m V4L2 mem2mem H.263 decoder wrapper (codec h263) // * h264_v4l2m2m V4L2 mem2mem H.264 decoder wrapper (codec h264) // * hevc_v4l2m2m V4L2 mem2mem HEVC decoder wrapper (codec hevc) // * mpeg1_v4l2m2m V4L2 mem2mem MPEG1 decoder wrapper (codec mpeg1video) // * mpeg2_v4l2m2m V4L2 mem2mem MPEG2 decoder wrapper (codec mpeg2video) // * mpeg4_v4l2m2m V4L2 mem2mem MPEG4 decoder wrapper (codec mpeg4) // * vc1_v4l2m2m V4L2 mem2mem VC1 decoder wrapper (codec vc1) // * vp8_v4l2m2m V4L2 mem2mem VP8 decoder wrapper (codec vp8) // * vp9_v4l2m2m V4L2 mem2mem VP9 decoder wrapper (codec vp9) // */ // switch (codec_id) // { // case AV_CODEC_ID_H263: // { // *codec_name = "h263_v4l2m2m"; // break; // } // case AV_CODEC_ID_H264: // { // *codec_name = "h264_v4l2m2m"; // break; // } // case AV_CODEC_ID_H265: // { // *codec_name = "hevc_v4l2m2m"; // break; // } // case AV_CODEC_ID_MPEG1VIDEO: // { // *codec_name = "mpeg1_v4l2m2m"; // break; // } // case AV_CODEC_ID_MPEG2VIDEO: // { // *codec_name = "mpeg2_v4l2m2m"; // break; // } // case AV_CODEC_ID_MPEG4: // { // *codec_name = "mpeg4_v4l2m2m"; // break; // } // //case AV_CODEC_ID_VC1: /** @todo WMV is currently not supported */ // //{ // // *codec_name = "vc1_v4l2m2m"; // // break; // //} // case AV_CODEC_ID_VP8: // { // *codec_name = "vp8_v4l2m2m"; // break; // } // case AV_CODEC_ID_VP9: // { // *codec_name = "vp9_v4l2m2m"; // break; // } // default: // { // ret = AVERROR_DECODER_NOT_FOUND; // break; // } // } // return ret; //} int FFmpeg_Transcoder::get_hw_omx_encoder_name(AVCodecID codec_id, std::string *codec_name) const { int ret = 0; /** * *** Openmax encoder *** * * h264_omx OpenMAX IL H.264 video encoder (codec h264) */ switch (codec_id) { case AV_CODEC_ID_H264: { *codec_name = "h264_omx"; break; } default: { ret = AVERROR_DECODER_NOT_FOUND; break; } } return ret; } int FFmpeg_Transcoder::get_hw_v4l2m2m_encoder_name(AVCodecID codec_id, std::string *codec_name) const { int ret = 0; /** * * *** v4l2m2m (Video2linux) encoder *** * * h263_v4l2m2m V4L2 mem2mem H.263 encoder wrapper (codec h263) * h264_v4l2m2m V4L2 mem2mem H.264 encoder wrapper (codec h264) * hevc_v4l2m2m V4L2 mem2mem HEVC encoder wrapper (codec hevc) * mpeg4_v4l2m2m V4L2 mem2mem MPEG4 encoder wrapper (codec mpeg4) * vp8_v4l2m2m V4L2 mem2mem VP8 encoder wrapper (codec vp8) */ switch (codec_id) { case AV_CODEC_ID_H263: { *codec_name = "h263_v4l2m2m"; break; } case AV_CODEC_ID_H264: { *codec_name = "h264_v4l2m2m"; break; } case AV_CODEC_ID_H265: { *codec_name = "hevc_v4l2m2m"; break; } case AV_CODEC_ID_MPEG4: { *codec_name = "mpeg4_v4l2m2m"; break; } case AV_CODEC_ID_VP8: { *codec_name = "vp8_v4l2m2m"; break; } default: { ret = AVERROR_DECODER_NOT_FOUND; break; } } return ret; } AVPixelFormat FFmpeg_Transcoder::find_sw_fmt_by_hw_type(AVHWDeviceType type) { DEVICETYPE_MAP::const_iterator it = m_devicetype_map.find(type); if (it == m_devicetype_map.cend()) { return AV_PIX_FMT_NONE; } return it->second; } void FFmpeg_Transcoder::get_pix_formats(AVPixelFormat *in_pix_fmt, AVPixelFormat *out_pix_fmt, AVCodecContext* output_codec_ctx) const { *in_pix_fmt = static_cast(m_in.m_video.m_stream->codecpar->format); if (m_hwaccel_enable_dec_buffering) { *in_pix_fmt = find_sw_fmt_by_hw_type(params.m_hwaccel_dec_device_type); } if (output_codec_ctx == nullptr) { output_codec_ctx = m_out.m_video.m_codec_ctx.get(); } // Fail safe: If output_codec_ctx is NULL, set to something common (AV_PIX_FMT_YUV420P is widely used) *out_pix_fmt = (output_codec_ctx != nullptr) ? output_codec_ctx->pix_fmt : AV_PIX_FMT_YUV420P; if (*in_pix_fmt == AV_PIX_FMT_NONE) { // If input's stream pixel format is unknown, use same as output (may not work but at least will not crash FFmpeg) *in_pix_fmt = *out_pix_fmt; } // If hardware acceleration is enabled, e.g., output_codec_ctx->pix_fmt is AV_PIX_FMT_VAAPI // but the format actually is AV_PIX_FMT_NV12 so we use the correct value from sw_format in // the hardware frames context. if (m_hwaccel_enable_enc_buffering && output_codec_ctx != nullptr && output_codec_ctx->hw_frames_ctx != nullptr && output_codec_ctx->hw_frames_ctx->data != nullptr) { *out_pix_fmt = reinterpret_cast(output_codec_ctx->hw_frames_ctx->data)->sw_format; } } uint32_t FFmpeg_Transcoder::get_next_segment(int64_t pos) const { return (static_cast(pos / params.m_segment_duration + 1)); } bool FFmpeg_Transcoder::goto_next_segment(uint32_t next_segment) const { return (next_segment == m_current_segment + 1 && next_segment <= m_virtualfile->get_segment_count()); } bool FFmpeg_Transcoder::is_audio_stream(int stream_idx) const { return (stream_exists(stream_idx) && stream_idx == m_in.m_audio.m_stream_idx); } bool FFmpeg_Transcoder::is_video_stream(int stream_idx) const { return (stream_exists(stream_idx) && stream_idx == m_in.m_video.m_stream_idx); } bool FFmpeg_Transcoder::is_subtitle_stream(int stream_idx) const { StreamRef_map::const_iterator it = m_in.m_subtitle.find(stream_idx); return (it != m_in.m_subtitle.cend()); } FFmpeg_Transcoder::StreamRef * FFmpeg_Transcoder::get_out_subtitle_stream(int stream_idx) { StreamRef_map::iterator it = m_out.m_subtitle.find(stream_idx); if (it == m_out.m_subtitle.end()) { return nullptr; } return &it->second; } bool FFmpeg_Transcoder::stream_exists(int stream_idx) const { return (stream_idx != INVALID_STREAM); } void FFmpeg_Transcoder::add_stream_map(int in_stream_idx, int out_stream_idx) { if (in_stream_idx != INVALID_STREAM) { m_stream_map.emplace(in_stream_idx, out_stream_idx); } } int FFmpeg_Transcoder::map_in_to_out_stream(int in_stream_idx) const { STREAM_MAP::const_iterator it = m_stream_map.find(in_stream_idx); if (it == m_stream_map.cend()) { return INVALID_STREAM; } return (it->second); } inline int FFmpeg_Transcoder::foreach_subtitle_file(const std::string& search_path, const std::regex& regex, int depth, const std::function &)> &f) { int ret = 0; try { const std::filesystem::directory_iterator end; for (std::filesystem::directory_iterator iter{ search_path }; iter != end && ret == 0; iter++) { const std::string subtitle_filename(iter->path().filename().string()); if (std::filesystem::is_regular_file(*iter)) { if (std::regex_match(subtitle_filename, regex)) { std::smatch res; if (std::regex_search(subtitle_filename.cbegin(), subtitle_filename.cend(), res, regex)) { if (res[2].length()) { ret = f(iter->path().string(), res[2]); } else { ret = f(iter->path().string(), std::nullopt); } } } } else if (std::filesystem::is_directory(*iter) && depth > 0) { ret = foreach_subtitle_file(iter->path().string(), regex, depth - 1, f); } } } catch (std::filesystem::filesystem_error& e) { ret = AVERROR(e.code().value()); Logging::error(filename(), "Could not open directory '%1' (error '%2').", search_path.c_str(), ffmpeg_geterror(ret).c_str()); } catch (std::bad_alloc & e) { ret = AVERROR(ENOMEM); Logging::error(filename(), "Could not open directory '%1' (error '%2').", search_path.c_str(), ffmpeg_geterror(ret).c_str()); } return ret; } int FFmpeg_Transcoder::read_packet(void *opaque, uint8_t *buf, int buf_size) { BUFFER_DATA *bd = static_cast(opaque); buf_size = FFMIN(buf_size, static_cast(bd->size)); if (!buf_size) { return AVERROR_EOF; } /* copy internal buffer data to buf */ std::memcpy(buf, bd->ptr, static_cast(buf_size)); bd->ptr += buf_size; bd->size -= static_cast(buf_size); return buf_size; } int FFmpeg_Transcoder::add_external_subtitle_stream(const std::string & subtitle_file, const std::optional & language) { AVFormatContext *format_ctx = nullptr; AVIOContext *avio_ctx = nullptr; uint8_t *buffer = nullptr; size_t buffer_size; int ret = 0; if (language) { Logging::debug(filename(), "Adding external subtitle stream: %1 [%2]", subtitle_file.c_str(), language->c_str()); } else { Logging::debug(filename(), "Adding external subtitle stream: %1", subtitle_file.c_str()); } try { uint8_t *avio_ctx_buffer = nullptr; int avio_ctx_buffer_size = 4096; BUFFER_DATA bd = { nullptr, 0 }; // slurp file content into buffer ret = av_file_map(subtitle_file.c_str(), &buffer, &buffer_size, 0, nullptr); if (ret < 0) { throw ret; } // fill opaque structure used by the AVIOContext read callback bd.ptr = buffer; bd.size = buffer_size; format_ctx = avformat_alloc_context(); if (format_ctx == nullptr) { throw AVERROR(ENOMEM); } avio_ctx_buffer = reinterpret_cast(av_malloc(static_cast(avio_ctx_buffer_size))); if (avio_ctx_buffer == nullptr) { throw AVERROR(ENOMEM); } avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, nullptr, nullptr); if (avio_ctx == nullptr) { throw AVERROR(ENOMEM); } format_ctx->pb = avio_ctx; ret = avformat_open_input(&format_ctx, nullptr, nullptr, nullptr); if (ret < 0) { Logging::error(filename(), "Could not open input file (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } ret = avformat_find_stream_info(format_ctx, nullptr); if (ret < 0) { Logging::error(filename(), "Could not find stream info (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } //av_dump_format(format_ctx, 0, subtitle_file.c_str(), 0); AVCodecContext * codec_ctx = nullptr; int stream_idx = INVALID_STREAM; ret = open_bestmatch_decoder(format_ctx, &codec_ctx, &stream_idx, AVMEDIA_TYPE_SUBTITLE); if (ret < 0 && ret != AVERROR_STREAM_NOT_FOUND) // AVERROR_STREAM_NOT_FOUND is not an error { Logging::error(filename(), "Failed to open video codec (error '%1').", ffmpeg_geterror(ret).c_str()); throw ret; } if (ret != AVERROR_STREAM_NOT_FOUND) { AVCodecID codec_id = m_current_format->subtitle_codec(codec_ctx->codec_id); // Get matching output codec if (codec_id != AV_CODEC_ID_NONE) { StreamRef input_streamref; codec_ctx->pkt_timebase = codec_ctx->time_base = format_ctx->streams[0]->time_base; // We have a subtitle stream input_streamref.set_codec_ctx(codec_ctx); input_streamref.m_stream = format_ctx->streams[0]; input_streamref.m_stream_idx = INVALID_STREAM; //m_active_stream_msk |= FFMPEGFS_SUBTITLE; ret = add_subtitle_stream(codec_id, input_streamref, language); if (ret < 0) { throw ret; } int output_stream_index = ret; FFmpeg_Packet pkt; ret = pkt.res(); if (ret < 0) { throw ret; } // Read one frame from the input file into a temporary packet. while ((ret = av_read_frame(format_ctx, pkt)) == 0) { int decoded; ret = decode_subtitle(codec_ctx, pkt, &decoded, output_stream_index); if (ret < 0) { throw ret; } } } } if (ret == AVERROR_EOF) { ret = 0; } } catch (int _ret) { ret = _ret; } avformat_close_input(&format_ctx); // note: the internal buffer could have changed, and be != avio_ctx_buffer if (avio_ctx != nullptr) { av_freep(&avio_ctx->buffer); } avio_context_free(&avio_ctx); av_file_unmap(buffer, buffer_size); return ret; } int FFmpeg_Transcoder::add_external_subtitle_streams() { int ret; try { std::filesystem::path file(filename()); std::string stem(file.stem().string()); // Escape characters that are meaningful to regexp. regex_escape(&stem); std::string regex_string("^(" + stem + "[.])(.*)([.]srt|[.]vtt)|^(" + stem + ")([.]srt|[.]vtt)"); // filename.srt/vtt or filename.lang.srt/vtt std::regex regex(regex_string, std::regex::ECMAScript); //ret = foreach_subtitle_file( // file.parent_path(), // regex, // 0, // std::bind(&FFmpeg_Transcoder::add_external_subtitle_stream, this, std::placeholders::_1, std::placeholders::_2)); // clang-tidy now recommends a lambda ret = foreach_subtitle_file( file.parent_path().string(), regex, 0, [this](const std::string & subtitle_file, const std::optional & language) { add_external_subtitle_stream(std::forward(subtitle_file), std::forward(language)); return 0; }); // Ugly, but a lambda would also be possible //ret = foreach_subtitle_file( // file.parent_path(), // regex, // 0, // [this](const std::string & subtitle_file, const std::optional & language) //{ // if (language) // { // Logging::error(filename(), "[%1] %2", language->c_str(), subtitle_file.c_str()); // } // else // { // Logging::error(filename(), "%1", subtitle_file.c_str()); // } // return 0; //}); } catch (std::regex_error & e) { ret = 0; // Ignore error Logging::error(filename(), "INTERNAL ERROR: FFmpeg_Transcoder::add_external_subtitle_streams()! Unable to create reg exp: %1", e.what()); } return ret; } int64_t FFmpeg_Transcoder::pts() const { MULTIFRAME_MAP::const_reverse_iterator it = m_frame_map.crbegin(); if (it != m_frame_map.crend()) { return it->first; } else { return 0; } } uint32_t FFmpeg_Transcoder::last_seek_frame_no() const { return m_last_seek_frame_no; } ffmpegfs-2.50/src/wave.h0000664000175000017500000001274415177713600010641 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file wave.h * @brief WAVE file structures * https://wavefilegem.com/how_wave_files_work.html * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef WAVE_H #define WAVE_H #pragma once #include #include #pragma pack(push, 1) /** * @brief WAVE header structure * * @note All numeric values are in big-endian format. */ typedef struct WAV_HEADER { /**@{*/ std::array m_riff_header; /**< @brief RIFF Header: Contains the letters "RIFF" in ASCII form (0x52494646 big-endian form). */ /** * Size of the wav portion of the file, which follows the first 8 bytes (File size - 8).@n * 36 + fmt_chunk_size, or more precisely:@n * 4 + (8 + fmt_chunk_size) + (8 + fmt_chunk_size) * This is the size of the rest of the chunk * following this number. This is the size of the * entire file in bytes minus 8 bytes for the * two fields not included in this count: * riff_header and wav_size. */ uint32_t m_wav_size; std::array m_wave_header; /**< @brief Contains the letters "WAVE" (0x57415645 big-endian form). */ /**@}*/ /**@{*/ std::array m_fmt_header; /**< @brief RIFF Format Header: Contains "fmt " including trailing space (0x666d7420 big-endian form). */ uint32_t m_fmt_chunk_size; /**< @brief Should be 16 for PCM This is the size of the rest of the chunk size which follows this number. */ uint16_t m_audio_format; /**< @brief Should be 1 for PCM. 3 for IEEE Float */ uint16_t m_num_channels; /**< @brief Number of channels 1...n (1: mono, 2: stereo/dual channel;...) */ uint32_t m_sample_rate; /**< @brief 8000, 44100, etc. */ uint32_t m_byte_rate; /**< @brief Number of bytes per second. sample_rate * num_channels * bit_depth / 8 */ uint16_t m_sample_alignment; /**< @brief num_channels * bit_depth / 8 */ uint16_t m_bit_depth; /**< @brief Number of bits per sample: 8 bits = 8, 16 bits = 16, etc. */ /**@}*/ } WAV_HEADER; static_assert(sizeof(WAV_HEADER) == 36); /** * @brief WAVE extended header structure * * @note All numeric values are in big-endian format. */ typedef struct WAV_HEADER_EX { uint16_t m_extension_size; /**< @brief Extension Size 2 16-bit unsigned integer (value 22) */ uint16_t m_valid_bits_per_sample; /**< @brief Valid Bits Per Sample 2 16-bit unsigned integer */ uint32_t m_channel_mask; /**< @brief Channel Mask 4 32-bit unsigned integer */ std::array m_sub_format_guid; /**< @brief Sub Format GUID 16 16-byte GUID */ } WAV_HEADER_EX; static_assert(sizeof(WAV_HEADER_EX) == 24); /** * @brief WAVE "fact" header structure * * @note All numeric values are in big-endian format. */ typedef struct WAV_FACT { std::array m_chunk_id; /**< @brief Chunk ID 4 0x66 0x61 0x63 0x74 (i.e. "fact") */ uint32_t m_body_size; /**< @brief Chunk Body Size 4 32-bit unsigned integer */ uint32_t m_number_of_sample_frames; /**< @brief Number of sample frames 4 32-bit unsigned integer */ } WAV_FACT; static_assert(sizeof(WAV_FACT) == 12); /** * @brief WAVE list header structure * * @note All numeric values are in big-endian format. */ typedef struct WAV_LIST_HEADER { std::array m_list_header; /**< @brief Contains "list" (0x6C696E74) */ uint32_t m_data_bytes; /**< @brief Number of bytes in list. */ std::array m_list_type; /**< @brief Contains "adtl" (0x6164746C) */ } WAV_LIST_HEADER; static_assert(sizeof(WAV_LIST_HEADER) == 12); /** @brief WAVE data header structure * * @note All numeric values are in big-endian format. */ typedef struct WAV_DATA_HEADER { std::array m_data_header; /**< @brief Contains "data" (0x64617461 big-endian form). */ uint32_t m_data_bytes; /**< @brief Number of bytes in data. Number of samples * num_channels * bit_depth / 8 */ // Remainder of wave file: actual sound data // uint8_t m_bytes[]; } WAV_DATA_HEADER; static_assert(sizeof(WAV_DATA_HEADER) == 8); #pragma pack(pop) #endif // WAVE_H ffmpegfs-2.50/src/buffer.cc0000664000175000017500000007407515207101110011270 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file buffer.cc * @brief Buffer class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "buffer.h" #include "ffmpegfs.h" #include "logging.h" #include #include #include #include // Initially Buffer is empty. It will be allocated as needed. Buffer::Buffer() : m_cur_ci(nullptr) , m_cur_open(0) { } // If buffer_data was never allocated, this is a no-op. Buffer::~Buffer() { release(); } VIRTUALTYPE Buffer::type() const { return VIRTUALTYPE::BUFFER; } size_t Buffer::bufsize() const { return 0; // Not applicable } int Buffer::openio(LPVIRTUALFILE virtualfile) { if (virtualfile == nullptr) { errno = EINVAL; return (EOF); } set_virtualfile(virtualfile); return 0; } bool Buffer::open_file(uint32_t segment_no, uint32_t flags, size_t defaultsize) { std::lock_guard lock_mutex(m_mutex); uint32_t index = segment_no; if (index) { index--; } CACHEINFO & ci = m_ci[index]; ci.m_flags |= flags; if (ci.m_fd != -1) { Logging::trace(ci.m_cachefile, "Cache file is already open."); if (defaultsize) { // Make sure the requested size is available reserve(defaultsize); } // Already open return true; } if (flags & CACHE_FLAG_RW) { Logging::debug(ci.m_cachefile, "Writing to cache file."); } else { Logging::info(ci.m_cachefile, "Reading from cache file."); } size_t filesize = 0; bool isdefaultsize = false; uint8_t *p = nullptr; if (!map_file(ci.m_cachefile, &ci.m_fd, &p, &filesize, &isdefaultsize, defaultsize, (flags & CACHE_FLAG_RW) ? true : false)) { return false; } if (!isdefaultsize) { ci.m_buffer_pos = ci.m_buffer_watermark = filesize; } ci.m_buffer_size = filesize; ci.m_buffer = static_cast(p); ci.m_buffer_write_size = 0; ci.m_buffer_writes = 0; ++m_cur_open; // track open files return true; } bool Buffer::close_file(uint32_t segment_no, uint32_t flags) { std::lock_guard lock_mutex(m_mutex); uint32_t index = segment_no; if (index) { index--; } CACHEINFO & ci = m_ci[index]; ci.m_flags &= ~flags; if (ci.m_flags) { Logging::trace(ci.m_cachefile, "While attempting to close, the cache file is still in use. Currently open: %1", m_cur_open); return true; } if (ci.m_fd == -1) { // Already closed Logging::trace(ci.m_cachefile, "No need to close the unopened cache file. Currently open: %1", m_cur_open); return true; } Logging::trace(ci.m_cachefile, "Closing cache file."); bool success = unmap_file(ci.m_cachefile, &ci.m_fd, &ci.m_buffer, ci.m_buffer_size, &ci.m_buffer_watermark); ci.m_buffer_pos = 0; ci.m_buffer_size = 0; if (success && m_cur_open > 0) { --m_cur_open; // track open files } return success; } bool Buffer::init(bool erase_cache) { std::lock_guard lock_mutex(m_mutex); if (is_open()) { return true; } bool success = true; try { if ((virtualfile()->m_flags & VIRTUALFLAG_HLS)) { // HLS format: create several segments if (virtualfile()->get_segment_count()) { m_ci.resize(virtualfile()->get_segment_count()); for (uint32_t segment_no = 1; segment_no <= virtualfile()->get_segment_count(); segment_no++) { make_cachefile_name(&m_ci[segment_no - 1].m_cachefile, filename() + "." + make_filename(segment_no, params.current_format(virtualfile())->fileext()), params.current_format(virtualfile())->fileext(), false); } } else { Logging::error(filename(), "INTERNAL ERROR: Buffer::init()! Segment count is 0."); errno = EINVAL; throw false; } } else { // All other formats: create just a single segment. m_ci.resize(1); make_cachefile_name(&m_ci[0].m_cachefile, filename(), params.current_format(virtualfile())->fileext(), false); if ((virtualfile()->m_flags & VIRTUALFLAG_FRAME)) { // Create extra index cash for frame sets only make_cachefile_name(&m_ci[0].m_cachefile_idx, filename(), params.current_format(virtualfile())->fileext(), true); } } // Set current segment m_cur_ci = &m_ci[0]; // Create the path to the cache file. All paths are the same, so this is required only once. std::shared_ptr cachefiletmp = new_strdup(m_ci[0].m_cachefile); if (cachefiletmp == nullptr) { Logging::error(m_ci[0].m_cachefile, "Error opening the cache file: out of memory."); errno = ENOMEM; throw false; } if (mktree(dirname(cachefiletmp.get()), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) && errno != EEXIST) { Logging::error(m_ci[0].m_cachefile, "Error creating cache directory: (%1) %2", errno, strerror(errno)); throw false; } errno = 0; // reset EEXIST, error can safely be ignored here #if __cplusplus >= 202002L // C++20 (and later) code for (uint32_t index = 0; CACHEINFO & ci : m_ci) #else uint32_t index = 0; for (CACHEINFO & ci : m_ci) #endif { ci.reset(); if (erase_cache) { remove_cachefile(index + 1); errno = 0; // ignore this error } index++; } // Index only required for frame sets and there is only one. if (!m_ci[0].m_cachefile_idx.empty()) { if (virtualfile()->m_video_frame_count == 0) { errno = EINVAL; Logging::error(m_ci[0].m_cachefile, "INTERNAL ERROR: Buffer::init()! Frame count is zero (%1) %2", errno, strerror(errno)); throw false; } static_assert(sizeof(IMAGE_FRAME) == 32, "sizeof(IMAGE_FRAME) must be 32 bytes"); size_t filesize = 0; bool isdefaultsize = false; uint8_t *p = nullptr; if (!map_file(m_ci[0].m_cachefile_idx, &m_ci[0].m_fd_idx, &p, &filesize, &isdefaultsize, sizeof(IMAGE_FRAME) * virtualfile()->m_video_frame_count, false)) { throw false; } m_ci[0].m_buffer_size_idx = filesize; m_ci[0].m_buffer_idx = static_cast(p); } } catch (bool _success) { success = _success; if (!success) { for (CACHEINFO & ci : m_ci) { ci.reset(); } } } return success; } bool Buffer::set_segment(uint32_t segment_no, size_t size) { std::lock_guard lock_mutex(m_mutex); if (!segment_no || segment_no > segment_count()) { errno = EINVAL; return false; } if (!close_file(current_segment_no(), CACHE_FLAG_RW)) { return false; } if (!open_file(segment_no, CACHE_FLAG_RW, size)) { return false; } m_cur_ci = &m_ci[segment_no - 1]; // Reserve enough buffer space for segment to avoid frequent resizes return reserve(size); } uint32_t Buffer::segment_count() { std::lock_guard lock_mutex(m_mutex); return static_cast(m_ci.size()); } uint32_t Buffer::current_segment_no() { std::lock_guard lock_mutex(m_mutex); if (!segment_count() || m_cur_ci == nullptr) { return 0; } return static_cast(m_cur_ci - &m_ci[0]) + 1; } bool Buffer::segment_exists(uint32_t segment_no) { std::lock_guard lock_mutex(m_mutex); if (!segment_count()) { return false; } uint32_t index = segment_no; if (index) { index--; } if (index >= m_ci.size()) { errno = EBADF; return false; } return file_exists(m_ci[index].m_cachefile); } bool Buffer::cachefile_valid(uint32_t segment_no) { std::lock_guard lock_mutex(m_mutex); LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return false; } struct stat sb; if (stat(ci->m_cachefile.c_str(), &sb) == -1) { return false; } if (!S_ISREG(sb.st_mode)) { errno = EINVAL; return false; } if (sb.st_size <= 0) { errno = ENODATA; return false; } errno = 0; return true; } bool Buffer::invalidate_segment(uint32_t segment_no) { std::lock_guard lock_mutex(m_mutex); LPCACHEINFO ci = cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return false; } bool success = true; ci->m_seg_finished = false; ci->m_buffer_pos = 0; ci->m_buffer_watermark = 0; ci->m_buffer_write_size = 0; ci->m_buffer_writes = 0; ci->m_flags = 0; if (ci->m_fd != -1 || ci->m_buffer != nullptr) { if (!unmap_file(ci->m_cachefile, &ci->m_fd, &ci->m_buffer, ci->m_buffer_size, &ci->m_buffer_watermark)) { success = false; } if (m_cur_open > 0) { --m_cur_open; } } ci->m_buffer_pos = 0; ci->m_buffer_watermark = 0; ci->m_buffer_size = 0; if (!remove_file(ci->m_cachefile)) { success = false; } if (segment_no == 0 && ci->m_buffer_idx != nullptr && ci->m_buffer_size_idx) { std::memset(ci->m_buffer_idx, 0, ci->m_buffer_size_idx); } return success; } bool Buffer::map_file(const std::string & filename, volatile int *fd, uint8_t **p, size_t *filesize, bool *isdefaultsize, size_t defaultsize, bool truncate) const { bool success = true; if (!defaultsize) { Logging::trace(filename, "Mapping cache file."); } else { Logging::trace(filename, "Mapping cache file with %1.", format_size(defaultsize).c_str()); } try { struct stat sb; *fd = ::open(filename.c_str(), O_CREAT | O_RDWR | (truncate ? O_TRUNC : 0), static_cast(0644)); if (*fd == -1) { Logging::error(filename, "The cache file could not be opened due to an error: (%1) %2", errno, strerror(errno)); throw false; } if (fstat(*fd, &sb) == -1) { Logging::error(filename, "File stat failed: (%1) %2 (fd = %3)", errno, strerror(errno), *fd); throw false; } if (!S_ISREG(sb.st_mode)) { Logging::error(filename, "Not a file."); throw false; } if (!sb.st_size || defaultsize) { // If file is empty or did not exist set file size to default if (!defaultsize) { defaultsize = static_cast(sysconf(_SC_PAGESIZE)); } if (ftruncate(*fd, static_cast(defaultsize)) == -1) { Logging::error(filename, "Error calling ftruncate() to 'stretch' the file: (%1) %2 (fd = %3)", errno, strerror(errno), *fd); throw false; } *filesize = defaultsize; *isdefaultsize = true; } else { // Keep size *filesize = static_cast(sb.st_size); *isdefaultsize = false; } *p = static_cast(mmap(nullptr, *filesize, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0)); if (*p == MAP_FAILED) { Logging::error(filename, "File mapping failed: (%1) %2 (fd = %3)", errno, strerror(errno), *fd); *p = nullptr; throw false; } } catch (bool _success) { success = _success; if (!success && *fd != -1) { ::close(*fd); *fd = -1; } } return success; } bool Buffer::unmap_file(const std::string &filename, volatile int *fd, uint8_t **p, size_t len, size_t * filesize) const { bool success = true; Logging::trace(filename, "Unmapping cache file."); void * _p = *p; size_t _filesize = *filesize; int _fd = *fd; // Clear all variables *p = nullptr; *filesize = 0; *fd = -1; if (_p != nullptr) { if (munmap(_p, len ? len : static_cast(sysconf(_SC_PAGESIZE))) == -1) // Make sure we do not unmap a zero size file (spits EINVAL error) { Logging::error(filename, "Unmapping cache file failed: (%1) %2 Size: %3", errno, strerror(errno), len); success = false; } } if (_fd != -1) { if (_filesize) { if (ftruncate(_fd, static_cast(_filesize)) == -1) { Logging::error(filename, "Error calling ftruncate() to resize and close the cache file: (%1) %2 (fd = %3) Size: %5", errno, strerror(errno), _fd, _filesize); success = false; } ::close(_fd); } else { ::close(_fd); if (unlink(filename.c_str()) && errno != ENOENT) // Ignore if file does not exist { Logging::error(filename, "Error removing the cache file: (%1) %2 (fd = %3)", errno, strerror(errno), _fd); success = false; } } } return success; } bool Buffer::release(int flags /*= CACHE_CLOSE_NOOPT*/) { std::lock_guard lock_mutex(m_mutex); bool success = true; if (!is_open()) { if (CACHE_CHECK_BIT(CACHE_CLOSE_DELETE, flags)) { for (uint32_t n = 1; n <= segment_count(); n++) { remove_cachefile(n); } errno = 0; // ignore this error } return true; } // Write active cache to disk flush(); // Close anything that's still open for (uint32_t index = 0; index < segment_count(); index++) { if (!close_file(index + 1, CACHE_FLAG_RO | CACHE_FLAG_RW)) { success = false; } if (CACHE_CHECK_BIT(CACHE_CLOSE_DELETE, flags)) { remove_cachefile(index + 1); errno = 0; // ignore this error } } // Remove index for frame sets. There is only one. if (!m_ci[0].m_cachefile_idx.empty()) { if (!unmap_file(m_ci[0].m_cachefile_idx, &m_ci[0].m_fd_idx, &m_ci[0].m_buffer_idx, m_ci[0].m_buffer_size_idx, &m_ci[0].m_buffer_size_idx)) { success = false; } } return success; } bool Buffer::remove_cachefile(uint32_t segment_no) const { const CACHEINFO & ci = !segment_no ? *m_cur_ci : m_ci[segment_no - 1]; bool success = remove_file(ci.m_cachefile); if (!ci.m_cachefile_idx.empty()) { if (!remove_file(ci.m_cachefile_idx)) { success = false; } } return success; } bool Buffer::flush() { std::lock_guard lock_mutex(m_mutex); if (!segment_count() || m_cur_ci == nullptr || m_cur_ci->m_buffer == nullptr) { errno = EPERM; return false; } if (msync(m_cur_ci->m_buffer, m_cur_ci->m_buffer_size, MS_SYNC) == -1) { Logging::error(m_cur_ci->m_cachefile, "Could not sync to disk: (%1) %2", errno, strerror(errno)); return false; } if (m_cur_ci->m_buffer_idx != nullptr) { if (msync(m_cur_ci->m_buffer_idx, m_cur_ci->m_buffer_size_idx, MS_SYNC) == -1) { Logging::error(m_cur_ci->m_cachefile_idx, "Could not sync to disk: (%1) %2", errno, strerror(errno)); return false; } } return true; } bool Buffer::clear() { std::lock_guard lock_mutex(m_mutex); bool success = true; for (CACHEINFO & ci : m_ci) { ci.m_buffer_pos = 0; ci.m_buffer_watermark = 0; ci.m_buffer_size = 0; ci.m_seg_finished = false; ci.m_buffer_write_size = 0; ci.m_buffer_writes = 0; if (ci.m_fd != -1) { // If empty set file size to 1 page long filesize = sysconf(_SC_PAGESIZE); if (ftruncate(ci.m_fd, filesize) == -1) { Logging::error(ci.m_cachefile, "Error calling ftruncate() to clear the file: (%1) %2 (fd = %3)", errno, strerror(errno), ci.m_fd); success = false; } } else { remove_file(ci.m_cachefile); } if (ci.m_fd_idx != -1) { std::memset(ci.m_buffer_idx, 0, ci.m_buffer_size_idx); } } return success; } bool Buffer::reserve(size_t size) { std::lock_guard lock_mutex(m_mutex); if (m_cur_ci == nullptr) { errno = ENOMEM; Logging::error(nullptr, "INTERNAL ERROR: Buffer::reserve() - m_cur_ci == nullptr!"); return false; } if (m_cur_ci->m_buffer == nullptr) { errno = ENOMEM; Logging::error(nullptr, "INTERNAL ERROR: Buffer::reserve() - m_cur_ci->m_buffer == nullptr!"); return false; } if (m_cur_ci->m_buffer_size >= size) { // Do not shrink return true; } m_cur_ci->m_buffer = static_cast(mremap(m_cur_ci->m_buffer, m_cur_ci->m_buffer_size, size, MREMAP_MAYMOVE)); if (m_cur_ci->m_buffer == MAP_FAILED) { Logging::error(m_cur_ci->m_cachefile, "Error calling mremap() to resize the file: (%1) %2 (fd = %3) Old size: %4 New: %5", errno, strerror(errno), m_cur_ci->m_fd, m_cur_ci->m_buffer_size, size); m_cur_ci->m_buffer = nullptr; return false; } // Save size m_cur_ci->m_buffer_size = size; if (ftruncate(m_cur_ci->m_fd, static_cast(m_cur_ci->m_buffer_size)) == -1) { Logging::error(m_cur_ci->m_cachefile, "Error calling ftruncate() to resize the file: (%1) %2 (fd = %3)", errno, strerror(errno), m_cur_ci->m_fd); return false; } return true; } size_t Buffer::writeio(const uint8_t* data, size_t length) { std::lock_guard lock_mutex(m_mutex); if (m_cur_ci == nullptr || m_cur_ci->m_buffer == nullptr) { errno = ENOMEM; return 0; } uint8_t* write_ptr = write_prepare(length); if (write_ptr == nullptr) { length = 0; } else { m_cur_ci->m_buffer_write_size += length; m_cur_ci->m_buffer_writes++; std::memcpy(write_ptr, data, length); increment_pos(length); } return length; } size_t Buffer::write_frame(const uint8_t *data, size_t length, uint32_t frame_no) { std::lock_guard lock_mutex(m_mutex); if (data == nullptr || m_cur_ci == nullptr || m_cur_ci->m_buffer_idx == nullptr || frame_no < 1 || frame_no > virtualfile()->m_video_frame_count) { // Invalid parameter errno = EINVAL; return 0; } LPIMAGE_FRAME old_image_frame; IMAGE_FRAME new_image_frame; size_t bytes_written; size_t start = static_cast(frame_no - 1) * sizeof(IMAGE_FRAME); old_image_frame = reinterpret_cast(m_cur_ci->m_buffer_idx + start); if (old_image_frame->m_frame_no && (old_image_frame->m_size <= static_cast(length))) { // Frame already exists and has enough space old_image_frame->m_size = static_cast(length); // Write image seek(static_cast(old_image_frame->m_offset), SEEK_SET); bytes_written = writeio(data, old_image_frame->m_size); if (bytes_written != old_image_frame->m_size) { return 0; } } else { // Create new frame if not existing or not enough space std::memset(&new_image_frame, 0xFF, sizeof(new_image_frame)); std::memcpy(new_image_frame.m_tag.data(), IMAGE_FRAME_TAG, sizeof(new_image_frame.m_tag)); new_image_frame.m_frame_no = frame_no; new_image_frame.m_offset = buffer_watermark(); new_image_frame.m_size = static_cast(length); // Write image seek(static_cast(new_image_frame.m_offset), SEEK_SET); bytes_written = writeio(data, new_image_frame.m_size); if (bytes_written != new_image_frame.m_size) { return 0; } std::memcpy(reinterpret_cast(m_cur_ci->m_buffer_idx + start), &new_image_frame, sizeof(IMAGE_FRAME)); } return bytes_written; } uint8_t* Buffer::write_prepare(size_t length) { if (reallocate(m_cur_ci->m_buffer_pos + length)) { if (m_cur_ci->m_buffer_watermark < m_cur_ci->m_buffer_pos + length) { m_cur_ci->m_buffer_watermark = m_cur_ci->m_buffer_pos + length; } return m_cur_ci->m_buffer + m_cur_ci->m_buffer_pos; } else { errno = ESPIPE; return nullptr; } } void Buffer::increment_pos(size_t increment) { m_cur_ci->m_buffer_pos += increment; } int Buffer::seek(int64_t offset, int whence) { return seek(offset, whence, 0); } int Buffer::seek(int64_t offset, int whence, uint32_t segment_no) { LPCACHEINFO ci = cacheinfo(segment_no); if (ci == nullptr || ci->m_buffer == nullptr) { errno = ENOMEM; return (EOF); } off_t seek_pos; switch (whence) { case SEEK_SET: { seek_pos = offset; break; } case SEEK_CUR: { seek_pos = static_cast(tell(segment_no)) + offset; break; } case SEEK_END: { seek_pos = static_cast(size(segment_no)) + offset; break; } default: { errno = EINVAL; return (EOF); } } if (seek_pos > static_cast(size(segment_no))) { ci->m_buffer_pos = size(segment_no); // Cannot go beyond EOF. Set position to end, leave errno untouched. return 0; } if (seek_pos < 0) // Cannot go before head, leave position untouched, set errno. { errno = EINVAL; return (EOF); } ci->m_buffer_pos = static_cast(seek_pos); return 0; } size_t Buffer::tell() const { return tell(0); } size_t Buffer::tell(uint32_t segment_no) const { LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return 0; } return ci->m_buffer_pos; } int64_t Buffer::duration() const { return AV_NOPTS_VALUE; // not applicable } size_t Buffer::size() const { return size(0); } size_t Buffer::size(uint32_t segment_no) const { LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return 0; } return ci->m_buffer_size; } size_t Buffer::buffer_watermark(uint32_t segment_no) const { LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return 0; } return ci->m_buffer_watermark; } bool Buffer::copy(std::vector * out_data, size_t offset, uint32_t segment_no) { return copy(out_data->data(), offset, out_data->size(), segment_no); } bool Buffer::copy(uint8_t* out_data, size_t offset, size_t bufsize, uint32_t segment_no) { std::lock_guard lock_mutex(m_mutex); LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return false; } if (ci->m_buffer == nullptr) { errno = ENOMEM; return false; } size_t segment_size = ci->m_buffer_size; if (!segment_size && errno) { Logging::error(ci->m_cachefile, "INTERNAL ERROR: Buffer::copy()! size(segment_no) returned error. Segment: %1 (%2) %3", segment_no, errno, strerror(errno)); return false; } if (segment_size > offset) { if (segment_size < offset + bufsize) { bufsize = segment_size - offset - 1; } std::memcpy(out_data, ci->m_buffer + offset, bufsize); return true; } else { Logging::error(ci->m_cachefile, "INTERNAL ERROR: Buffer::copy()! segment_size <= offset - Segment: %1 Segment Size: %2 Offset: %3", segment_no, segment_size, offset); errno = ESPIPE; return false; } } bool Buffer::reallocate(size_t newsize) { if (newsize > size()) { if (m_cur_ci->m_buffer_writes) { size_t alloc_size = newsize - size(); size_t write_avg = m_cur_ci->m_buffer_write_size / m_cur_ci->m_buffer_writes; size_t write_size = PREALLOC_FACTOR * write_avg; if (write_size > alloc_size) { alloc_size = write_size; newsize = size() + alloc_size; } } Logging::trace(filename(), "Buffer reallocate: %1 -> %2 (Diff %3).", size(), newsize, newsize - size()); if (!reserve(newsize)) { return false; } } return true; } const std::string & Buffer::cachefile(uint32_t segment_no) const { LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { static std::string empty; errno = EBADF; return empty; } return ci->m_cachefile; } const std::string & Buffer::make_cachefile_name(std::string * cachefile, const std::string & filename, const std::string & fileext, bool is_idx) { transcoder_cache_path(cachefile); *cachefile += params.m_mountpath; *cachefile += filename; if (is_idx) { *cachefile += ".idx."; } else { *cachefile += ".cache."; } *cachefile += fileext; return *cachefile; } bool Buffer::remove_file(const std::string & filename) { if (unlink(filename.c_str()) && errno != ENOENT) { Logging::warning(filename, "Cannot unlink the file: (%1) %2", errno, strerror(errno)); return false; } else { errno = 0; return true; } } size_t Buffer::readio(void * /*data*/, size_t /*size*/) { // Not implemented errno = EPERM; return 0; } size_t Buffer::read_frame(std::vector * data, uint32_t frame_no) { std::lock_guard lock_mutex(m_mutex); if (data == nullptr || m_cur_ci->m_buffer_idx == nullptr || frame_no < 1 || frame_no > virtualfile()->m_video_frame_count) { // Invalid parameter errno = EINVAL; return 0; } LPCIMAGE_FRAME image_frame; size_t start = static_cast(frame_no - 1) * sizeof(IMAGE_FRAME); image_frame = reinterpret_cast(m_cur_ci->m_buffer_idx + start); if (!image_frame->m_frame_no) { errno = EAGAIN; return 0; } data->resize(image_frame->m_size); return copy(data, static_cast(image_frame->m_offset)); } int Buffer::error() const { return errno; } bool Buffer::eof() const { return eof(0); } bool Buffer::eof(uint32_t segment_no) const { return (tell(segment_no) == size(segment_no)); } void Buffer::closeio() { release(); } bool Buffer::have_frame(uint32_t frame_no) { std::lock_guard lock_mutex(m_mutex); if (m_cur_ci->m_buffer_idx == nullptr || frame_no < 1 || frame_no > virtualfile()->m_video_frame_count) { // Invalid parameter errno = EINVAL; return false; } LPCIMAGE_FRAME image_frame; size_t start = static_cast(frame_no - 1) * sizeof(IMAGE_FRAME); image_frame = reinterpret_cast(m_cur_ci->m_buffer_idx + start); return (image_frame->m_frame_no ? true : false); } bool Buffer::is_open() { std::lock_guard lock_mutex(m_mutex); for (const CACHEINFO & ci : m_ci) { if ((ci.m_fd != -1 && (fcntl(ci.m_fd, F_GETFL) != -1 || errno != EBADF))) { return true; } } return false; } void Buffer::finished_segment() { if (m_cur_ci == nullptr) { return; } m_cur_ci->m_seg_finished = true; flush(); } bool Buffer::is_segment_finished(uint32_t segment_no) const { LPCCACHEINFO ci = const_cacheinfo(segment_no); if (ci == nullptr) { errno = EBADF; return false; } return ci->m_seg_finished; } Buffer::LPCACHEINFO Buffer::cacheinfo(uint32_t segment_no) { if (segment_no) { segment_no--; if (segment_no >= segment_count()) { return nullptr; } return (&m_ci[segment_no]); } return m_cur_ci; } Buffer::LPCCACHEINFO Buffer::const_cacheinfo(uint32_t segment_no) const { if (segment_no) { segment_no--; if (segment_no >= m_ci.size()) { return nullptr; } return (&m_ci[segment_no]); } return m_cur_ci; } ffmpegfs-2.50/src/ffmpeg_packet.h0000664000175000017500000000632115177713600012464 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_packet.h * @brief FFmpeg AVPacket RAII wrapper * * @ingroup ffmpegfs */ #ifndef FFMPEG_PACKET_H #define FFMPEG_PACKET_H #pragma once #include "ffmpeg_utils.h" /** * @brief RAII wrapper for AVPacket. * * AVPacket must not be stack allocated anymore. Newer FFmpeg versions deprecated * av_init_packet() and explicitly warn that sizeof(AVPacket) is no longer meant * to be part of the public ABI. This wrapper always uses av_packet_alloc() and * releases the packet with av_packet_free(). */ class FFmpeg_Packet { public: /** * @brief Allocate an empty packet. */ explicit FFmpeg_Packet(int stream_index = INVALID_STREAM); /** * @brief Clone an existing packet. */ FFmpeg_Packet(const AVPacket *packet); // cppcheck-suppress noExplicitConstructor /** * @brief Deep-copy construct from another wrapper. */ FFmpeg_Packet(const FFmpeg_Packet &packet); /** * @brief Move construct from another wrapper. */ FFmpeg_Packet(FFmpeg_Packet &&packet) noexcept; /** * @brief Free the underlying packet. */ virtual ~FFmpeg_Packet(); /** * @brief Deep-copy assign from another wrapper. */ FFmpeg_Packet& operator=(const FFmpeg_Packet &packet) noexcept; /** * @brief Move assign from another wrapper. */ FFmpeg_Packet& operator=(FFmpeg_Packet &&packet) noexcept; /** * @brief Deep-copy assign from an AVPacket. */ FFmpeg_Packet& operator=(const AVPacket *packet) noexcept; /** * @brief Get result of last operation. * @return 0 if successful, or a negative AVERROR value. */ int res() const; /** * @brief Clone packet to a new AVPacket*. Caller owns the returned packet. */ AVPacket* clone() const; /** * @brief Unreference packet payload, keep the AVPacket object allocated. */ void unref(); /** * @brief Free the underlying packet. */ void free(); /** * @brief Access the underlying packet. */ AVPacket* get(); /** * @brief Access the underlying packet. */ const AVPacket* get() const; /** * @brief operator AVPacket*: behave like a packet pointer for FFmpeg APIs. */ operator AVPacket*(); /** * @brief operator const AVPacket*: behave like a const packet pointer for FFmpeg APIs. */ operator const AVPacket*() const; /** * @brief operator ->: behave like a packet pointer. */ AVPacket* operator->(); /** * @brief operator ->: behave like a const packet pointer. */ const AVPacket* operator->() const; protected: AVPacket * m_packet; /**< @brief Pointer to underlying AVPacket struct */ int m_res; /**< @brief 0 if last operation was successful, or negative AVERROR value */ }; #endif // FFMPEG_PACKET_H ffmpegfs-2.50/src/ffmpeg_base.cc0000664000175000017500000004261315177713600012271 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_base.cc * @brief FFmpeg_Base class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #include #include #include #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_base.h" #include "logging.h" FFmpeg_Base::FFmpeg_Base() : m_virtualfile(nullptr) { } void FFmpeg_Base::video_stream_setup(AVCodecContext *output_codec_ctx, AVStream* output_stream, AVCodecContext *input_codec_ctx, AVRational framerate, AVPixelFormat enc_hw_pix_fmt) const { AVRational time_base_tbn; AVRational time_base_tbc; if (!framerate.num || !framerate.den) { framerate.num = 25; framerate.den = 1; Logging::warning(nullptr, "No information about the input framerate is available. Falling back to a default value of 25fps for the output stream."); } // timebase: This is the fundamental unit of time (in seconds) in terms // of which frame timestamps are represented. For fixed-fps content, // timebase should be 1/framerate and timestamp increments should be // identical to 1. //time_base = m_in.m_pVideo_stream->time_base; // tbn: must be set differently for the target format. Otherwise produces strange results. switch (output_codec_ctx->codec_id) { case AV_CODEC_ID_THEORA: // ogg case AV_CODEC_ID_MPEG1VIDEO: case AV_CODEC_ID_MPEG2VIDEO: { time_base_tbn = av_inv_q(framerate); time_base_tbc = time_base_tbn; break; } case AV_CODEC_ID_VP9: // webm { time_base_tbn.num = 1; time_base_tbn.den = 1000; time_base_tbc = time_base_tbn; break; } case AV_CODEC_ID_H264: // h264 case AV_CODEC_ID_H265: // h265 { time_base_tbn.num = 1; time_base_tbn.den = 90000; time_base_tbc = av_inv_q(framerate); break; } default: // mp4 and all others { time_base_tbn.num = 1; time_base_tbn.den = 90000; time_base_tbc = time_base_tbn; break; } } // tbn output_stream->time_base = time_base_tbn; // tbc output_codec_ctx->time_base = time_base_tbc; // tbr // output_stream->r_frame_rate = m_in.m_pVideo_stream->r_frame_rate; output_stream->r_frame_rate = framerate; // fps output_stream->avg_frame_rate = framerate; // output_codec_ctx->framerate = framerate; if (enc_hw_pix_fmt == AV_PIX_FMT_NONE) { // Automatic pix_fmt selection int loss = 0; AVPixelFormat src_pix_fmt = input_codec_ctx->pix_fmt; #if LAVC_USE_SUPPORTED_CFG { const enum AVPixelFormat *pix_list = nullptr; int npix = 0; int ret_cfg = avcodec_get_supported_config(output_codec_ctx, output_codec_ctx->codec, AV_CODEC_CONFIG_PIX_FORMAT, 0, (const void**)&pix_list, &npix); if (ret_cfg >= 0 && pix_list && npix > 0) { int alpha = 0; enc_hw_pix_fmt = avcodec_find_best_pix_fmt_of_list(pix_list, src_pix_fmt, alpha, &loss); } } #else if (output_codec_ctx->codec->pix_fmts != nullptr) { int alpha = 0; enc_hw_pix_fmt = avcodec_find_best_pix_fmt_of_list(output_codec_ctx->codec->pix_fmts, src_pix_fmt, alpha, &loss); } #endif if (enc_hw_pix_fmt == AV_PIX_FMT_NONE) { // Fail safe if avcodec_find_best_pix_fmt_of_list has no idea what to use. switch (output_codec_ctx->codec_id) { case AV_CODEC_ID_PRORES: // mov/prores { // yuva444p10le // ProRes 4:4:4 if the source is RGB and ProRes 4:2:2 if the source is YUV. enc_hw_pix_fmt = AV_PIX_FMT_YUV422P10LE; break; } default: // all others { // At this moment the output format must be AV_PIX_FMT_YUV420P; enc_hw_pix_fmt = AV_PIX_FMT_YUV420P; break; } } } } output_codec_ctx->pix_fmt = enc_hw_pix_fmt; output_codec_ctx->gop_size = 12; // emit one intra frame every twelve frames at most } int FFmpeg_Base::dict_set_with_check(AVDictionary **pm, const char *key, const char *value, int flags, const char * filename, bool nodelete) const { if (nodelete && !*value) { return 0; } int ret = av_dict_set(pm, key, value, flags); if (ret < 0) { Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str()); } return ret; } int FFmpeg_Base::dict_set_with_check(AVDictionary **pm, const char *key, int64_t value, int flags, const char * filename, bool nodelete) const { if (nodelete && !value) { return 0; } int ret = av_dict_set_int(pm, key, value, flags); if (ret < 0) { Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str()); } return ret; } int FFmpeg_Base::opt_set_with_check(void *obj, const char *key, const char *value, int flags, const char * filename) const { int ret = av_opt_set(obj, key, value, flags); if (ret < 0) { Logging::error(filename, "Error setting dictionary option key(%1)='%2' (error '%3').", key, value, ffmpeg_geterror(ret).c_str()); } return ret; } void FFmpeg_Base::video_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const { if (stream != nullptr && stream->codecpar != nullptr) { int64_t duration = AV_NOPTS_VALUE; if (stream->duration != AV_NOPTS_VALUE) { duration = ffmpeg_rescale_q_rnd(stream->duration, stream->time_base); } Logging::debug(out_file ? virtname() : filename(), "Video %1 #%2: %3@%4 [%5]", out_file ? "out" : "in", stream->index, get_codec_name(stream->codecpar->codec_id), format_bitrate((stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : format_ctx->bit_rate).c_str(), format_duration(duration).c_str()); } else { Logging::debug(out_file ? virtname() : filename(), "Video %1: invalid stream", out_file ? "out" : "in"); } } void FFmpeg_Base::audio_info(bool out_file, const AVFormatContext *format_ctx, const AVStream *stream) const { if (stream != nullptr && stream->codecpar != nullptr) { int64_t duration = AV_NOPTS_VALUE; if (stream->duration != AV_NOPTS_VALUE) { duration = ffmpeg_rescale_q_rnd(stream->duration, stream->time_base); } Logging::debug(out_file ? virtname() : filename(), "Audio %1 #2: %3@%4 %5 Channels %6 [%7]", out_file ? "out" : "in", stream->index, get_codec_name(stream->codecpar->codec_id), format_bitrate((stream->codecpar->bit_rate != 0) ? stream->codecpar->bit_rate : format_ctx->bit_rate).c_str(), get_channels(stream->codecpar), format_samplerate(stream->codecpar->sample_rate).c_str(), format_duration(duration).c_str()); } else { Logging::debug(out_file ? virtname() : filename(), "Audio %1: invalid stream", out_file ? "out" : "in"); } } void FFmpeg_Base::subtitle_info(bool out_file, const AVFormatContext * /*format_ctx*/, const AVStream *stream) const { if (stream != nullptr && stream->codecpar != nullptr) { Logging::debug(out_file ? virtname() : filename(), "Subtitle %1 #%2: %3", out_file ? "out" : "in", stream->index, get_codec_name(stream->codecpar->codec_id)); } else { Logging::debug(out_file ? virtname() : filename(), "Subtitle %1: invalid stream", out_file ? "out" : "in"); } } std::string FFmpeg_Base::get_pix_fmt_name(enum AVPixelFormat pix_fmt) { const char *fmt_name = av_get_pix_fmt_name(pix_fmt); return (fmt_name != nullptr ? fmt_name : "none"); } std::string FFmpeg_Base::get_sample_fmt_name(AVSampleFormat sample_fmt) { return av_get_sample_fmt_name(sample_fmt); } #if LAVU_DEP_OLD_CHANNEL_LAYOUT std::string FFmpeg_Base::get_channel_layout_name(const AVChannelLayout * ch_layout) { std::array buffer; av_channel_layout_describe(ch_layout, buffer.data(), buffer.size() - 1); return buffer.data(); } #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT std::string FFmpeg_Base::get_channel_layout_name(int nb_channels, uint64_t channel_layout) { std::array buffer; av_get_channel_layout_string(buffer.data(), buffer.size() - 1, nb_channels, channel_layout); return buffer.data(); } #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT uint32_t FFmpeg_Base::pts_to_frame(AVStream* stream, int64_t pts) const { if (pts == AV_NOPTS_VALUE) { return 0; } int64_t start_time = (stream->start_time != AV_NOPTS_VALUE) ? stream->start_time : 0; AVRational factor = av_mul_q(stream->avg_frame_rate, stream->time_base); return static_cast(av_rescale(pts - start_time, factor.num, factor.den) + 1); } int64_t FFmpeg_Base::frame_to_pts(AVStream* stream, uint32_t frame_no) const { int64_t start_time = (stream->start_time != AV_NOPTS_VALUE) ? stream->start_time : 0; AVRational factor = av_mul_q(stream->avg_frame_rate, stream->time_base); return static_cast(av_rescale(frame_no - 1, factor.den, factor.num) + start_time); } int FFmpeg_Base::get_channels(const AVCodecParameters *codecpar) const { #if LAVU_DEP_OLD_CHANNEL_LAYOUT return codecpar->ch_layout.nb_channels; #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT return codecpar->channels; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT } void FFmpeg_Base::set_channels(AVCodecParameters *codecpar_out, const AVCodecParameters *codecpar_in) const { #if LAVU_DEP_OLD_CHANNEL_LAYOUT codecpar_out->ch_layout.nb_channels = codecpar_in->ch_layout.nb_channels; #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT codecpar_out->channels = codecpar_in->channels; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT } int FFmpeg_Base::get_channels(const AVCodecContext *codec_ctx) const { #if LAVU_DEP_OLD_CHANNEL_LAYOUT return codec_ctx->ch_layout.nb_channels; #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT return codec_ctx->channels; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT } void FFmpeg_Base::set_channels(AVCodecContext *codec_ctx_out, const AVCodecContext *codec_ctx_in) const { #if LAVU_DEP_OLD_CHANNEL_LAYOUT codec_ctx_out->ch_layout.nb_channels= codec_ctx_in->ch_layout.nb_channels; #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT codec_ctx_out->channels = codec_ctx_in->channels; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT } void FFmpeg_Base::set_channels(AVCodecContext *codec_ctx_out, int channels) const { #if LAVU_DEP_OLD_CHANNEL_LAYOUT codec_ctx_out->ch_layout.nb_channels = channels; #else // !LAVU_DEP_OLD_CHANNEL_LAYOUT codec_ctx_out->channels = channels; #endif // !LAVU_DEP_OLD_CHANNEL_LAYOUT } // See... // // https://en.wikipedia.org/wiki/SubStation_Alpha // https://datatracker.ietf.org/doc/html/draft-ietf-cellar-codec-02 // https://fileformats.fandom.com/wiki/SubStation_Alpha int FFmpeg_Base::get_script_info(AVCodecContext *codec_ctx, int play_res_x, int play_res_y, const char *font, int font_size, int primary_color, int secondary_color, int outline_color, int back_color, int bold, int italic, int underline, int border_style, int alignment) const { const char *format = "[Script Info]\r\n" // "; Script generated by ffmpegfs " FFMPEFS_VERSION "\r\n" // "; https://github.com/nschlia/ffmpegfs\r\n" // "ScriptType: v4.00+\r\n" // "PlayResX: %d\r\n" // "PlayResY: %d\r\n" // "ScaledBorderAndShadow: yes\r\n" // // Some other tags... //"Title: NAME (Language)\r\n" // //"Original Script: ???\r\n" // //"Script Updated By: version 2.8.01\r\n" // //"Collisions: Normal\r\n" // //"PlayDepth: 0\r\n" // //"Timer: 100,0000\r\n" // //"Video Aspect Ratio: 0\r\n" // //"Video Zoom: 6\r\n" // //"Video Position: 0\r\n" // "\r\n" // "[V4+ Styles]\r\n" // "Format: " // "Name, " // "Fontname, Fontsize, " // "PrimaryColour, SecondaryColour, OutlineColour, BackColour, " // "Bold, Italic, Underline, StrikeOut, " // "ScaleX, ScaleY, " // "Spacing, Angle, " // "BorderStyle, Outline, Shadow, " // "Alignment, MarginL, MarginR, MarginV, " // "Encoding\r\n" // "Style: " // "Default," // Name "%s,%d," // Font{name,size} "&H%x,&H%x,&H%x,&H%x," // {Primary,Secondary,Outline,Back}Colour "%d,%d,%d,0," // Bold, Italic, Underline, StrikeOut "100,100," // Scale{X,Y} "0,0," // Spacing, Angle "%d,1,0," // BorderStyle, Outline, Shadow "%d,10,10,10," // Alignment, Margin[LRV] "0\r\n" // Encoding "\r\n" // "[Events]\r\n" // "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n"; size_t size = static_cast(snprintf(nullptr, 0, format, play_res_x, play_res_y, font, font_size, primary_color, secondary_color, outline_color, back_color, -bold, -italic, -underline, border_style, alignment)) + 1; // Extra space for '\0' codec_ctx->subtitle_header = reinterpret_cast(av_malloc(size + 1)); if (codec_ctx->subtitle_header == nullptr) { return AVERROR(ENOMEM); } snprintf(reinterpret_cast(codec_ctx->subtitle_header), size, format, play_res_x, play_res_y, font, font_size, primary_color, secondary_color, outline_color, back_color, -bold, -italic, -underline, border_style, alignment); codec_ctx->subtitle_header_size = static_cast(size); return 0; } ffmpegfs-2.50/src/ffmpeg_frame.cc0000664000175000017500000000730015177713600012443 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_frame.cc * @brief FFmpeg_Frame class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifdef __cplusplus extern "C" { #endif // Disable annoying warnings outside our code #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wsign-conversion" #include #pragma GCC diagnostic pop #ifdef __cplusplus } #endif #include "ffmpeg_utils.h" #include "ffmpeg_frame.h" FFmpeg_Frame::FFmpeg_Frame(int stream_index) : m_frame(av_frame_alloc()), m_res(0), m_stream_idx(stream_index) { m_res = (m_frame != nullptr) ? 0 : AVERROR(ENOMEM); } FFmpeg_Frame::FFmpeg_Frame(const FFmpeg_Frame& frame) : m_frame(nullptr), m_res(0), m_stream_idx(frame.m_stream_idx) { if (frame.m_frame != nullptr) { m_frame = av_frame_clone(frame.m_frame); m_res = (m_frame != nullptr) ? 0 : AVERROR(ENOMEM); } else { m_res = AVERROR(EINVAL); } } FFmpeg_Frame::FFmpeg_Frame(const AVFrame * frame) : m_frame(nullptr), m_res(0), m_stream_idx(INVALID_STREAM) { if (frame != nullptr) { m_frame = av_frame_clone(frame); m_res = (m_frame != nullptr) ? 0 : AVERROR(ENOMEM); } else { m_res = AVERROR(EINVAL); } } FFmpeg_Frame::~FFmpeg_Frame() { free(); } AVFrame* FFmpeg_Frame::clone() { return av_frame_clone(m_frame); } void FFmpeg_Frame::unref() { if (m_frame != nullptr) { av_frame_unref(m_frame); } } void FFmpeg_Frame::free() { if (m_frame != nullptr) { av_frame_free(&m_frame); m_frame = nullptr; } } int FFmpeg_Frame::res() const { return m_res; } AVFrame* FFmpeg_Frame::get() { return m_frame; } FFmpeg_Frame::operator AVFrame*() { return m_frame; } FFmpeg_Frame::operator const AVFrame*() const { return m_frame; } AVFrame* FFmpeg_Frame::operator->() { return m_frame; } FFmpeg_Frame& FFmpeg_Frame::operator=(const FFmpeg_Frame & frame) noexcept { // Do self assignment check if (this != &frame && m_frame != frame.m_frame) { AVFrame *new_frame = av_frame_clone(frame.m_frame); free(); m_frame = new_frame; m_stream_idx = frame.m_stream_idx; m_res = (m_frame != nullptr) ? 0 : AVERROR(ENOMEM); } return *this; } FFmpeg_Frame& FFmpeg_Frame::operator=(const AVFrame * frame) noexcept { if (m_frame != frame) { AVFrame *new_frame = av_frame_clone(frame); free(); m_frame = new_frame; m_stream_idx = INVALID_STREAM; m_res = (m_frame != nullptr) ? 0 : AVERROR(ENOMEM); } return *this; } ffmpegfs-2.50/src/ffmpeg_audiofifo.h0000664000175000017500000001063315200152616013152 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_audiofifo.h * @brief FFmpeg AVAudioFifo RAII wrapper. * * @ingroup ffmpegfs */ #ifndef FFMPEG_AUDIOFIFO_H #define FFMPEG_AUDIOFIFO_H #pragma once #include "ffmpeg_utils.h" struct AVAudioFifo; /** * @brief RAII wrapper for AVAudioFifo. * * Owns an AVAudioFifo allocated with av_audio_fifo_alloc() and releases it * with av_audio_fifo_free(). The wrapper is movable but not copyable. */ class FFmpeg_AudioFifo { public: /** * @brief Construct an empty FIFO wrapper. */ FFmpeg_AudioFifo(); /** * @brief Construct and allocate an audio FIFO. * @param[in] sample_fmt Sample format stored in the FIFO. * @param[in] channels Number of audio channels. * @param[in] nb_samples Initial allocation size in samples. */ FFmpeg_AudioFifo(AVSampleFormat sample_fmt, int channels, int nb_samples = 1); /** * @brief Release the owned FIFO, if any. */ ~FFmpeg_AudioFifo(); FFmpeg_AudioFifo(const FFmpeg_AudioFifo&) = delete; FFmpeg_AudioFifo& operator=(const FFmpeg_AudioFifo&) = delete; /** * @brief Move-construct a FIFO wrapper. * @param[in,out] fifo Source wrapper whose FIFO ownership is transferred. */ FFmpeg_AudioFifo(FFmpeg_AudioFifo&& fifo) noexcept; /** * @brief Move-assign a FIFO wrapper. * @param[in,out] fifo Source wrapper whose FIFO ownership is transferred. * @return Reference to this wrapper. */ FFmpeg_AudioFifo& operator=(FFmpeg_AudioFifo&& fifo) noexcept; /** * @brief Allocate a new audio FIFO, replacing any existing one. * @param[in] sample_fmt Sample format stored in the FIFO. * @param[in] channels Number of audio channels. * @param[in] nb_samples Initial allocation size in samples. * @return 0 on success, or a negative FFmpeg error code. */ int alloc(AVSampleFormat sample_fmt, int channels, int nb_samples = 1); /** * @brief Free the owned FIFO and reset the wrapper to empty. */ void reset(); /** * @brief Check whether the wrapper currently owns a FIFO. * @return true if no FIFO is owned, false otherwise. */ bool empty() const; /** * @brief Return the number of samples currently stored in the FIFO. * @return Number of queued samples, or 0 if the wrapper is empty. */ int size() const; /** * @brief Resize the FIFO allocation. * @param[in] nb_samples New allocation size in samples. * @return 0 on success, or a negative FFmpeg error code. */ int realloc(int nb_samples); /** * @brief Write audio samples into the FIFO. * @param[in] data Per-channel sample buffers as expected by FFmpeg. * @param[in] nb_samples Number of samples to write. * @return Number of samples written, or a negative FFmpeg error code. */ int write(void **data, int nb_samples); /** * @brief Read audio samples from the FIFO. * @param[out] data Per-channel destination buffers as expected by FFmpeg. * @param[in] nb_samples Number of samples to read. * @return Number of samples read, or a negative FFmpeg error code. */ int read(void **data, int nb_samples); /** * @brief Get the owned FFmpeg FIFO pointer. * @return Mutable AVAudioFifo pointer, or nullptr if empty. */ AVAudioFifo* get(); /** * @brief Get the owned FFmpeg FIFO pointer. * @return Const AVAudioFifo pointer, or nullptr if empty. */ const AVAudioFifo* get() const; /** * @brief Release ownership without freeing the FIFO. * @return Previously owned AVAudioFifo pointer, or nullptr if empty. */ AVAudioFifo* release(); /** * @brief Convert to the underlying mutable FIFO pointer. */ operator AVAudioFifo*(); /** * @brief Convert to the underlying const FIFO pointer. */ operator const AVAudioFifo*() const; private: AVAudioFifo * m_fifo; /**< @brief Pointer to underlying AVAudioFifo. */ }; #endif // FFMPEG_AUDIOFIFO_H ffmpegfs-2.50/src/cuesheetparser.h0000664000175000017500000000377015177713600012720 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cuesheetparser.h * @brief Clue sheet parser * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef CUESHEETPARSER_H #define CUESHEETPARSER_H #pragma once #include #define TRACKDIR "tracks" ///<* Extension of virtual tracks directory struct AVFormatContext; /** * @brief Get number of titles in cue sheet * @param[in] filename - Path to check * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @note buf and filler can be nullptr. In that case the call will run faster, so these parameters should only be passed if to be filled in. * @return -errno or number or titles in cue sheet */ int check_cuesheet(const std::string & filename, void *buf = nullptr, fuse_fill_dir_t filler = nullptr); #endif // CUESHEETPARSER_H ffmpegfs-2.50/src/blurayparser.h0000664000175000017500000000373515177713600012412 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file blurayparser.h * @brief Blu-ray parser * * This is only available if built with -DUSE_LIBBLURAY parameter. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef BLURAYPARSER_H #define BLURAYPARSER_H #pragma once #ifdef USE_LIBBLURAY #include /** * @brief Get number of titles on Blu-ray. * @param[in] path - Path to check * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @note buf and filler can be nullptr. In that case, the call will run faster, so these parameters should only be passed if they are to be filled in. * @return -errno or number of titles on Blu-ray. */ int check_bluray(const std::string & path, void *buf = nullptr, fuse_fill_dir_t filler = nullptr); #endif // USE_LIBBLURAY #endif // BLURAYPARSER_H ffmpegfs-2.50/src/ffmpeg_swscontext.h0000664000175000017500000000560215200152616013426 /* * This file is part of FFmpegfs. * * FFmpegfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ /** * @file ffmpeg_swscontext.h * @brief FFmpeg SwsContext RAII wrapper. * * @ingroup ffmpegfs */ #ifndef FFMPEG_SWSCONTEXT_H #define FFMPEG_SWSCONTEXT_H struct SwsContext; /** * @brief RAII wrapper for SwsContext. * * Owns a libswscale context and releases it with sws_freeContext(). The wrapper * is movable but not copyable. */ class FFmpeg_SwsContext { public: /** * @brief Construct an empty scaler-context wrapper. */ FFmpeg_SwsContext(); /** * @brief Construct a wrapper taking ownership of an existing context. * @param[in] ctx Context pointer to take ownership of, or nullptr. */ explicit FFmpeg_SwsContext(SwsContext *ctx); /** * @brief Release the owned scaler context, if any. */ ~FFmpeg_SwsContext(); FFmpeg_SwsContext(const FFmpeg_SwsContext&) = delete; FFmpeg_SwsContext& operator=(const FFmpeg_SwsContext&) = delete; /** * @brief Move-construct a scaler-context wrapper. * @param[in,out] ctx Source wrapper whose context ownership is transferred. */ FFmpeg_SwsContext(FFmpeg_SwsContext&& ctx) noexcept; /** * @brief Move-assign a scaler-context wrapper. * @param[in,out] ctx Source wrapper whose context ownership is transferred. * @return Reference to this wrapper. */ FFmpeg_SwsContext& operator=(FFmpeg_SwsContext&& ctx) noexcept; /** * @brief Get the owned FFmpeg scaler context pointer. * @return SwsContext pointer, or nullptr if empty. */ SwsContext *get() const; /** * @brief Release ownership without freeing the scaler context. * @return Previously owned SwsContext pointer, or nullptr if empty. */ SwsContext *release(); /** * @brief Replace the owned context. * @param[in] ctx New context pointer to own, or nullptr to only reset. * @return true if a previous context was freed, false otherwise. */ bool reset(SwsContext *ctx = nullptr); /** * @brief Check whether the wrapper currently owns a scaler context. * @return true if no context is owned, false otherwise. */ bool empty() const; /** * @brief Check whether the wrapper owns a valid context. */ explicit operator bool() const; /** * @brief Convert to the underlying scaler context pointer. */ operator SwsContext *() const; /** * @brief Access members of the underlying scaler context. * @return SwsContext pointer. */ SwsContext *operator->() const; private: SwsContext *m_ctx; /**< @brief Pointer to underlying SwsContext. */ }; #endif // FFMPEG_SWSCONTEXT_H ffmpegfs-2.50/src/ffmpegfs.h0000664000175000017500000005275515215723104011473 /* * Copyright (C) 2006-2008 David Collett * Copyright (C) 2008-2013 K. Henriksson * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ #ifndef FFMPEGFS_H #define FFMPEGFS_H #pragma once /** * @file ffmpegfs.h * @ingroup ffmpegfs * @brief Main include for FFmpegfs project * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2006-2008 David Collett @n * Copyright (C) 2008-2013 K. Henriksson @n * Copyright (C) 2017-2026 FFmpeg support by Norbert Schlia (nschlia@oblivion-software.de) */ /** @mainpage FFmpegfs FUSE Filesystem * * @section ffmpeg_introduction Introduction to FFmpegfs * * FFmpegfs is a read-only FUSE filesystem which transcodes various audio and video formats to MP4, WebM, * and many more on the fly when opened and read using the FFmpeg library, thus supporting a multitude of * input formats and a variety of common output formats. * * This allows access to a multi-media file collection with software and/or hardware which only understands * one of the supported output formats, or transcodes files through simple drag-and-drop in a file browser. * * FFmpegfs can be downloaded [from Github](https://github.com/nschlia/ffmpegfs). * * @section ffmpeg_documentation FFmpegfs Documentation * * @li [Introduction](README.md) * @li [**Man Pages**](manpages.html) * @li [Features In Detail](FEATURES.md) * @li [Revision History](HISTORY.md) * @li [Installation Instructions](INSTALL.md) * @li [Fixing Problems](PROBLEMS.md) * @li @ref todo * @li @ref bug * @li @ref ffmpegfs_NEWS * @li @ref ffmpegfs_TODO * * @section ffmpeg_licences Licenses * * @li @ref ffmpegfs_COPYING * @li @ref ffmpegfs_COPYING.DOC * @li @ref ffmpegfs_COPYING.CC0 * * @section ffmpegfs_external External Links * * @li [**FFmpegfs on Github**](https://github.com/nschlia/ffmpegfs) * @li [**FFmpeg Homepages**](https://www.ffmpeg.org/) */ /** * @page ffmpegfs_NEWS Online News List * * \include NEWS * * @page ffmpegfs_TODO Online TODO List * * \include TODO * * @page ffmpegfs_COPYING General License * * \include COPYING * * @page ffmpegfs_COPYING.DOC Documentation License * * \include COPYING.DOC * * @page ffmpegfs_COPYING.CC0 Demo Files License * * \include COPYING.CC0 */ #define FUSE_USE_VERSION 30 /**< @brief Requested minimum FUSE version */ #ifdef __cplusplus extern "C" { #endif #include #ifdef __cplusplus } #endif #include #include "ffmpeg_utils.h" #include "fileio.h" /** * * @brief Hardware acceleration types. */ enum class HWACCELAPI { NONE, VAAPI, /**< Intel: VAAPI */ MMAL, /**< Raspberry: MMAL */ OMX, /**< Raspberry: OpenMAX */ // Additional formats #if 0 CUDA, /**< Nividia: CUDA to be added */ V4L2M2M, /**< v4l2 mem to mem to be added */ VDPAU, /**< VDPAU to be added */ QSV, /**< QSV to be added */ OPENCL, /**< OPENCL to be added */ #if HAVE_VULKAN_HWACCEL VULKAN, /**< VULKAN to be added */ #endif // HAVE_VULKAN_HWACCEL #if __APPLE__ // MacOS acceleration APIs not supported VIDEOTOOLBOX, /**< VIDEOTOOLBOX not supported */ #endif #if __ANDROID__ // Android acceleration APIs not supported MEDIACODEC, /**< MediaCodec API not supported */ #endif #if _WIN32 // Windows acceleration APIs not supported DRM, /**< DRM not supported */ DXVA2, /**< DXVA2 not supported */ D3D11VA, /**< D3D11VA not supported */ #endif #endif }; typedef std::multimap HWACCEL_BLOCKED_MAP; /**< @brief Map command line option to AVCodecID */ extern FFMPEGFS_FORMAT_ARR ffmpeg_format; /**< @brief Two FFmpegfs_Format infos, 0: video file, 1: audio file */ /** * @brief Global program parameters */ extern struct FFMPEGFS_PARAMS { FFMPEGFS_PARAMS(); /** * @brief Make copy from other FFMPEGFS_PARAMS object. * @param[in] other - Reference to source FFmpeg_Frame object. */ FFMPEGFS_PARAMS(const FFMPEGFS_PARAMS & other); ~FFMPEGFS_PARAMS(); // Do not make virtual, breaks fuse API compatibility! /** * @brief Check for smart transcode mode * @return true if smart transcode is active, false if not */ bool smart_transcode() const; /** * @brief Get FFmpegfs_Format for a virtual file. * @param[in] virtualfile - VIRTUALFILE struct of a file. * @return On success, returns pointer to format. On error, returns nullptr. */ const FFmpegfs_Format * current_format(LPCVIRTUALFILE virtualfile) const; /** * @brief Make copy from other FFMPEGFS_PARAMS object. * @param[in] other - Reference to source FFmpeg_Frame object. * @return Reference to new FFmpeg_Frame object. */ FFMPEGFS_PARAMS& operator=(const FFMPEGFS_PARAMS & other) noexcept; // Paths std::string m_basepath; /**< @brief Base path: Files from this directory (including all sub directories) will be mapped to m_mountpath. */ std::string m_mountpath; /**< @brief Mount path: Files from m_mountpath will be mapped to this directory. */ // Output type AVCodecID m_audio_codec; /**< @brief Either AV_CODEC_ID_NONE for default, or a user selected codec */ AVCodecID m_video_codec; /**< @brief Either AV_CODEC_ID_NONE for default, or a user selected codec */ AUTOCOPY m_autocopy; /**< @brief Copy streams if codec matches */ RECODESAME m_recodesame; /**< @brief Recode to same format options */ PROFILE m_profile; /**< @brief Target profile: Firefox, MS Edge/IE or other */ PRORESLEVEL m_level; /**< @brief Level, currently proxy/hq/lt/HQ (ProRes only) */ // Audio BITRATE m_audiobitrate; /**< @brief Output audio bit rate (bits per second) */ int m_audiosamplerate; /**< @brief Output audio sample rate (in Hz) */ int m_audiochannels; /**< @brief Max. number of audio channels */ SAMPLE_FMT m_sample_fmt; /**< @brief Sample format */ // Video BITRATE m_videobitrate; /**< @brief Output video bit rate (bits per second) */ int m_videowidth; /**< @brief Output video width */ int m_videoheight; /**< @brief Output video height */ int m_deinterlace; /**< @brief 1: deinterlace video, 0: no deinterlace */ // HLS Options int64_t m_segment_duration; /**< @brief Duration of one HLS segment file, in AV_TIME_BASE fractional seconds. */ int64_t m_min_seek_time_diff; /**< @brief Minimum time diff from current to next requested segment to perform a seek, in AV_TIME_BASE fractional seconds. */ // Hardware acceleration HWACCELAPI m_hwaccel_enc_API; /**< @brief Encoder API */ AVHWDeviceType m_hwaccel_enc_device_type; /**< @brief Enable hardware acceleration buffering for encoder */ std::string m_hwaccel_enc_device; /**< @brief Encoder device. May be AUTO to auto detect or empty */ HWACCELAPI m_hwaccel_dec_API; /**< @brief Decoder API */ AVHWDeviceType m_hwaccel_dec_device_type; /**< @brief Enable hardware acceleration buffering for decoder */ std::string m_hwaccel_dec_device; /**< @brief Decoder device. May be AUTO to auto detect or empty */ HWACCEL_BLOCKED_MAP* m_hwaccel_dec_blocked; /**< @brief List of blocked decoders and optional profiles */ // Subtitles int m_no_subtitles; /**< @brief 0: allow subtitles, 1: do no transcode subtitles */ // Album arts int m_noalbumarts; /**< @brief Skip album arts */ // Virtual script int m_enablescript; /**< @brief Enable virtual script */ std::string m_scriptfile; /**< @brief Script name */ std::string m_scriptsource; /**< @brief Source script */ // FFmpegfs options int m_debug; /**< @brief Debug mode (stay in foreground */ std::string m_log_maxlevel; /**< @brief Max. log level */ int m_log_stderr; /**< @brief Log output to standard error */ int m_log_syslog; /**< @brief Log output to system log */ std::string m_logfile; /**< @brief Output filename if logging to file */ // Background recoding/caching time_t m_expiry_time; /**< @brief Time (seconds) after which an cache entry is deleted */ time_t m_max_inactive_suspend; /**< @brief Time (seconds) that must elapse without access until transcoding is suspended */ time_t m_max_inactive_abort; /**< @brief Time (seconds) that must elapse without access until transcoding is aborted */ time_t m_prebuffer_time; /**< @brief Playing time that will be decoded before the output can be accessed */ size_t m_prebuffer_size; /**< @brief Number of bytes that will be decoded before the output can be accessed */ size_t m_max_cache_size; /**< @brief Max. cache size in MB. When exceeded, oldest entries will be pruned */ size_t m_min_diskspace; /**< @brief Min. diskspace required for cache */ std::string m_cachepath; /**< @brief Disk cache path, defaults to $XDG_CACHE_HOME */ int m_disable_cache; /**< @brief Disable cache */ time_t m_cache_maintenance; /**< @brief Prune timer interval */ int m_prune_cache; /**< @brief Prune cache immediately */ int m_clear_cache; /**< @brief Clear cache on start up */ unsigned int m_max_threads; /**< @brief Max. number of recoder threads */ // Miscellanous options int m_decoding_errors; /**< @brief Break transcoding on decoding error */ int m_min_dvd_chapter_duration; /**< @brief Min. DVD chapter duration. Shorter chapters will be ignored. */ int m_oldnamescheme; /**< @brief Use old output name scheme, can create duplicate filenames */ std::unique_ptr m_include_extensions; /**< @brief Set of extensions to include. If empty, include all. Must be a pointer as the fuse API cannot handle advanced c++ objects. */ std::unique_ptr m_hide_extensions; /**< @brief Set of extensions to block/hide. Must be a pointer as the fuse API cannot handle advanced c++ objects. */ // Experimental options int m_win_smb_fix; /**< @brief Experimental Windows fix for access to EOF at file open */ } params; /**< @brief Command line parameters */ class Cache_Entry; extern bool docker_client; /**< @brief True if running inside a Docker container */ /** * @brief Fuse operations struct */ extern fuse_operations ffmpegfs_ops; class thread_pool; /** * @brief Thread pool object */ extern std::unique_ptr tp; /** * @brief Initialise FUSE operation structure. */ void init_fuse_ops(); #endif /** * @brief Build the base directory used for the transcoder cache. * * Uses the configured cache path when one was supplied. Otherwise root uses * `/var/cache`, while regular users use `$XDG_CACHE_HOME` or `~/.cache`. * The package name is appended and the returned path always ends with a path * separator. * * @param[out] path Receives the complete transcoder cache directory path. */ void transcoder_cache_path(std::string *path); /** * @brief Initialise transcoder, create cache. * @return Returns true on success; false on error. Check errno for details. */ bool transcoder_init(); /** * @brief Free transcoder. */ void transcoder_free(); /** * @brief Run cache maintenance. * @return Returns true on success; false on error. Check errno for details. */ bool transcoder_cache_maintenance(); /** * @brief Clear transcoder cache. * @return Returns true on success; false on error. Check errno for details. */ bool transcoder_cache_clear(); /** * @brief Add new virtual file to internal list. * * For Blu-ray/DVD/VCD, actually, no physical input file exists, so virtual and * origfile are the same. * * The input file is handled by the BlurayIO or VcdIO classes. * * For cue sheets, the original (huge) input file is used. Start positions are * sought; end positions are reported as EOF. * * @param[in] type - Type of virtual file. * @param[in] virtfile - Name of virtual file. * @param[in] stbuf - stat buffer with file size, time etc. * @param[in] flags - One of the VIRTUALFLAG_* flags to control the detailed behaviour. * @return Returns constant pointer to VIRTUALFILE object of file, nullptr if not found */ LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string & virtfile, const struct stat *stbuf, int flags = VIRTUALFLAG_NONE); /** * @brief Add new virtual file to internal list. If the file already exists, it will be updated. * @param[in] type - Type of virtual file. * @param[in] virtfile - Name of virtual file. * @param[in] origfile - Original file name. * @param[in] stbuf - stat buffer with file size, time etc. * @param[in] flags - One of the VIRTUALFLAG_* flags to control the detailed behaviour. * @return Returns constant pointer to VIRTUALFILE object of file, nullptr if not found */ LPVIRTUALFILE insert_file(VIRTUALTYPE type, const std::string &virtfile, const std::string & origfile, const struct stat *stbuf, int flags = VIRTUALFLAG_NONE); /** * @brief Add new virtual directory to the internal list. If the file already exists, it will be updated. * @param[in] type - Type of virtual file. * @param[in] virtdir - Name of virtual directory. * @param[in] stbuf - stat buffer with file size, time etc. * @param[in] flags - One of the VIRTUALFLAG_* flags to control the detailed behaviour. * @return Returns constant pointer to VIRTUALFILE object of file, nullptr if not found */ LPVIRTUALFILE insert_dir(VIRTUALTYPE type, const std::string & virtdir, const struct stat * stbuf, int flags = VIRTUALFLAG_NONE); /** * @brief Find file in cache. * @param[in] virtfile - Virtual filename and path of file to find. * @return If found, returns VIRTUALFILE object, if not found returns nullptr. */ LPVIRTUALFILE find_file(const std::string &virtfile); /** * @brief Look for the file in the cache. * @param[in] origfile - Filename and path of file to find. * @return If found, returns VIRTUALFILE object, if not found returns nullptr. */ LPVIRTUALFILE find_file_from_orig(const std::string &origfile); /** * @brief Check if the path has already been parsed. * Only useful if for DVD, Blu-ray or VCD where it is guaranteed that all files have been parsed whenever the directory is in the hash. * @param[in] path - Path to parse. * @return Returns true if path was found; false if not. */ bool check_path(const std::string & path); /** * @brief Load a path with virtual files for FUSE. * @param[in] path - Physical path to load. * @param[in] statbuf - stat buffer to load. * @param[in] buf - FUSE buffer to fill. * @param[in] filler - Filler function. * @return Returns number of files found. */ int load_path(const std::string & path, const struct stat *statbuf, void *buf, fuse_fill_dir_t filler); /** * @brief Given the destination (post-transcode) file name, determine the parent of the file to be transcoded. * @param[in] origpath - The original file * @return Returns contstant pointer to VIRTUALFILE object of file, nullptr if not found */ LPVIRTUALFILE find_original(const std::string & origpath); /** * @brief Given the destination (post-transcode) file name, determine the name of the original file to be transcoded. * @param[inout] filepath - Input the original file, output name of transcoded file. * @return Returns contstant pointer to VIRTUALFILE object of file, nullptr if not found */ LPVIRTUALFILE find_original(std::string *filepath); /** * @brief Given the destination (post-transcode) file name, determine the parent of the file to be transcoded. * @param[in] origpath - The original file * @return Returns contstant pointer to VIRTUALFILE object of file, nullptr if not found */ LPVIRTUALFILE find_parent(const std::string & origpath); /** * @brief Convert SAMPLE_FMT enum to human readable text. * @return SAMPLE_FMT enum as text or "INVALID" if not known. */ std::string get_sampleformat_text(SAMPLE_FMT sample_fmt); /** * @brief Convert AVCodecID enum for audio codec to human readable text. * @param[in] audio_codec - AVCodecID enum value to convert. * @return AVCodecID as text or "INVALID" if not known. */ std::string get_audio_codec_text(AVCodecID audio_codec); /** * @brief Convert AVCodecID enum for video codec to human readable text. * @param[in] video_codec - AVCodecID enum value to convert. * @return AVCodecID as text or "INVALID" if not known. */ std::string get_video_codec_text(AVCodecID video_codec); /** * @brief Convert AUTOCOPY enum to human readable text. * @param[in] autocopy - AUTOCOPY enum value to convert. * @return AUTOCOPY enum as text or "INVALID" if not known. */ std::string get_autocopy_text(AUTOCOPY autocopy); /** * @brief Convert RECODESAME enum to human readable text. * @param[in] recode - RECODESAME enum value to convert. * @return RECODESAME enum as text or "INVALID" if not known. */ std::string get_recodesame_text(RECODESAME recode); /** * @brief Convert PROFILE enum to human readable text. * @param[in] profile - PROFILE enum value to convert. * @return PROFILE enum as text or "INVALID" if not known. */ std::string get_profile_text(PROFILE profile); /** * @brief Convert PRORESLEVEL enum to human readable text. * @param[in] level - PRORESLEVEL enum value to convert. * @return PRORESLEVEL enum as text or "INVALID" if not known. */ std::string get_level_text(PRORESLEVEL level); /** * @brief Get the selected hardware acceleration as text. * @param[in] hwaccel_API - Hardware acceleration buffering API. * @return Hardware acceleration API as string. */ std::string get_hwaccel_API_text(HWACCELAPI hwaccel_API); /** * @brief Check if codec_id and the optional profile are in the block list. * @param[in] codec_id - Codec ID to check * @param[in] profile - Profile to check. Set to FF_PROFILE_UNKOWN to ignore. * @return Returns true if codec is in block list, false if not. */ bool check_hwaccel_dec_blocked(AVCodecID codec_id, int profile); /** * @brief Wrapper to the Fuse filler function. * @param[inout] buf - The buffer passed to the readdir() operation. May be nullptr. * @param[in] filler - Function pointer to the Fuse update function. May be nullptr. * @param[in] name - The file name of the directory entry. Do not include the path! * @param[in] stbuf - File attributes, can be nullptr. * @param[in] off - Offset of the next entry or zero. * @return 1 if buffer is full, zero otherwise or if buf or filler is nullptr. */ int add_fuse_entry(void *buf, fuse_fill_dir_t filler, const std::string & name, const struct stat *stbuf, off_t off); /** * @brief Make dot and double dot entries for a virtual directory. * @param[inout] buf - The buffer passed to the readdir() operation. May be nullptr. * @param[in] filler - Function pointer to the Fuse update function. May be nullptr. * @param[in] stbuf - File attributes. May be nullptr. * @param[in] off - Offset of the next entry or zero. * @return Returns 1 if the buffer is full, zero otherwise. If buf or filler is nullptr, returns zero. */ int add_dotdot(void *buf, fuse_fill_dir_t filler, const struct stat *stbuf, off_t off); ffmpegfs-2.50/src/cache_entry.h0000664000175000017500000002352315177713600012160 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cache_entry.h * @brief %Cache entry * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef CACHE_ENTRY_H #define CACHE_ENTRY_H #pragma once #include "cache.h" #include "id3v1tag.h" #include class Buffer; /** * @brief The #Cache_Entry class */ class Cache_Entry { private: /** * @brief Create Cache_Entry object. * @param[in] owner - Cache object of owner. * @param[in] virtualfile - Requesting virtual file. */ explicit Cache_Entry(Cache *owner, LPVIRTUALFILE virtualfile); /** * @brief Copy constructor declared deleted, should use create to get this object to maintain reference count. */ Cache_Entry(Cache_Entry &) = delete; /** * @brief Destroy Cache_Entry object. */ virtual ~Cache_Entry(); public: /** * @brief operator = declared deleted, should use create to get this object to maintain reference count. * @param[in] other - Assignment object * @return Pointer to this */ Cache_Entry & operator= (Cache_Entry const & other) = delete; /** * @brief Create a new Cache_Entry object. * @param[in] owner - Cache object of owner. * @param[in] virtualfile - Requesting virtual file. * @return On success, returns a Cache_Entry object; on error (out of memory) returns a nullptr */ static Cache_Entry * create(Cache *owner, LPVIRTUALFILE virtualfile); /** * @brief Destroy this Cache_Entry object. * @return true if object was destroyed right away; false if it will be destroyed later (NOT IMPLEMENTED, WILL BE DESTROYED AT ONCE). */ bool destroy(); /** * @brief Open the cache file. * @param[in] create_cache - If true, the cache will be created if it does not yet exist. * @return On success returns true; on error returns false and errno contains the error code. */ bool openio(bool create_cache = true); /** * @brief Flush current memory cache to disk. * @return On success returns true; on error returns false and errno contains the error code. */ bool flush(); /** * @brief Clear the cache entry * @param[in] fetch_file_time - If true, the entry file time will be filled in from the source file. */ void clear(bool fetch_file_time = true); /** @brief Return size of output file, as computed by encoder. * * Returns the file size, either the predicted size (which may be inaccurate) or * the real size (which is only available once the file was completely recoded). * * @return The size of the file. Function never fails. */ size_t size() const; /** * @brief Get the video frame count. * @return On success, returns the number of frames; on error, returns 0 (calculation failed or no video source file). */ uint32_t video_frame_count() const; /** * @brief Get the age of the cache entry. * @return Returns the age of the cache entry in seconds since epoch. */ time_t age() const; /** * @brief Get last access time. * @return Returns last access time in seconds since epoch. */ time_t last_access() const; /** * @brief Check if cache entry expired. * * Checks if entry is older or larger than the limit. * * @return If entry is expired, returns true. */ bool expired() const; /** * @brief Check for decode suspend timeout. * @return Returns true if decoding was suspended. */ bool suspend_timeout() const; /** * @brief Check for decode timeout. * @return Returns true if decoding timed out. */ bool decode_timeout() const; /** * @brief Return source filename. * @return Returns the name of the transcoded file. */ const char * filename() const; /** * @brief Return destination filename. * @return Returns the name of the transcoded file. */ const char * destname() const; /** * @brief Return virtual filename. Same as destination filename, but with virtual (mount) path.. * @return Returns the name of the transcoded file. */ const char * virtname() const; /** * @brief Update last access time. * @param[in] update_database - If true, also persist in SQL database. * @return If update was successful, returns true; returns false on error. */ bool update_access(bool update_database = false); /** * @brief Lock the access mutex. */ void lock(); /** * @brief Unlock the access mutex. */ void unlock(); /** * @brief Get the current reference counter. * @return Returns the current reference counter. */ int ref_count() const; /** * @brief Increment the current reference counter. * @return Returns the current reference counter. */ int inc_refcount(); /** * @brief Decrement the current reference counter. * @return Returns the current reference counter. */ int decr_refcount(); /** * @brief Check if cache entry needs to be recoded */ bool outdated() const; /** * @brief Get the underlying VIRTUALFILE object. * @return Return the underlying VIRTUALFILE object. */ LPVIRTUALFILE virtualfile(); /** * @brief Close the cache entry * @param[in] flags - one of the CACHE_CLOSE_* flags * @return Returns true if entry may be deleted, false if still in use. */ bool closeio(int flags); /** * @brief Update read counter. */ void update_read_count(); /** * @brief Get read counter. * * This is the number of read accesses to the cache entry. * * @return Returns current read counter */ unsigned int read_count() const; /** * @brief Get if cache has been finished. * @return Returns true if cache is finished, false if not. */ bool is_finished() const; /** * @brief Get if cache has been finished, but not completely filled. * @return Returns true if cache is finished, but not completely filled, false if not. */ bool is_finished_incomplete() const; /** * @brief Get if cache has been finished and filled successfully. * @return Returns true if cache is finished successfully, false if not. */ bool is_finished_success() const; /** * @brief Get if cache has been finished and with an error. * @return Returns true if cache is finished with error, false if not. */ bool is_finished_error() const; protected: /** * @brief Close buffer object. * @param[in] flags - one of the CACHE_CLOSE_* flags */ void close_buffer(int flags); /** * @brief Read cache info. * @return On success, returns true; returns false on error. */ bool read_info(); /** * @brief Write cache info. * @return On success, returns true; returns false on error. */ bool write_info(); /** * @brief Delete cache info. * @return On success, returns true; returns false on error. */ bool delete_info(); protected: Cache * m_owner; /**< @brief Owner cache object */ std::recursive_mutex m_mutex; /**< @brief Access mutex */ std::atomic_int m_ref_count; /**< @brief Reference counter */ LPVIRTUALFILE m_virtualfile; /**< @brief Underlying virtual file object */ public: std::unique_ptr m_buffer; /**< @brief Buffer object */ std::atomic_bool m_is_decoding; /**< @brief true while file is decoding */ std::recursive_mutex m_active_mutex; /**< @brief Mutex while thread is active */ std::recursive_mutex m_restart_mutex; /**< @brief Mutex while thread is restarted */ std::atomic_bool m_suspend_timeout; /**< @brief true to temporarly disable read_frame timeout */ CACHE_INFO m_cache_info; /**< @brief Info about cached object */ ID3v1 m_id3v1; /**< @brief ID3v1 structure which is used to send to clients */ std::atomic_uint32_t m_seek_to_no; /**< @brief If not 0, seeks to specified frame */ }; #endif // CACHE_ENTRY_H ffmpegfs-2.50/src/cache_entry.cc0000664000175000017500000003007215201047732012305 /* * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file cache_entry.cc * @brief Cache_Entry class implementation * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "cache_entry.h" #include "ffmpegfs.h" #include "buffer.h" #include "logging.h" #include Cache_Entry::Cache_Entry(Cache *owner, LPVIRTUALFILE virtualfile) : m_owner(owner) , m_ref_count(0) , m_virtualfile(virtualfile) , m_is_decoding(false) , m_suspend_timeout(false) , m_seek_to_no(0) { m_cache_info.m_origfile = virtualfile->m_origfile; m_cache_info.m_destfile = virtualfile->m_destfile; m_cache_info.m_desttype[0] = '\0'; strncat(m_cache_info.m_desttype.data(), params.current_format(virtualfile)->desttype().c_str(), m_cache_info.m_desttype.size() - 1); m_buffer = std::make_unique(); if (m_buffer != nullptr) { m_buffer->openio(virtualfile); } clear(); Logging::trace(filename(), "Created a new cache entry."); } Cache_Entry::~Cache_Entry() { std::unique_lock lock_active_mutex(m_active_mutex); unlock(); Logging::trace(filename(), "Deleted buffer."); } Cache_Entry * Cache_Entry::create(Cache *owner, LPVIRTUALFILE virtualfile) { return new(std::nothrow) Cache_Entry(owner, virtualfile); } bool Cache_Entry::destroy() { delete this; /** @todo Implement delete later mechanism */ return true; /** @todo Return true when deleted, false if kept for delete later */ } void Cache_Entry::clear(bool fetch_file_time /*= true*/) { // Do not touch m_is_decoding here. clear() is also used by an already // running transcoder to remove stale cache contents before a full recode; // resetting the flag in that window allows a second reader to start a // second transcoder for the same cache entry. // Initialise ID3v1.1 tag structure init_id3v1(&m_id3v1); //m_cache_info.m_enable_ismv = params.m_enable_ismv; m_cache_info.m_audiobitrate = params.m_audiobitrate; m_cache_info.m_audiosamplerate = params.m_audiosamplerate; m_cache_info.m_videobitrate = params.m_videobitrate; m_cache_info.m_videowidth = params.m_videowidth; m_cache_info.m_videoheight = params.m_videoheight; m_cache_info.m_deinterlace = params.m_deinterlace; m_cache_info.m_predicted_filesize = 0; m_cache_info.m_encoded_filesize = 0; m_cache_info.m_video_frame_count = 0; m_cache_info.m_result = RESULTCODE::NONE; m_cache_info.m_error = false; m_cache_info.m_errno = 0; m_cache_info.m_averror = 0; m_cache_info.m_access_time = m_cache_info.m_creation_time = time(nullptr); m_cache_info.m_access_count = 0; if (fetch_file_time) { struct stat sb; if (stat(filename(), &sb) == -1) { m_cache_info.m_file_time = 0; m_cache_info.m_file_size = 0; } else { m_cache_info.m_file_time = sb.st_mtime; m_cache_info.m_file_size = static_cast(sb.st_size); } } if (m_buffer != nullptr) { m_buffer->clear(); } } bool Cache_Entry::read_info() { return m_owner->read_info(&m_cache_info); } bool Cache_Entry::write_info() { return m_owner->write_info(&m_cache_info); } bool Cache_Entry::delete_info() { return m_owner->delete_info(filename(), m_cache_info.m_desttype.data()); } bool Cache_Entry::update_access(bool update_database /*= false*/) { m_cache_info.m_access_time = time(nullptr); if (update_database) { return m_owner->write_info(&m_cache_info); } else { return true; } } bool Cache_Entry::openio(bool create_cache /*= true*/) { if (m_buffer == nullptr) { errno = EINVAL; return false; } if (m_ref_count++ > 0) // fetch_and_add { // Already open. This can happen when an entry was first opened only // for metadata (create_cache == false) and the actual HLS/frame-set // buffer is created later when the requested segment is known. if (create_cache && m_buffer->segment_count() == 0) { bool erase_cache = !is_finished(); Logging::trace(filename(), "Initialising cache buffer for an already referenced entry. Result %1 Erasing cache: %2.", static_cast(m_cache_info.m_result), erase_cache); update_access(true); if (m_buffer->init(erase_cache)) { return true; } else { clear(false); return false; } } // Already open return true; } bool erase_cache = !read_info(); // If read_info fails, rebuild cache entry if (!create_cache) { return true; } if (!is_finished()) { // If no database entry found (database is not consistent), // or file was not completely transcoded last time, // simply create a new file. erase_cache = true; } Logging::trace(filename(), "The last transcode was completed. Result %1 Erasing cache: %2.", static_cast(m_cache_info.m_result), erase_cache); // Store access time update_access(true); // Open the cache if (m_buffer->init(erase_cache)) { return true; } else { clear(false); return false; } } void Cache_Entry::close_buffer(int flags) { if (m_buffer->release(flags)) { if (flags) { delete_info(); } } } bool Cache_Entry::closeio(int flags) { write_info(); if (m_buffer == nullptr) { errno = EINVAL; return false; } if (!m_ref_count) { close_buffer(flags); return true; } if (--m_ref_count > 0) // sub_and_fetch { // Just flush to disk flush(); return false; } close_buffer(flags); return true; } bool Cache_Entry::flush() { if (m_buffer == nullptr) { errno = EINVAL; return false; } m_buffer->flush(); //write_info(); return true; } size_t Cache_Entry::size() const { if (m_cache_info.m_encoded_filesize) { return m_cache_info.m_encoded_filesize; } else { if (m_buffer == nullptr) { return m_cache_info.m_predicted_filesize; } else { size_t current_size = m_buffer->buffer_watermark(); return std::max(current_size, m_cache_info.m_predicted_filesize); } } } uint32_t Cache_Entry::video_frame_count() const { return m_cache_info.m_video_frame_count; } time_t Cache_Entry::age() const { return (time(nullptr) - m_cache_info.m_creation_time); } time_t Cache_Entry::last_access() const { return m_cache_info.m_access_time; } bool Cache_Entry::expired() const { return (age() > params.m_expiry_time); } bool Cache_Entry::suspend_timeout() const { return (((time(nullptr) - m_cache_info.m_access_time) >= params.m_max_inactive_suspend) && m_ref_count <= 1); } bool Cache_Entry::decode_timeout() const { return (((time(nullptr) - m_cache_info.m_access_time) >= params.m_max_inactive_abort) && m_ref_count <= 1); } const char * Cache_Entry::filename() const { return (m_virtualfile != nullptr ? m_virtualfile->m_origfile.c_str() : ""); } const char *Cache_Entry::destname() const { return (m_virtualfile != nullptr ? m_virtualfile->m_destfile.c_str() : ""); } const char * Cache_Entry::virtname() const { return (m_virtualfile != nullptr ? m_virtualfile->m_virtfile.c_str() : ""); } void Cache_Entry::lock() { m_mutex.lock(); } void Cache_Entry::unlock() { m_mutex.unlock(); } int Cache_Entry::ref_count() const { return m_ref_count; } int Cache_Entry::inc_refcount() { return m_ref_count++; // fetch_and_add } int Cache_Entry::decr_refcount() { return --m_ref_count; // sub_and_fetch } bool Cache_Entry::outdated() const { struct stat sb; if (m_cache_info.m_audiobitrate != params.m_audiobitrate) { if (m_cache_info.m_audiobitrate) { // Report if old rate is known Logging::debug(filename(), "Triggering re-transcode: Selected audio bitrate changed from %1 to %2.", m_cache_info.m_audiobitrate, params.m_audiobitrate); } return true; } if (m_cache_info.m_audiosamplerate != params.m_audiosamplerate) { if (m_cache_info.m_audiosamplerate) { // Report if old rate is known Logging::debug(filename(), "Triggering re-transcode: Selected audio samplerate changed from %1 to %2.", m_cache_info.m_audiosamplerate, params.m_audiosamplerate); } return true; } if (m_cache_info.m_videobitrate != params.m_videobitrate) { if (m_cache_info.m_videobitrate) { // Report if old rate is known Logging::debug(filename(), "Triggering re-transcode: Selected video bitrate changed from %1 to %2.", m_cache_info.m_audiobitrate, params.m_audiobitrate); } return true; } if (m_cache_info.m_videowidth != params.m_videowidth || m_cache_info.m_videoheight != params.m_videoheight) { if (m_cache_info.m_videowidth && m_cache_info.m_videoheight) { // Report if old dimensions is known Logging::debug(filename(), "Triggering re-transcode: Selected video width/height changed from %1/%2 to %3/%4.", m_cache_info.m_videowidth, m_cache_info.m_videoheight, params.m_videowidth, params.m_videoheight); } return true; } if (m_cache_info.m_deinterlace != (params.m_deinterlace ? true : false)) { Logging::debug(filename(), "Triggering re-transcode: Selected video deinterlace changed from %1 to %2.", m_cache_info.m_deinterlace, params.m_deinterlace); return true; } if (stat(filename(), &sb) != -1) { // If source file exists, check file date/size if (m_cache_info.m_file_time < sb.st_mtime) { Logging::debug(filename(), "Triggering re-transcode: File time has gone forward."); return true; } if (m_cache_info.m_file_size != static_cast(sb.st_size)) { Logging::debug(filename(), "Triggering re-transcode: File size has changed."); return true; } } return false; } LPVIRTUALFILE Cache_Entry::virtualfile() { return m_virtualfile; } void Cache_Entry::update_read_count() { m_cache_info.m_access_count++; } unsigned int Cache_Entry::read_count() const { return m_cache_info.m_access_count; } bool Cache_Entry::is_finished() const { return (m_cache_info.m_result != RESULTCODE::NONE); } bool Cache_Entry::is_finished_incomplete() const { return (m_cache_info.m_result == RESULTCODE::FINISHED_INCOMPLETE); } bool Cache_Entry::is_finished_success() const { return (m_cache_info.m_result == RESULTCODE::FINISHED_SUCCESS); } bool Cache_Entry::is_finished_error() const { return (m_cache_info.m_result == RESULTCODE::FINISHED_ERROR); } ffmpegfs-2.50/src/logging.cc0000664000175000017500000001744215177713600011463 /* * Copyright (C) 2017 Original author K. Henriksson * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file logging.cc * @brief Log facilities implementation * * @ingroup ffmpegfs * * @author K. Henriksson, Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017 Original author K. Henriksson @n * Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #include "logging.h" #include "ffmpegfs.h" #include #include #include #include #include #define COLOUR_BLACK "\033[0;30m" /**< @brief ANSI ESC for black foreground */ #define COLOUR_DARK_GRAY "\033[1;30m" /**< @brief ANSI ESC for dark gray foreground */ #define COLOUR_LIGHT_GRAY "\033[0;37m" /**< @brief ANSI ESC for light gray foreground */ #define COLOUR_RED "\033[0;31m" /**< @brief ANSI ESC for red foreground */ #define COLOUR_LIGHT_RED "\033[1;31m" /**< @brief ANSI ESC for light red foreground */ #define COLOUR_GREEN "\033[0;32m" /**< @brief ANSI ESC for green foreground */ #define COLOUR_LIGHT_GREEN "\033[1;32m" /**< @brief ANSI ESC for light green foreground */ #define COLOUR_BROWN_ORANGE "\033[0;33m" /**< @brief ANSI ESC for brown orange foreground */ #define COLOUR_YELLOW "\033[1;33m" /**< @brief ANSI ESC for yellow foreground */ #define COLOUR_BLUE "\033[0;34m" /**< @brief ANSI ESC for blue foreground */ #define COLOUR_LIGHT_BLUE "\033[1;34m" /**< @brief ANSI ESC for light blue foreground */ #define COLOUR_PURPLE "\033[0;35m" /**< @brief ANSI ESC for purple foreground */ #define COLOUR_LIGHT_PURPLE "\033[1;35m" /**< @brief ANSI ESC for light purple foreground */ #define COLOUR_CYAN "\033[0;36m" /**< @brief ANSI ESC for cyan foreground */ #define COLOUR_LIGHT_CYAN "\033[1;36m" /**< @brief ANSI ESC for light cyan foreground */ #define COLOUR_WHITE "\033[1;37m" /**< @brief ANSI ESC for white foreground */ #define COLOUR_RESET "\033[0m" /**< @brief ANSI ESC to reset the foreground colour */ std::unique_ptr Logging::m_logging; std::recursive_mutex Logging::m_mutex; const std::map Logging::Logger::m_syslog_level_map = { { LOGERROR, LOG_ERR }, { LOGWARN, LOG_WARNING }, { LOGINFO, LOG_INFO }, { LOGDEBUG, LOG_DEBUG }, { LOGTRACE, LOG_DEBUG }, }; const std::map Logging::Logger::m_level_name_map = { { LOGERROR, "ERROR " }, { LOGWARN, "WARNING" }, { LOGINFO, "INFO " }, { LOGDEBUG, "DEBUG " }, { LOGTRACE, "TRACE " }, }; const std::map Logging::Logger::m_level_colour_map = { { LOGERROR, COLOUR_RED }, { LOGWARN, COLOUR_YELLOW }, { LOGINFO, COLOUR_WHITE }, { LOGDEBUG, COLOUR_GREEN }, { LOGTRACE, COLOUR_BLUE }, }; Logging::Logging(const std::string &logfile, LOGLEVEL max_level, bool to_stderr, bool to_syslog) : m_max_level(max_level), m_to_stderr(to_stderr), m_to_syslog(to_syslog) { if (!logfile.empty()) { m_logfile.open(logfile); } if (m_to_syslog) { openlog(PACKAGE, 0, LOG_USER); } } Logging::Logger::~Logger() { std::lock_guard lock_mutex(m_mutex); // Construct string containing time struct timeval tv; long int millisec; std::string time_string(sizeof("YYYY-MM-DD MM:HH:SS.###"), '\0'); // Reserve space for "YYYY-MM-DD MM:HH:SS.###" plus \0 std::string fmt; std::string loglevel; std::string filename; std::string msg; gettimeofday(&tv, NULL); millisec = std::lrint(static_cast(tv.tv_usec) / 1000.0); // Round to nearest millisec if (millisec >= 1000) { // Allow for rounding up to nearest second millisec -= 1000; tv.tv_sec++; } strsprintf(&fmt, "%%F %%T.%03d", millisec); struct tm buf; time_string.resize(strftime(&time_string[0], time_string.size(), fmt.c_str(), localtime_r(&tv.tv_sec, &buf))); // Mind the blank at the end loglevel = m_level_name_map.at(m_loglevel) + ":"; msg = str(); trim(msg); if (!m_filename.empty()) { filename = m_filename; if (replace_start(&filename, params.m_basepath)) { filename = "INPUT [" + filename + "] "; } else if (replace_start(&filename, params.m_mountpath)) { filename = "OUTPUT [" + filename + "] "; } else { std::string cachepath; transcoder_cache_path(&cachepath); if (replace_start(&filename, cachepath + params.m_mountpath + params.m_basepath)) { filename = "CACHE [" + filename + "] "; } else { if (filename.size() && filename[0] == '/') { filename.erase(0, 1); } filename = "OTHER [" + filename + "] "; } } } if (m_logging->m_to_syslog) { syslog(m_syslog_level_map.at(m_loglevel), "%s %s%s", loglevel.c_str(), filename.c_str(), msg.c_str()); } if (m_logging->m_logfile.is_open()) { m_logging->m_logfile << time_string << " " << loglevel << " " << filename << msg << std::endl; } if (m_logging->m_to_stderr) { if (!filename.empty()) { filename = COLOUR_LIGHT_PURPLE + filename + COLOUR_RESET; } if (m_loglevel <= LOGERROR) { msg = COLOUR_LIGHT_RED + msg + COLOUR_RESET; } std::clog << COLOUR_DARK_GRAY << time_string << " " << loglevel << COLOUR_RESET << " " << filename << msg << std::endl; } } bool Logging::GetFail() const { return m_logfile.fail(); } Logging::Logger Log(Logging::LOGLEVEL loglevel, const std::string & filename) { return {loglevel, filename}; } bool Logging::init_logging(const std::string & logfile, LOGLEVEL max_level, bool to_stderr, bool to_syslog) { if (m_logging != nullptr) { // Do not alloc twice return false; } m_logging = std::make_unique(logfile, max_level, to_stderr, to_syslog); if (m_logging == nullptr) { return false; // Out of memory... } return !m_logging->GetFail(); } void Logging::log_with_level(LOGLEVEL loglevel, const char * filename, const std::string & message) { log_with_level(loglevel, std::string(filename != nullptr ? filename : ""), message); } void Logging::log_with_level(LOGLEVEL loglevel, const std::string & filename, const std::string & message) { Log(loglevel, filename) << message; } std::string Logging::format_helper(const std::string &string_to_update, const size_t __attribute__((unused)) index_to_replace) { return string_to_update; } ffmpegfs-2.50/src/ffmpeg_subtitle.h0000664000175000017500000000602715177713600013053 /* * Copyright (C) 2017-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file ffmpeg_subtitle.h * @brief FFmpeg AVSubtitle extension * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef FFMPEG_SUBTITLE_H #define FFMPEG_SUBTITLE_H #pragma once #include "ffmpeg_utils.h" #include struct AVSubtitle; /** * @brief The #FFmpeg_Subtitle class */ class FFmpeg_Subtitle : public std::shared_ptr { public: /** * @brief Construct FFmpeg_Subtitle object. * @param[in] stream_index - Index of stream */ explicit FFmpeg_Subtitle(int stream_index = INVALID_STREAM); /** * @brief Destruct FFmpeg_Subtitle object. */ virtual ~FFmpeg_Subtitle() = default; /** * @brief Get result of last operation * @return Returns 0 if last operation was successful, or negative AVERROR value. */ int res() const; /** * @brief Unreference underlying frame. Synonym for shared_ptr::reset(). */ void unref() noexcept; /** * @brief operator AVSubtitle *: Do as if we were a pointer to AVSubtitle */ operator AVSubtitle*(); /** * @brief operator const AVSubtitle *: Do as if we were a const pointer to AVSubtitle */ operator const AVSubtitle*() const; /** * @brief operator ->: Do as if we were a pointer to AVSubtitle * @return Pointer to AVSubtitle struct. */ AVSubtitle* operator->(); protected: /** * @brief Allocate a subtitle * @return Returns a newly allocated AVSubtitle field, or nullptr if out of memory. */ AVSubtitle* alloc_subtitle(); /** * @brief Delete a subtitle * @param[in] subtitle - AVSubtitle structure to delete/free. */ static void delete_subtitle(AVSubtitle *subtitle); protected: int m_res; /**< @brief 0 if last operation was successful, or negative AVERROR value */ public: int m_stream_idx; /**< @brief Stream index frame belongs to, or -1 (INVALID_STREAM) */ }; #endif // FFMPEG_SUBTITLE_H ffmpegfs-2.50/src/diskio.h0000664000175000017500000001060115177713600011147 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file diskio.h * @brief Direct disk I/O * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef DISKIO_H #define DISKIO_H #pragma once #include "fileio.h" /** @brief Disk file I/O class */ class DiskIO : public FileIO { public: explicit DiskIO(); virtual ~DiskIO(); /** * @brief Get type of the virtual file * @return Returns the type of the virtual file. */ virtual VIRTUALTYPE type() const override; /** * @brief Get the ideal buffer size. * @return Return the ideal buffer size. */ virtual size_t bufsize() const override; /** @brief Open a file * @param[in] virtualfile - LPCVIRTUALFILE of file to open * @return Upon successful completion, #openio() returns 0. @n * On error, an nonzero value is returned and errno is set to indicate the error. */ virtual int openio(LPVIRTUALFILE virtualfile) override; /** @brief Read data from file * @param[out] data - buffer to store read bytes in. Must be large enough to hold up to size bytes. * @param[in] size - number of bytes to read * @return Upon successful completion, #readio() returns the number of bytes read. @n * This may be less than size. @n * On error, the value 0 is returned and errno is set to indicate the error. @n * If at end of file, 0 may be returned by errno not set. error() will return 0 if at EOF. */ virtual size_t readio(void *data, size_t size) override; /** * @brief Get last error. * @return errno value of last error. */ virtual int error() const override; /** @brief Get the duration of the file, in AV_TIME_BASE fractional seconds. * * Not applicable to generic disk files, always returns AV_NOPTS_VALUE. */ virtual int64_t duration() const override; /** * @brief Get the file size. * @return Returns the file size. */ virtual size_t size() const override; /** * @brief Get current read position. * @return Gets the current read position. */ virtual size_t tell() const override; /** @brief Seek to position in file * * Repositions the offset of the open file to the argument offset according to the directive whence. * * @param[in] offset - offset in bytes * @param[in] whence - how to seek: @n * SEEK_SET: The offset is set to offset bytes. @n * SEEK_CUR: The offset is set to its current location plus offset bytes. @n * SEEK_END: The offset is set to the size of the file plus offset bytes. * @return Upon successful completion, #seek() returns the resulting offset location as measured in bytes * from the beginning of the file. @n * On error, the value -1 is returned and errno is set to indicate the error. */ virtual int seek(int64_t offset, int whence) override; /** * @brief Check if at end of file. * @return Returns true if at end of file. */ virtual bool eof() const override; /** * @brief Close virtual file. */ virtual void closeio() override; private: /** * @brief Close virtual file. * Non-virtual version to be safely called from constructor/destructor */ void pvt_close(); protected: FILE * m_fpi; /**< @brief File pointer to source media */ }; #endif // DISKIO_H ffmpegfs-2.50/src/config.h.in0000664000175000017500000001053115215723160011534 /* src/config.h.in. Generated from configure.ac by autoheader. */ /* Arguments passed to configure */ #undef CONFIGURE_ARGS /* format string for pthread_t */ #undef FFMPEGFS_FORMAT_PTHREAD_T /* format string for time_t */ #undef FFMPEGFS_FORMAT_TIME_T /* Use FFMPEG libraries. */ #undef HAVE_FFMPEG /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Link with Google perftools/tcmalloc heap profiler. */ #undef HAVE_PERFTOOLS /* libsqlite3 has sqlite3_db_cacheflush() function. */ #undef HAVE_SQLITE_CACHEFLUSH /* libsqlite3 has sqlite3_errstr() function. */ #undef HAVE_SQLITE_ERRSTR /* libsqlite3 has sqlite3_expanded_sql() function. */ #undef HAVE_SQLITE_EXPANDED_SQL /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if the system has the type '_Bool'. */ #undef HAVE__BOOL /* Host operating system */ #undef HOST_OS /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* The size of 'int', as computed by sizeof. */ #undef SIZEOF_INT /* The size of 'long', as computed by sizeof. */ #undef SIZEOF_LONG /* The size of 'off_t', as computed by sizeof. */ #undef SIZEOF_OFF_T /* The size of 'pthread_t', as computed by sizeof. */ #undef SIZEOF_PTHREAD_T /* The size of 'time_t', as computed by sizeof. */ #undef SIZEOF_TIME_T /* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS /* Version number of package */ #undef VERSION /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS /* Define to 1 on platforms where this makes off_t a 64-bit type. */ #undef _LARGE_FILES /* Define the POSIX version */ #undef _POSIX_C_SOURCE /* Number of bits in time_t, on hosts where this is settable. */ #undef _TIME_BITS /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ #undef _UINT32_T /* Define for Solaris 2.5.1 so the uint64_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ #undef _UINT64_T /* Define to 1 on platforms where this makes time_t a 64-bit type. */ #undef __MINGW_USE_VC2005_COMPAT /* Must be defined for build with GCC 13 */ #undef __STDC_CONSTANT_MACROS /* Define to the type of a signed integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ #undef int32_t /* Define to 'long int' if does not define. */ #undef off_t /* Define as 'unsigned int' if doesn't define. */ #undef size_t /* Define as 'int' if doesn't define. */ #undef ssize_t /* Define to the type of an unsigned integer type of width exactly 16 bits if such a type exists and the standard includes do not define it. */ #undef uint16_t /* Define to the type of an unsigned integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ #undef uint32_t /* Define to the type of an unsigned integer type of width exactly 64 bits if such a type exists and the standard includes do not define it. */ #undef uint64_t ffmpegfs-2.50/src/vcdparser.h0000664000175000017500000000367315177713600011671 /* * Copyright (C) 2018-2026 by Norbert Schlia (nschlia@oblivion-software.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * On Debian systems, the complete text of the GNU General Public License * Version 3 can be found in `/usr/share/common-licenses/GPL-3'. */ /** * @file vcdparser.h * @brief Video CD and Super Video CD parser * * This is only available if built with -DUSE_LIBVCD parameter. * * @ingroup ffmpegfs * * @author Norbert Schlia (nschlia@oblivion-software.de) * @copyright Copyright (C) 2018-2026 Norbert Schlia (nschlia@oblivion-software.de) */ #ifndef VCDPARSER_H #define VCDPARSER_H #pragma once #ifdef USE_LIBVCD /** * @brief Get number of chapters on S/VCD * @param[in] path - Path to check * @param[in, out] buf - The buffer passed to the readdir() operation. * @param[in, out] filler - Function to add an entry in a readdir() operation (see https://libfuse.github.io/doxygen/fuse_8h.html#a7dd132de66a5cc2add2a4eff5d435660) * @note buf and filler can be nullptr. In that case the call will run faster, so these parameters should only be passed if to be filled in. * @return -errno or number of chapters on S/VCD. */ int check_vcd(const std::string & path, void *buf = nullptr, fuse_fill_dir_t filler = nullptr); #endif // USE_LIBVCD #endif // VCDPARSER_H ffmpegfs-2.50/src/Makefile.in0000664000175000017500000010025615215723144011564 # Makefile.in generated by automake 1.17 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2024 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ am__is_gnu_make = { \ if test -z '$(MAKELEVEL)'; then \ false; \ elif test -n '$(MAKE_HOST)'; then \ true; \ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ true; \ else \ false; \ fi; \ } am__make_running_with_option = \ case $${target_option-} in \ ?) ;; \ *) echo "am__make_running_with_option: internal error: invalid" \ "target option '$${target_option-}' specified" >&2; \ exit 1;; \ esac; \ has_opt=no; \ sane_makeflags=$$MAKEFLAGS; \ if $(am__is_gnu_make); then \ sane_makeflags=$$MFLAGS; \ else \ case $$MAKEFLAGS in \ *\\[\ \ ]*) \ bs=\\; \ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ esac; \ fi; \ skip_next=no; \ strip_trailopt () \ { \ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ }; \ for flg in $$sane_makeflags; do \ test $$skip_next = yes && { skip_next=no; continue; }; \ case $$flg in \ *=*|--*) continue;; \ -*I) strip_trailopt 'I'; skip_next=yes;; \ -*I?*) strip_trailopt 'I';; \ -*O) strip_trailopt 'O'; skip_next=yes;; \ -*O?*) strip_trailopt 'O';; \ -*l) strip_trailopt 'l'; skip_next=yes;; \ -*l?*) strip_trailopt 'l';; \ -[dEDm]) skip_next=yes;; \ -[JT]) skip_next=yes;; \ esac; \ case $$flg in \ *$$target_option*) has_opt=yes; break;; \ esac; \ done; \ test $$has_opt = yes am__make_dryrun = (target_option=n; $(am__make_running_with_option)) am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) am__rm_f = rm -f $(am__rm_f_notfound) am__rm_rf = rm -rf $(am__rm_f_notfound) pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkglibexecdir = $(libexecdir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = ffmpegfs$(EXEEXT) # DVD support: requires both libdbdnav and libdvdread @USE_LIBDVD_TRUE@am__append_1 = -DUSE_LIBDVD $(libdvdnav_CFLAGS) \ @USE_LIBDVD_TRUE@ $(libdvdread_CFLAGS) @USE_LIBDVD_TRUE@am__append_2 = dvdio.cc dvdio.h dvdparser.cc dvdparser.h @USE_LIBDVD_TRUE@am__append_3 = $(libdvdnav_LIBS) $(libdvdread_LIBS) # Blu-ray support: requires libbluray @USE_LIBBLURAY_TRUE@am__append_4 = -DUSE_LIBBLURAY $(libbluray_CFLAGS) @USE_LIBBLURAY_TRUE@am__append_5 = blurayio.cc blurayio.h blurayparser.cc blurayparser.h @USE_LIBBLURAY_TRUE@am__append_6 = $(libbluray_LIBS) # VCD support: uses internal code @USE_LIBVCD_TRUE@am__append_7 = -DUSE_LIBVCD $(libvcd_CFLAGS) @USE_LIBVCD_TRUE@am__append_8 = vcdio.cc vcdio.h vcdparser.cc vcdparser.h vcd/vcdchapter.cc vcd/vcdchapter.h vcd/vcdentries.cc vcd/vcdentries.h vcd/vcdinfo.cc vcd/vcdinfo.h vcd/vcdutils.cc vcd/vcdutils.h subdir = src ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) am__ffmpegfs_SOURCES_DIST = ffmpegfs.cc ffmpegfs.h fuseops.cc \ transcode.cc transcode.h cache.cc cache.h buffer.cc buffer.h \ logging.cc logging.h cache_entry.cc cache_entry.h \ cache_maintenance.cc cache_maintenance.h id3v1tag.h aiff.h \ wave.h cuesheetparser.cc cuesheetparser.h diskio.cc diskio.h \ fileio.cc fileio.h ffmpeg_compat.h ffmpeg_profiles.h \ ffmpeg_audiofifo.h ffmpeg_dictionary.h ffmpeg_packet.h \ ffmpeg_swrcontext.h ffmpeg_swscontext.h thread_pool.cc \ thread_pool.h ffmpeg_formatcontext.cc ffmpeg_base.cc \ ffmpeg_base.h ffmpeg_transcoder.cc ffmpeg_transcoder.h \ ffmpeg_utils.cc ffmpeg_utils.h ffmpeg_profiles.cc \ ffmpeg_audiofifo.cc ffmpeg_dictionary.cc ffmpeg_packet.cc \ ffmpeg_swrcontext.cc ffmpeg_swscontext.cc ffmpeg_frame.h \ ffmpeg_frame.cc ffmpeg_subtitle.h ffmpeg_subtitle.cc \ ffmpeg_formatcontext.h dvdio.cc dvdio.h dvdparser.cc \ dvdparser.h blurayio.cc blurayio.h blurayparser.cc \ blurayparser.h vcdio.cc vcdio.h vcdparser.cc vcdparser.h \ vcd/vcdchapter.cc vcd/vcdchapter.h vcd/vcdentries.cc \ vcd/vcdentries.h vcd/vcdinfo.cc vcd/vcdinfo.h vcd/vcdutils.cc \ vcd/vcdutils.h @USE_LIBDVD_TRUE@am__objects_1 = dvdio.$(OBJEXT) dvdparser.$(OBJEXT) @USE_LIBBLURAY_TRUE@am__objects_2 = blurayio.$(OBJEXT) \ @USE_LIBBLURAY_TRUE@ blurayparser.$(OBJEXT) am__dirstamp = $(am__leading_dot)dirstamp @USE_LIBVCD_TRUE@am__objects_3 = vcdio.$(OBJEXT) vcdparser.$(OBJEXT) \ @USE_LIBVCD_TRUE@ vcd/vcdchapter.$(OBJEXT) \ @USE_LIBVCD_TRUE@ vcd/vcdentries.$(OBJEXT) \ @USE_LIBVCD_TRUE@ vcd/vcdinfo.$(OBJEXT) vcd/vcdutils.$(OBJEXT) am_ffmpegfs_OBJECTS = ffmpegfs.$(OBJEXT) fuseops.$(OBJEXT) \ transcode.$(OBJEXT) cache.$(OBJEXT) buffer.$(OBJEXT) \ logging.$(OBJEXT) cache_entry.$(OBJEXT) \ cache_maintenance.$(OBJEXT) cuesheetparser.$(OBJEXT) \ diskio.$(OBJEXT) fileio.$(OBJEXT) thread_pool.$(OBJEXT) \ ffmpeg_formatcontext.$(OBJEXT) ffmpeg_base.$(OBJEXT) \ ffmpeg_transcoder.$(OBJEXT) ffmpeg_utils.$(OBJEXT) \ ffmpeg_profiles.$(OBJEXT) ffmpeg_audiofifo.$(OBJEXT) \ ffmpeg_dictionary.$(OBJEXT) ffmpeg_packet.$(OBJEXT) \ ffmpeg_swrcontext.$(OBJEXT) ffmpeg_swscontext.$(OBJEXT) \ ffmpeg_frame.$(OBJEXT) ffmpeg_subtitle.$(OBJEXT) \ $(am__objects_1) $(am__objects_2) $(am__objects_3) ffmpegfs_OBJECTS = $(am_ffmpegfs_OBJECTS) am__DEPENDENCIES_1 = @USE_LIBDVD_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) @USE_LIBBLURAY_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) ffmpegfs_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \ $(am__DEPENDENCIES_3) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false am__v_P_1 = : AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; am__v_GEN_1 = AM_V_at = $(am__v_at_@AM_V@) am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ depcomp = $(SHELL) $(top_srcdir)/config/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/blurayio.Po \ ./$(DEPDIR)/blurayparser.Po ./$(DEPDIR)/buffer.Po \ ./$(DEPDIR)/cache.Po ./$(DEPDIR)/cache_entry.Po \ ./$(DEPDIR)/cache_maintenance.Po ./$(DEPDIR)/cuesheetparser.Po \ ./$(DEPDIR)/diskio.Po ./$(DEPDIR)/dvdio.Po \ ./$(DEPDIR)/dvdparser.Po ./$(DEPDIR)/ffmpeg_audiofifo.Po \ ./$(DEPDIR)/ffmpeg_base.Po ./$(DEPDIR)/ffmpeg_dictionary.Po \ ./$(DEPDIR)/ffmpeg_formatcontext.Po \ ./$(DEPDIR)/ffmpeg_frame.Po ./$(DEPDIR)/ffmpeg_packet.Po \ ./$(DEPDIR)/ffmpeg_profiles.Po ./$(DEPDIR)/ffmpeg_subtitle.Po \ ./$(DEPDIR)/ffmpeg_swrcontext.Po \ ./$(DEPDIR)/ffmpeg_swscontext.Po \ ./$(DEPDIR)/ffmpeg_transcoder.Po ./$(DEPDIR)/ffmpeg_utils.Po \ ./$(DEPDIR)/ffmpegfs.Po ./$(DEPDIR)/fileio.Po \ ./$(DEPDIR)/fuseops.Po ./$(DEPDIR)/logging.Po \ ./$(DEPDIR)/thread_pool.Po ./$(DEPDIR)/transcode.Po \ ./$(DEPDIR)/vcdio.Po ./$(DEPDIR)/vcdparser.Po \ vcd/$(DEPDIR)/vcdchapter.Po vcd/$(DEPDIR)/vcdentries.Po \ vcd/$(DEPDIR)/vcdinfo.Po vcd/$(DEPDIR)/vcdutils.Po am__mv = mv -f CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) AM_V_CXX = $(am__v_CXX_@AM_V@) am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) am__v_CXX_0 = @echo " CXX " $@; am__v_CXX_1 = CXXLD = $(CXX) CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ -o $@ AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) am__v_CXXLD_0 = @echo " CXXLD " $@; am__v_CXXLD_1 = COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) AM_V_CC = $(am__v_CC_@AM_V@) am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) am__v_CC_0 = @echo " CC " $@; am__v_CC_1 = CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = SOURCES = $(ffmpegfs_SOURCES) DIST_SOURCES = $(am__ffmpegfs_SOURCES_DIST) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ *) (install-info --version) >/dev/null 2>&1;; \ esac am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \ config.h.in # Read a list of newline-separated strings from the standard input, # and print each of them once, without duplicates. Input order is # *not* preserved. am__uniquify_input = $(AWK) '\ BEGIN { nonempty = 0; } \ { items[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in items) print i; }; } \ ' # Make sure the list of sources is unique. This is necessary because, # e.g., the same source file might be shared among _SOURCES variables # for different programs/libraries. am__define_uniq_tagged_files = \ list='$(am__tagged_files)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | $(am__uniquify_input)` am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/config.h.in \ $(top_srcdir)/config/depcomp DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) A2X = @A2X@ ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ CPPFLAGS = @CPPFLAGS@ CSCOPE = @CSCOPE@ CTAGS = @CTAGS@ CURL = @CURL@ CXX = @CXX@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DOXYGEN = @DOXYGEN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ ETAGS = @ETAGS@ EXEEXT = @EXEEXT@ EXTRA_VERSION = @EXTRA_VERSION@ GRAPHVIZ = @GRAPHVIZ@ HAVE_A2X = @HAVE_A2X@ HAVE_W3M = @HAVE_W3M@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ MKDIR_P = @MKDIR_P@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERFTOOLS_CFLAGS = @PERFTOOLS_CFLAGS@ PERFTOOLS_CXXFLAGS = @PERFTOOLS_CXXFLAGS@ PERFTOOLS_LIBS = @PERFTOOLS_LIBS@ PKG_CONFIG = @PKG_CONFIG@ PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ RANLIB = @RANLIB@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ VERSION = @VERSION@ W3M = @W3M@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ chardet_CFLAGS = @chardet_CFLAGS@ chardet_LIBS = @chardet_LIBS@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ fuse3_CFLAGS = @fuse3_CFLAGS@ fuse3_LIBS = @fuse3_LIBS@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libavcodec_CFLAGS = @libavcodec_CFLAGS@ libavcodec_LIBS = @libavcodec_LIBS@ libavfilter_CFLAGS = @libavfilter_CFLAGS@ libavfilter_LIBS = @libavfilter_LIBS@ libavformat_CFLAGS = @libavformat_CFLAGS@ libavformat_LIBS = @libavformat_LIBS@ libavutil_CFLAGS = @libavutil_CFLAGS@ libavutil_LIBS = @libavutil_LIBS@ libbluray_CFLAGS = @libbluray_CFLAGS@ libbluray_LIBS = @libbluray_LIBS@ libcue_CFLAGS = @libcue_CFLAGS@ libcue_LIBS = @libcue_LIBS@ libdir = @libdir@ libdvdread_CFLAGS = @libdvdread_CFLAGS@ libdvdread_LIBS = @libdvdread_LIBS@ libexecdir = @libexecdir@ libswresample_CFLAGS = @libswresample_CFLAGS@ libswresample_LIBS = @libswresample_LIBS@ libswscale_CFLAGS = @libswscale_CFLAGS@ libswscale_LIBS = @libswscale_LIBS@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ runstatedir = @runstatedir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sqlite3_CFLAGS = @sqlite3_CFLAGS@ sqlite3_LIBS = @sqlite3_LIBS@ srcdir = @srcdir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ EXTRA_DIST = $(wildcard scripts/*) # Add fuse3 # Add libchardet AM_CPPFLAGS = $(fuse3_CFLAGS) -D_FILE_OFFSET_BITS=64 \ $(libavcodec_CFLAGS) $(libavutil_CFLAGS) $(libavformat_CFLAGS) \ $(libswscale_CFLAGS) $(libavfilter_CFLAGS) \ $(libswresample_CFLAGS) $(chardet_CFLAGS) $(am__append_1) \ $(am__append_4) $(am__append_7) AM_CFLAGS = $(PERFTOOLS_CFLAGS) AM_CXXFLAGS = $(PERFTOOLS_CXXFLAGS) ffmpegfs_SOURCES = ffmpegfs.cc ffmpegfs.h fuseops.cc transcode.cc \ transcode.h cache.cc cache.h buffer.cc buffer.h logging.cc \ logging.h cache_entry.cc cache_entry.h cache_maintenance.cc \ cache_maintenance.h id3v1tag.h aiff.h wave.h cuesheetparser.cc \ cuesheetparser.h diskio.cc diskio.h fileio.cc fileio.h \ ffmpeg_compat.h ffmpeg_profiles.h ffmpeg_audiofifo.h \ ffmpeg_dictionary.h ffmpeg_packet.h ffmpeg_swrcontext.h \ ffmpeg_swscontext.h thread_pool.cc thread_pool.h \ ffmpeg_formatcontext.cc ffmpeg_base.cc ffmpeg_base.h \ ffmpeg_transcoder.cc ffmpeg_transcoder.h ffmpeg_utils.cc \ ffmpeg_utils.h ffmpeg_profiles.cc ffmpeg_audiofifo.cc \ ffmpeg_dictionary.cc ffmpeg_packet.cc ffmpeg_swrcontext.cc \ ffmpeg_swscontext.cc ffmpeg_frame.h ffmpeg_frame.cc \ ffmpeg_subtitle.h ffmpeg_subtitle.cc ffmpeg_formatcontext.h \ $(am__append_2) $(am__append_5) $(am__append_8) # Add sqlite3 ffmpegfs_LDADD = $(libcue_LIBS) $(fuse3_LIBS) -lrt -lstdc++fs \ $(PERFTOOLS_LIBS) $(libavcodec_LIBS) $(libavutil_LIBS) \ $(libavformat_LIBS) $(libswscale_LIBS) $(libavfilter_LIBS) \ $(libswresample_LIBS) $(sqlite3_LIBS) $(chardet_LIBS) \ $(am__append_3) $(am__append_6) # Add conversion of manpages source. Will be used in binary. BUILT_SOURCES = ../ffmpegfs.1.text ffmpegfshelp.h all: $(BUILT_SOURCES) config.h $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: .cc .o .obj $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ && { if test -f $@; then exit 0; else break; fi; }; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ $(am__cd) $(top_srcdir) && \ $(AUTOMAKE) --foreign src/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): config.h: stamp-h1 @test -f $@ || rm -f stamp-h1 @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1 stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status $(AM_V_at)rm -f stamp-h1 $(AM_V_GEN)cd $(top_builddir) && $(SHELL) ./config.status src/config.h $(srcdir)/config.h.in: $(am__configure_deps) $(AM_V_GEN)($(am__cd) $(top_srcdir) && $(AUTOHEADER)) $(AM_V_at)rm -f stamp-h1 $(AM_V_at)touch $@ distclean-hdr: -rm -f config.h stamp-h1 install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ if test -n "$$list"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p \ ; then echo "$$p"; echo "$$p"; else :; fi; \ done | \ sed -e 'p;s,.*/,,;n;h' \ -e 's|.*|.|' \ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ sed 'N;N;N;s,\n, ,g' | \ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ if ($$2 == $$4) files[d] = files[d] " " $$1; \ else { print "f", $$3 "/" $$4, $$1; } } \ END { for (d in files) print "f", d, files[d] }' | \ while read type dir files; do \ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ test -z "$$files" || { \ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ } \ ; done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ -e 's/$$/$(EXEEXT)/' \ `; \ test -n "$$list" || exit 0; \ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ cd "$(DESTDIR)$(bindir)" && $(am__rm_f) $$files clean-binPROGRAMS: -$(am__rm_f) $(bin_PROGRAMS) vcd/$(am__dirstamp): @$(MKDIR_P) vcd @: >>vcd/$(am__dirstamp) vcd/$(DEPDIR)/$(am__dirstamp): @$(MKDIR_P) vcd/$(DEPDIR) @: >>vcd/$(DEPDIR)/$(am__dirstamp) vcd/vcdchapter.$(OBJEXT): vcd/$(am__dirstamp) \ vcd/$(DEPDIR)/$(am__dirstamp) vcd/vcdentries.$(OBJEXT): vcd/$(am__dirstamp) \ vcd/$(DEPDIR)/$(am__dirstamp) vcd/vcdinfo.$(OBJEXT): vcd/$(am__dirstamp) \ vcd/$(DEPDIR)/$(am__dirstamp) vcd/vcdutils.$(OBJEXT): vcd/$(am__dirstamp) \ vcd/$(DEPDIR)/$(am__dirstamp) ffmpegfs$(EXEEXT): $(ffmpegfs_OBJECTS) $(ffmpegfs_DEPENDENCIES) $(EXTRA_ffmpegfs_DEPENDENCIES) @rm -f ffmpegfs$(EXEEXT) $(AM_V_CXXLD)$(CXXLINK) $(ffmpegfs_OBJECTS) $(ffmpegfs_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) -rm -f vcd/*.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blurayio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blurayparser.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cache.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cache_entry.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cache_maintenance.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cuesheetparser.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/diskio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dvdio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dvdparser.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_audiofifo.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_base.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_dictionary.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_formatcontext.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_frame.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_profiles.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_subtitle.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_swrcontext.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_swscontext.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_transcoder.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpeg_utils.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffmpegfs.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuseops.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logging.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/thread_pool.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transcode.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vcdio.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vcdparser.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vcd/$(DEPDIR)/vcdchapter.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vcd/$(DEPDIR)/vcdentries.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vcd/$(DEPDIR)/vcdinfo.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@vcd/$(DEPDIR)/vcdutils.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @$(MKDIR_P) $(@D) @: >>$@ am--depfiles: $(am__depfiles_remade) .cc.o: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< .cc.obj: @am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ @am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` ID: $(am__tagged_files) $(am__define_uniq_tagged_files); mkid -fID $$unique tags: tags-am TAGS: tags tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) set x; \ here=`pwd`; \ $(am__define_uniq_tagged_files); \ shift; \ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ if test $$# -gt 0; then \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ "$$@" $$unique; \ else \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$unique; \ fi; \ fi ctags: ctags-am CTAGS: ctags ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) $(am__define_uniq_tagged_files); \ test -z "$(CTAGS_ARGS)$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && $(am__cd) $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) "$$here" cscopelist: cscopelist-am cscopelist-am: $(am__tagged_files) list='$(am__tagged_files)'; \ case "$(srcdir)" in \ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ *) sdir=$(subdir)/$(srcdir) ;; \ esac; \ for i in $$list; do \ if test -f "$$i"; then \ echo "$(subdir)/$$i"; \ else \ echo "$$sdir/$$i"; \ fi; \ done >> $(top_builddir)/cscope.files distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d "$(distdir)/$$file"; then \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ fi; \ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ else \ test -f "$(distdir)/$$file" \ || cp -p $$d/$$file "$(distdir)/$$file" \ || exit 1; \ fi; \ done check-am: all-am check: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) check-am all-am: Makefile $(PROGRAMS) config.h installdirs: for dir in "$(DESTDIR)$(bindir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-am install-exec: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: if test -z '$(STRIP)'; then \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ install; \ else \ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ fi mostlyclean-generic: clean-generic: distclean-generic: -$(am__rm_f) $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES) -$(am__rm_f) vcd/$(DEPDIR)/$(am__dirstamp) -$(am__rm_f) vcd/$(am__dirstamp) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." -$(am__rm_f) $(BUILT_SOURCES) clean: clean-am clean-am: clean-binPROGRAMS clean-generic mostlyclean-am distclean: distclean-am -rm -f ./$(DEPDIR)/blurayio.Po -rm -f ./$(DEPDIR)/blurayparser.Po -rm -f ./$(DEPDIR)/buffer.Po -rm -f ./$(DEPDIR)/cache.Po -rm -f ./$(DEPDIR)/cache_entry.Po -rm -f ./$(DEPDIR)/cache_maintenance.Po -rm -f ./$(DEPDIR)/cuesheetparser.Po -rm -f ./$(DEPDIR)/diskio.Po -rm -f ./$(DEPDIR)/dvdio.Po -rm -f ./$(DEPDIR)/dvdparser.Po -rm -f ./$(DEPDIR)/ffmpeg_audiofifo.Po -rm -f ./$(DEPDIR)/ffmpeg_base.Po -rm -f ./$(DEPDIR)/ffmpeg_dictionary.Po -rm -f ./$(DEPDIR)/ffmpeg_formatcontext.Po -rm -f ./$(DEPDIR)/ffmpeg_frame.Po -rm -f ./$(DEPDIR)/ffmpeg_packet.Po -rm -f ./$(DEPDIR)/ffmpeg_profiles.Po -rm -f ./$(DEPDIR)/ffmpeg_subtitle.Po -rm -f ./$(DEPDIR)/ffmpeg_swrcontext.Po -rm -f ./$(DEPDIR)/ffmpeg_swscontext.Po -rm -f ./$(DEPDIR)/ffmpeg_transcoder.Po -rm -f ./$(DEPDIR)/ffmpeg_utils.Po -rm -f ./$(DEPDIR)/ffmpegfs.Po -rm -f ./$(DEPDIR)/fileio.Po -rm -f ./$(DEPDIR)/fuseops.Po -rm -f ./$(DEPDIR)/logging.Po -rm -f ./$(DEPDIR)/thread_pool.Po -rm -f ./$(DEPDIR)/transcode.Po -rm -f ./$(DEPDIR)/vcdio.Po -rm -f ./$(DEPDIR)/vcdparser.Po -rm -f vcd/$(DEPDIR)/vcdchapter.Po -rm -f vcd/$(DEPDIR)/vcdentries.Po -rm -f vcd/$(DEPDIR)/vcdinfo.Po -rm -f vcd/$(DEPDIR)/vcdutils.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-tags dvi: dvi-am dvi-am: html: html-am html-am: info: info-am info-am: install-data-am: install-dvi: install-dvi-am install-dvi-am: install-exec-am: install-binPROGRAMS install-html: install-html-am install-html-am: install-info: install-info-am install-info-am: install-man: install-pdf: install-pdf-am install-pdf-am: install-ps: install-ps-am install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/blurayio.Po -rm -f ./$(DEPDIR)/blurayparser.Po -rm -f ./$(DEPDIR)/buffer.Po -rm -f ./$(DEPDIR)/cache.Po -rm -f ./$(DEPDIR)/cache_entry.Po -rm -f ./$(DEPDIR)/cache_maintenance.Po -rm -f ./$(DEPDIR)/cuesheetparser.Po -rm -f ./$(DEPDIR)/diskio.Po -rm -f ./$(DEPDIR)/dvdio.Po -rm -f ./$(DEPDIR)/dvdparser.Po -rm -f ./$(DEPDIR)/ffmpeg_audiofifo.Po -rm -f ./$(DEPDIR)/ffmpeg_base.Po -rm -f ./$(DEPDIR)/ffmpeg_dictionary.Po -rm -f ./$(DEPDIR)/ffmpeg_formatcontext.Po -rm -f ./$(DEPDIR)/ffmpeg_frame.Po -rm -f ./$(DEPDIR)/ffmpeg_packet.Po -rm -f ./$(DEPDIR)/ffmpeg_profiles.Po -rm -f ./$(DEPDIR)/ffmpeg_subtitle.Po -rm -f ./$(DEPDIR)/ffmpeg_swrcontext.Po -rm -f ./$(DEPDIR)/ffmpeg_swscontext.Po -rm -f ./$(DEPDIR)/ffmpeg_transcoder.Po -rm -f ./$(DEPDIR)/ffmpeg_utils.Po -rm -f ./$(DEPDIR)/ffmpegfs.Po -rm -f ./$(DEPDIR)/fileio.Po -rm -f ./$(DEPDIR)/fuseops.Po -rm -f ./$(DEPDIR)/logging.Po -rm -f ./$(DEPDIR)/thread_pool.Po -rm -f ./$(DEPDIR)/transcode.Po -rm -f ./$(DEPDIR)/vcdio.Po -rm -f ./$(DEPDIR)/vcdparser.Po -rm -f vcd/$(DEPDIR)/vcdchapter.Po -rm -f vcd/$(DEPDIR)/vcdentries.Po -rm -f vcd/$(DEPDIR)/vcdinfo.Po -rm -f vcd/$(DEPDIR)/vcdutils.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-binPROGRAMS .MAKE: all check install install-am install-exec install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ clean-binPROGRAMS clean-generic cscopelist-am ctags ctags-am \ distclean distclean-compile distclean-generic distclean-hdr \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-binPROGRAMS install-data \ install-data-am install-dvi install-dvi-am install-exec \ install-exec-am install-html install-html-am install-info \ install-info-am install-man install-pdf install-pdf-am \ install-ps install-ps-am install-strip installcheck \ installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ uninstall-am uninstall-binPROGRAMS .PRECIOUS: Makefile ../ffmpegfs.1.text: ../ffmpegfs.1.txt $(AM_V_GEN)a2x -a revnumber="$(VERSION)" \ -a revdate="$(shell date +'%B %Y')" -D ".." -f text $< # sed in makefiles is a menace so we use a helper script ffmpegfshelp.h: ../ffmpegfs.1.text $(srcdir)/makehelp.sh $@ # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: # Tell GNU make to disable its built-in pattern rules. %:: %,v %:: RCS/%,v %:: RCS/% %:: s.% %:: SCCS/s.% ffmpegfs-2.50/Makefile.am0000664000175000017500000002260215202071356010757 # Definitions for sub-makes if DEBUG OPTIMISATION = -DDEBUG -ggdb else OPTIMISATION = -DNDEBUG endif WARNINGS = -Wall -Wextra -Wconversion -Wsign-compare -Wsign-conversion -Wpedantic AM_CFLAGS = -std=c17 $(INCLUDES) $(WARNINGS) $(OPTIMISATION) -D_GNU_SOURCE AM_CXXFLAGS = -std=c++17 $(INCLUDES) $(WARNINGS) $(OPTIMISATION) -D_GNU_SOURCE if ENABLE_EXTRA_VERSION AM_CFLAGS += -DEXTRA_VERSION=\"$(EXTRA_VERSION)\" AM_CXXFLAGS += -DEXTRA_VERSION=\"$(EXTRA_VERSION)\" endif export CACHEDIR = $(DESTDIR)/var/cache/ffmpegfs if ENABLE_PERFTOOLS PERFTOOLS_STATUS = enabled else PERFTOOLS_STATUS = disabled endif if USE_LIBDVD LIBDVD_STATUS = enabled else LIBDVD_STATUS = disabled endif if USE_LIBBLURAY LIBBLURAY_STATUS = enabled else LIBBLURAY_STATUS = disabled endif if USE_LIBVCD LIBVCD_STATUS = enabled else LIBVCD_STATUS = disabled endif if DEBUG DEBUG_STATUS = enabled else DEBUG_STATUS = disabled endif SUBDIRS = src test dist_man_MANS = ffmpegfs.1 MAINTAINERCLEANFILES = ffmpegfs.1 EXTRA_DIST = INSTALL.md README.md HISTORY.md PROBLEMS.md FEATURES.md COPYING.CC0 ffmpegfs.1.txt src/makehelp.sh #CLEANFILES = .PHONY: help help: @echo "FFMPEGFS make targets" @echo @echo "Common targets:" @echo " make Build ffmpegfs" @echo " make check Run the test suite" @echo " make install Install binaries, man page and support files" @echo " make uninstall Uninstall installed files and remove the cache directory" @echo " make clean Remove normal build artefacts" @echo " make distclean Remove build artefacts and configure output" @echo " make maintainer-clean Remove generated maintainer files" @echo " make dist Create a release tarball" @echo " make distcheck Create and verify a release tarball" @echo @echo "Project documentation targets:" @echo " make help-html Build HTML help from ffmpegfs.1.txt" @echo " make help-pdf Build PDF help from ffmpegfs.1.txt" @echo " make doxy Build Doxygen documentation" @echo " make doxy-clean Remove Doxygen output" @echo @echo "Project maintenance/check targets:" @echo " make cppcheck Run cppcheck and write cppcheck.log" @echo " make lint Run clang-tidy and write lint.log" @echo " make squeaky-clean Remove Autotools and generated project files" @echo " make wipe-all Run distclean and remove generated Autotools/Doxygen files" @echo @echo "Configured options:" @echo " Debug build: $(DEBUG_STATUS)" @echo " Google perftools: $(PERFTOOLS_STATUS)" @echo " DVD support: $(LIBDVD_STATUS)" @echo " Blu-ray support: $(LIBBLURAY_STATUS)" @echo " VCD support: $(LIBVCD_STATUS)" @echo @echo "Profiling build:" @echo " ./configure --enable-perftools" @echo " make" @echo @echo "Useful check after a perftools build:" @echo " ldd ./src/ffmpegfs | grep -E 'tcmalloc|profiler|perftools'" ffmpegfs.1: ffmpegfs.1.txt $(AM_V_GEN)a2x -a revnumber="$(VERSION)" \ -a revdate="$(shell date +'%B %Y')" -f manpage -D . --xsltproc-opts="-param ulink.show 1" $< # Just for the fun of it # PDF help - needs FOP ffmpegfs.1.pdf: ffmpegfs.1.txt $(AM_V_GEN)a2x -a revnumber="$(VERSION)" \ -a revdate="$(shell date +'%B %Y')" -f pdf --fop --xsltproc-opts="-param ulink.show 1" $< #--xsltproc-opts="-param ulink.show 0" help-pdf: ffmpegfs.1.pdf # HTML help ffmpegfs.1.htmlhelp: ffmpegfs.1.txt $(AM_V_GEN)a2x -a revnumber="$(VERSION)" \ -a revdate="$(shell date +'%B %Y')" -D "`pwd`" -f htmlhelp $< @mv ffmpegfs.1.htmlhelp/index.html ffmpegfs.1.htmlhelp/manpages.html help-html: ffmpegfs.1.htmlhelp # Remove absolutely every generated file .PHONY: squeaky-clean squeaky-clean: maintainer-clean rm -rf aclocal.m4 autom4te.cache config config.log configure ffmpegfs.1 ffmpegfs.1.hhc ffmpegfs.1.hhp ffmpegfs.1.htmlhelp ffmpegfs.1.pdf ffmpegfs.1.text ffmpegfs.layout Makefile.in src/config.h.in src/.deps src/ffmpegfshelp.h src/Makefile.in src/vcd/.deps test/.deps test/Makefile.in dist-hook: if NOCHANGELOG @echo 'Not generating ChangeLog' else @echo 'Creating ChangeLog file from git log' @( set -o pipefail && \ echo 'Automatically generated by Makefile' ; echo ; \ git log --pretty="format:%d %h / %aD%n %an <%ae>%n * %s%n" \ | sed -E -e 'N;N;N' \ -e 's|^ \(.*tag: ([0-9\.]+).*\)[^\n]* (.{5,6} [0-9]{4}) .*|Changes for version \1 (\2):\n|' \ -e 's|^ \(.*\)||' ) > ChangeLog.tmp \ && mv -f ChangeLog.tmp $(top_distdir)/ChangeLog \ || ( rm -f ChangeLog.tmp ; exit 1 ) endif SRCS = $(OBJS:.o=.c) #.PHONY: all distclean doxy cppcheck # If makefile changes, maybe the list of sources has changed, so update doxygens list doxyfile.inc: Doxyfile Makefile @echo "PROJECT_NAME = \"FFmpegfs Fuse Multi Media Filesystem\"" > doxyfile.inc @echo "OUTPUT_DIRECTORY = doxygen" >> doxyfile.inc @echo "PREDEFINED = USE_LIBBLURAY" >> doxyfile.inc @echo "PREDEFINED += USE_LIBDVD" >> doxyfile.inc @echo "PREDEFINED += USE_LIBVCD" >> doxyfile.inc @echo "INPUT = $(SUBDIRS)" >> doxyfile.inc @echo "FILE_PATTERNS = *.h *.cc $(SRCS)" >> doxyfile.inc @echo "EXCLUDE = src/config.h" >> doxyfile.inc @echo "EXCLUDE += src/ffmpegfshelp.h" >> doxyfile.inc @echo "EXCLUDE += doxyfile.inc" >> doxyfile.inc @echo "HTML_EXTRA_FILES += ffmpegfs.1.htmlhelp/manpages.html" >> doxyfile.inc @echo "HTML_EXTRA_FILES += ffmpegfs.1.htmlhelp/docbook-xsl.css" >> doxyfile.inc @echo "INPUT += README.md" >> doxyfile.inc @echo "INPUT += INSTALL.md" >> doxyfile.inc @echo "INPUT += HISTORY.md" >> doxyfile.inc @echo "INPUT += PROBLEMS.md" >> doxyfile.inc @echo "INPUT += FEATURES.md" >> doxyfile.inc @echo "EXAMPLE_PATH += NEWS" >> doxyfile.inc @echo "EXAMPLE_PATH += TODO" >> doxyfile.inc @echo "EXAMPLE_PATH += COPYING" >> doxyfile.inc @echo "EXAMPLE_PATH += COPYING.DOC" >> doxyfile.inc @echo "EXAMPLE_PATH += COPYING.CC0" >> doxyfile.inc @echo "RECURSIVE = YES" >> doxyfile.inc @echo "SOURCE_BROWSER = YES" >> doxyfile.inc @echo "DISTRIBUTE_GROUP_DOC = YES" >> doxyfile.inc @echo "REFERENCED_BY_RELATION = YES" >> doxyfile.inc @echo "REFERENCES_RELATION = YES" >> doxyfile.inc @echo "EXTRACT_STATIC = YES" >> doxyfile.inc @echo "EXTRACT_PRIVATE = YES" >> doxyfile.inc @echo "PROJECT_NUMBER = $(VERSION)" >> doxyfile.inc # Run doxygen, first render Github markdown documents, see https://developer.github.com/v3/markdown/#render-an-arbitrary-markdown-document doxy: doxyfile.inc ffmpegfs.1.htmlhelp $(SRCS) @doxygen Doxyfile # Clean up Doxygen files doxy-clean: rm -Rf doxyfile.inc doxygen doxygen.log # Clean up extra stuff clean-local: doxy-clean rm -Rf ffmpegfs.1.htmlhelp ffmpegfs.1 ffmpegfs.1.hhc ffmpegfs.1.hhp ffmpegfs.1.pdf ffmpegfs.1.text src/ffmpegfshelp.h # Really clean up everything wipe-all: doxy-clean distclean rm -Rf autom4te.cache configure config aclocal.m4 Makefile.in test/Makefile.in test/.deps src/Makefile.in src/config.h.in src/.deps src/vcd/.deps src/config.h.in ffmpegfs.layout # Run cppcheck cppcheck: cppcheck --inline-suppr --force --enable=all --inconclusive --library=posix --output-file=cppcheck.log --suppressions-list=cppcheck-suppressions.txt -I src/ -I src/vcd/ -DUSE_LIBBLURAY -DUSE_LIBDVD -DUSE_LIBVCD -DHAVE_CONFIG_H -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -DUSE_LIBSWRESAMPLE -DLAVU_DEP_OLD_CHANNEL_LAYOUT=1 -D__GNUC_ -DPACKAGE_NAME="FFmpegfs" -DFFMPEFS_VERSION=\"V0.0\" -U__clang_version__ -UPRESCAN_FRAMES -DEXTRA_VERSION="" src/ src/vcd/ # android- Checks related to Android. # boost- Checks related to Boost library. # bugprone- Checks that target bugprone code constructs. CLANGCHECKS = bugprone-*,-bugprone-branch-clone # cert- Checks related to CERT Secure Coding Guidelines. CLANGCHECKS += ,cert-*,-cert-err09-cpp,-cert-err61-cpp,-cert-err58-cpp # cppcoreguidelines- Checks related to C++ Core Guidelines. # clang-analyzer- Clang Static Analyzer checks. CLANGCHECKS += ,clang-analyzer-* # google- Checks related to Google coding conventions. # hicpp- Checks related to High Integrity C++ Coding Standard. # llvm- Checks related to the LLVM coding conventions. # misc- Checks that we didn’t have a better category for. # modernize- Checks that advocate usage of modern (currently “modern” means “C++11”) language constructs. CLANGCHECKS += ,modernize-*,-modernize-use-trailing-return-type,-modernize-use-auto CLANGCHECKS += ,-modernize-use-using # mpi- Checks related to MPI (Message Passing Interface). # performance- Checks that target performance-related issues. CLANGCHECKS += ,performance-* # readability- Checks that target readability-related issues that don’t relate to any particular coding style. CLANGTIDY = clang-tidy -extra-arg="--std=c++17" -warnings-as-errors='*' --checks='$(CLANGCHECKS)' --quiet lint: $(CLANGTIDY) src/*.c* src/vcd/*.c* -- -Isrc -Isrc/vcd -I/usr/include/chardet -DHAVE_CONFIG_H -D_FILE_OFFSET_BITS=64 -DUSE_LIBDVD -DUSE_LIBBLURAY -DUSE_LIBVCD -D_GNU_SOURCE > lint.log install-exec-hook: @if [ -d "$(CACHEDIR)" ]; \ then \ echo "$(CACHEDIR) already exists."; \ else \ echo "Creating cache in $(CACHEDIR)."; \ mkdir -p "$(CACHEDIR)" || true; \ fi uninstall-hook: @if [ -d "$(CACHEDIR)" ]; \ then \ echo "Removing cache directory $(CACHEDIR)."; \ rm -Rf "$(CACHEDIR)" || true; \ fiffmpegfs-2.50/NEWS0000664000175000017500000017121015215723104007421 FFmpegfs NEWS Important changes in 2.50 (2026-06-12): * Bugfix: Fixed audio/video synchronization issues during transcoding. * Bugfix: Fixed HLS playback stopping unexpectedly, especially near the final segment. * Refactored FFmpeg resource handling to use RAII wrappers for AVPacket, AVFormatContext, AVAudioFifo, AVDictionary, SwrContext, and SwsContext. * Removed deprecated av_init_packet() usage and switched packet handling to allocation-based AVPacket lifetime management. * Improved cleanup of FFmpeg input/output contexts, including custom AVIOContext handling and error paths during context allocation. * Improved audio FIFO, resampler, scaler, and dictionary lifetime management to reduce manual cleanup code and avoid leaks on error paths. * Cleaned up transcoder shutdown paths by centralizing FFmpeg resource cleanup in dedicated wrapper classes. * Bugfix: Fixed retained FFmpeg deinterlace filter graphs when the output pipeline is rebuilt, especially after HLS seeks. Old filter graphs are now released before reinitialisation and when closing the current output, preventing stale filter pointers and retained filter buffers. * Bugfix: Fixed HLS playback stopping unexpectedly, especially near the last HLS segment. * Bugfix: Improved HLS segment finalisation and state cleanup between segment transitions. * Bugfix: Improved packet/frame lifetime handling to avoid stale state during HLS playback. * Added a `make help` target to list the available build, test, installation, documentation and maintenance targets. * Added `./configure --enable-perftools` to build ffmpegfs with Google Perftools heap profiling support. * Bugfix: Fixed HLS playback for 10-bit UHD/HDR sources by converting H.264 HLS output to yuv420p/8-bit. This avoids unsupported H.264 High10 streams in browser-based players such as hls.js. * Added elapsed-time reporting to successful transcode completion messages, showing the total transcoding time in milliseconds. * Bugfix: Fixed a race condition in transcoder thread start-up which could allow the same cache entry to be transcoded more than once concurrently. * Consolidated transcoder completion logging so each worker now emits one clear final status message, including elapsed runtime for successful transcodes. * Bugfix: Fixed HLS cache recovery so stale segment metadata no longer leaves playback waiting indefinitely when the corresponding cache file is missing, empty, or otherwise unusable. * Improved HLS segment recovery by explicitly restarting the transcoder when a segment is marked as available but its cache file is missing, empty, or otherwise unusable. * Bugfix: Fixed HLS segment availability checks so an incomplete cache entry no longer makes segments appear available if those segments were never actually produced. * Added additional safeguards for stale decoder state to avoid leaving cache entries permanently marked as decoding when no active worker is available to repair them. * Bugfix: Ignored HLS seek requests that are too close to the beginning of a stream so transcoding starts at segment 1 instead of creating an avoidable partial cache set. * Added an HLS cache regression test that pre-populates the cache, re-reads all generated segments, and verifies that cached segment output remains stable across repeated reads. * Bugfix: Fixed HLS cache test log naming so wrapper scripts which already contain the "_hls" suffix no longer generate duplicate "_hls_hls" builtin log files. * Bugfix: Fixed distclean/distcheck failures caused by incorrectly named HLS test log files being left behind in the test build directory. * Refactored transcoder stream and output initialisation into smaller helper functions, keeping high-level setup flow easier to follow. * Improved AVCodecContext ownership handling during stream, output, and frame-set setup so partially failed initialisation paths no longer leak codec contexts. * Hardened output/cache setup with additional validation and null checks to avoid partially initialised stream state and clearer error handling on invalid setup conditions. * Improved duration metadata handling for stream-copy and album-art output so invalid or missing input timing information is no longer used. * Improved test cleanup by using explicit ffmpegfs-owned temporary directory names and guarded removal logic, avoiding stale anonymous /tmp directories after interrupted or parallel test runs. * Improved deinterlacing quality by replacing yadif with bwdif while keeping one output frame per input frame. This improves playback smoothness for interlaced sources without changing the output frame rate or requiring changes to timestamp, FPS, or HLS segment timing logic. Important changes in 2.18 (2026-04-10): * Feature: Added ALAC profile for iTunes (--desttype=ALAC --profile=ITUNES). Playback of the file will not commence until it is fully recoded; however, it can be played in iTunes. * Feature: Implemented a validation check for the combination of TYPE and PROFILE in --desttype=TYPE --profile=PROFILE. * Updated Dockerfile to include Fuse3 * Bugfix: Fix error with new FFmpeg API. "Option 'pix_fmts' is not a runtime option and so cannot be set after the object has been initialized" * Fixed deprecation: Replace avcodec_get_supported_config() * Fixed deprecation: Remove avcodec_close() * Fixed deprecation: Remove av_format_inject_global_side_data() * Fixed deprecation: Replace std::codecvt with iconv in read_file * Bugfix: reserve() only guarantees capacity, not size → Writing via .data() is undefined behaviour. Using resize() makes the memory usable. * As strerror() is not thread-safe, use strerror_r() where available. * strncpy likes to copy without NUL → terminate explicitly. * Bugfix: Issue #173: Fixed output directory no showing complete list of files under Debian 13. * Closes#1115015: Fix build with FFmpeg 8 (already applied in Debian via NMU) * Closes#1119414: Changed configure.ac and makefile.am to preserve the default build flags Important changes in 2.17 (2024-11-10): * Bugfix: Issue #164: Fixed incorrectly discarded HLS seek requests. * Bugfix: Wrong error message fixed when an invalid audio/video codec was selected. The message should rather say "unsupported codec" instead of talking about "sample format not supported.". * Bugfix: Issue #162: If not present, add time stamps to the copied streams. * Changed quality from 34 to 40 for hardware encoded video streams to create slightly smaller files. * Closes#1084487: Moved from the Fuse 2 to the Fuse 3 API. Important changes in 2.16 (2024-06-10): * Bugfix: Closes#1072412: Fix build with FFmpeg 7.0. write_packet() now with const buffer as of Libavformat 61+. * Fixed deprecation: 2014-05-18 - 68c0518 / fd05602 - lavc 55.63.100 / 55.52.0 - avcodec.h Add avcodec_free_context(). From now on it should be used for freeing AVCodecContext. * Fixed deprecation: 2023-05-15 - 7d1d61cc5f5 - lavc 60 - avcodec.h Depreate AVCodecContext.ticks_per_frame in favor of AVCodecContext.framerate (encoding) and AV_CODEC_PROP_FIELDS (decoding). Important changes in 2.15 (2024-02-03): * Bugfix: Issue #151: Fixed autocopy STRICT never triggers for video streams * Bugfix: Issue #153: The --include_extensions parameter now contains a description, which was previously missing from the manual and online help. * Issue #149: 2023-05-04 - xxxxxxxxxx - lavu 58.7.100 - frame.h Deprecate AVFrame.interlaced_frame, AVFrame.top_field_first, and AVFrame.key_frame. Add AV_FRAME_FLAG_INTERLACED, AV_FRAME_FLAG_TOP_FIELD_FIRST, and AV_FRAME_FLAG_KEY flags as replacement. * Issue #149: 2023-05-04 - xxxxxxxxxx - lavu 58.7.100 - frame.h Deprecate AVFrame.interlaced_frame, AVFrame.top_field_first, and AVFrame.key_frame. Add AV_FRAME_FLAG_INTERLACED, AV_FRAME_FLAG_TOP_FIELD_FIRST, and AV_FRAME_FLAG_KEY flags as replacement. * Issue #149: 2021-09-20 - dd846bc4a91 - lavc 59.8.100 - avcodec.h codec.h Deprecate AV_CODEC_FLAG_TRUNCATED and AV_CODEC_CAP_TRUNCATED, as they are redundant with parsers. * Issue #136: The CMake build files have been removed. Support was never more than experimental, and CMake lacks a good uninstall option. Will stick to automake system from now on. Important changes in 2.14 (2023-06-15): * Bugfix: Issue #141: Improved memory management by allocating several times the average size of allocations. This prevents obtaining tiny portions over and over again. Additionally, after the file is opened, grab the entire expected memory block rather than doing a tiny allocation initially, followed by a larger allocation. * Bugfix: Avoid race conditions that cause the inter-process semaphore creation to fail for the second process. * Bugfix: Issue #119: If a seek request is still open after EOF, restart transcoding. * Bugfix: Issue #119: To prevent frame/segment creation errors, the frame set and HLS code has been updated. * Bugfix: Avoid crashes during shutdown if cache objects have already been closed. * Bugfix: Issue #119: The AVSEEK_FLAG_FRAME set should be used to seek to frames when building frame sets. Otherwise, output images may vary if searched for or continuously decoded. * Bugfix: The conversion of PTS to frame number and vice versa for frame sets was incorrect if TBR did not equal frames per second. * Bugfix: Fixed seek requests that are being ignored with frame sets. * Bugfix: When transferring from cache to the Fuse buffer, avoid a possible 1 byte overrun. * Bugfix: Issue #143: To avoid occasional EPERM failures, missing synchronisation objects were added. * Bugfix: Issue #144: To fix the crashes that may have been caused by them, the variables impacted by a potential threading issue were marked as "volatile." * Bugfix: Closes#1037653: Fix build with GCC-13 * Bugfix: Update docker build for Debian Bookworm * Enhancement: Record milliseconds for every log event. * Enhancement: make check: added a file size check to frame set tests. * Optimisation: When reopening after invalidating the cache, the size remained at 0. The original size is now once again reserved in order to prevent reallocations. * Optimisation: To avoid reallocations, save enough space in the cache buffer to hold the entire frame set. * Optimisation: Checking folders to see if they can be transcoded is completely pointless. Directories are now immediately skipped. * To avoid problems with logfile viewers, renamed built-in logfiles to *_builtin.log (removing the double extension). Important changes in 2.13 (2023-01-15): * Added --prebuffer_time parameter. Files will be decoded until the buffer contains the specified playing time, allowing playback to start smoothly without lags. Works similar to --prebuffer_size but gives better control because it does not depend on the bit rate. An example: when set to 25 seconds for HLS transcoding, this will make sure that at least 2 complete segments will be available once the file is released and visible. * Feature: Issue #140: Filtering the files that will be encoded has been added. A comma-separated list of extensions is specified by the --include_extensions parameter. These file extensions are the only ones that will be transcoded. The entries support shell wildcard patterns. * Feature: The --hide_extensions parameter syntax has been extended. The entries now support shell wildcard patterns. * Bugfix: Issue #139: Additional files could be added using the --extensions parameter. However, this is no longer necessary; in the past, a file's extension determined whether or not it would be transcoded. Files with unknown extensions would be ignored. The extension is no longer important because FFmpegfs now examines all input files and recognises transcodable files by the format. The outdated --extensions argument was removed without substitution. * Bugfix: Fixed crash when implode() function was called with an empty string. Happened with Windows GCC 11.3.0 only. Important changes in 2.12 (2022-08-27): * The code has been run through clang-tidy to identify bug-prone or inefficient code, and to find parts that could be modernised to C++17. Several issues could be resolved. Sometimes, many lines of code could be replaced by a few. Some parts run far more efficiently than before. C++17 is cool. I need to get a t-shirt. * Bugfix: In get prores bitrate(), a crash that might have happened under unusual circumstances has been corrected. If the best match resolution could not be found, array access out-of-bounds could happen. * Bugfix: Several unlikely, but potential problems that could have happened when subtitle decoding failed or delayed video/audio packets couldn't be decoded have been fixed. * Bugfix: An internal problem could cause the application to crash. Should never happen, though. Fixed anyway. * Bugfix: Sometimes, the last segment's estimated size was incredibly small - about 2 bytes. Each segment should have the same predicted size as it is calculated simply by dividing the projected size of the entire file by the number of segments. Following transcoding, the size was accurate. Important changes in 2.11 (2022-06-16): * Feature: Issue #86: Smart transcode now detects if a source file is audio only and uses the correct target format. For example, with --destination=webm+mp3, if one MP4 input file contains a video stream and another an audio stream only, the resulting files will be WebM (for the video input) and mp3 for the audio only file. * Feature: Issue #137: Add --no_subtitles option to turn subtitles off. * Bugfix: Smart encode selected the video format for cue sheet tracks, regardless of the input format. This has been fixed now. * Bugfix: Fix a crash that occurs when a DVD/Blu-ray is transcoded to audio only. * Bugfix: If the track performer field in the cuesheet is blank, try album performer instead. * Bugfix: Failing to mount Fuse during "make check" went unnoticed as the result code (which was supposed to be 99) was actually 0. Return the correct result code, failing the operation as expected. * Bugfix: The Docker build command contained a "make check" which actually failed altogether. Step has been removed. "make check" mounts Fuse, but this requires privileges that do not exist during "docker build". * Bugfix: On error, mremap () returns MAP_FAILED rather than NULL. Fixed a check for incorrect error conditions, which could cause the application to crash or return illogical error messages. * Bugfix: Issue #119: Fix a problem that caused frame set generation to fail sometimes. It seems to be related to the nremap() issue. * Generally revisited documentation, logging, and display texts. Improved grammar, formatting, and fixed quite a few typos that escaped all proofreading sessions. * The FFmpeg API INFO and DEBUG level log output has been changed to the FFmpegfs DEBUG level. What FFmpeg considers "INFO" is far too chatty. * Frequent memory reallocations when creating HLS segments have been reduced to speed up processing. * Optimised logging to save CPU time by not formatting log entries that are not written anyway at their log level. * Logging has been revised to shorten file paths and remove mount, input, and cache paths. Log the additional portion only to reduce log file size and improve readability. * Bugfix: To fix the build with GCC 12, add the missing include headers (closes: #1012925). Important changes in 2.10 (2022-04-26): * Feature: Issue #123: New command line option to hide files by extension. Example: --hide_extensions=jpg,png,cue would stop covers and cue sheets from showing up. * Feature: Issue #120: Added subtitle support. Subtitle streams can now also be transcoded to the output files. Separate SRT or VTT files will be incorporated as subtitle streams. * Bugfix: Fixed memory leak in encode_audio_frame(). * Bugfix: Issue #122: Last song was missing from cuesheet files. * Bugfix: Issue #129: Files remained zero size when previously transcoded. * Bugfix: Issue #130: Fix file sizes can be incorrectly reported by ls but are correct when data is read. * Bugfix: Duration was not saved in cache SQLite database. * Bugfix: Issue #131: Sometimes video parameters for some Blu-ray or DVD chapters cannot be detected by FFmpeg API. Transcode then failes - fixed by using data from the Blu-ray directory or DVD IFO instead. * Lowest supported FFmpeg API version raised to 4.1.8 "al-Khwarizmi". * Dropped libavresample support, library was removed from FFmpeg API after 3.4.9. * Deprecated previous channel layout API based on uint64 bitmasks. * Deprecated swr_alloc_set_opts() and swr_build_matrix(). * Going C++17 now: The packet queue has been recoded in C++17 to support external subtitles files. As C++17 is required now, why not go all the way: Starting to replace legacy C++ and somewhat C-like parts with real C++. * Using std::shared_ptr to ensure proper memory allocation/free. Important changes in 2.9 (2022-02-16): * Feature: Issue #97: Added options to chose different codecs. The audio codec can be selected with --audiocodec, for videos use --videocodec. * Feature: Issue #109: Allow user defined file extensions for source files. By default, only standard extensions are accepted, i.e., mp4, ts, avi etc. Arbitrary file extensions can be defined now, e.g. using --extensions=xxx,abc,yxz,aaa to also convert files ending with .xxx, .abc and so on. * Feature: Issue #121: Added MKV support. New format can be selected with --desttype=mkv. * Bugfix: Issue #112: Fixed Docker detection. * Bugfix: Issue #110: Docker build command failed, added missing libchardet and allow libdvdread4 or *8 to be used, whatever available. * Bugfix: Fixed crash when video had no audio. * Bugfix: Issue #112: Fixed access problems with frame sets and HLS. * Bugfix: Issue #119: Fixed problem that caused frame set generation to fail sometimes. * Bugfix: Fixed JPG frame set generation. Suddenly FF_COMPLIANCE_UNOFFICIAL was required to have FFmpeg API accept the codec. * Enhancement: Issue #67: Enhance file size prediction. * Bugfix: Need to synchronise screen log. Concurrent entries by separate flags created garbled output. * Bugfix: Avoid creating an HLS segment number which is out of bounds (higher than the expected number of segments). * Enhancement: Removed QMake support, replaced with CMake. Important changes in 2.8 (2021-11-29): * Bugfix: Issue #102: Not all SQL queries where case sensitive, causing cache confusion. Several database entries were created, but only one was updated. Made all queries case sensitive. * Bugfix: Issue #91: Fixed HLS problems with cache causing garbled videos and hick-ups in audio. Make sure that the cache is clean when transcoding starts. There should be no remains left, all segments must be created or recreated anyway. Do not append to cache when writing. This caused cache files to become larger and larger. * Enhancement: Issue #103: If requested HLS segment is less than X (adjustable) seconds away, discard seek request. Segment would be available very soon anyway, and that seek makes a re-transcode necessary. Can be set with --min_seek_time_diff. Defaults to 30 seconds. * Feature: Issue #105: Added Free Lossless Audio Codec (FLAC) support. Activate with *--desttype=FLAC*. * Feature: Issue #101: Sample format for audio files can be selected via command line with --audiosamplefmt. Possible values are 0 to use the predefined setting, 8, 16, 32, 64 for integer format, F16, F32, F64 for floating point. Not all formats are supported by all destination types, selecting an invalid format will be reported as error and a list of values printed. Defaults to 0 (Use same as source or the predefined format of the destination if source format is not possible). Important changes in 2.7 (2021-11-08): * Bugfix: Issue #92: Fixed crash when hardware decoding failed. The problem is that the FFmpeg API very late reports that it cannot decode the file in hardware. To find out about that, the source file must be decoded until the first video frame is encountered. It would be very time consuming to do this on every file (decode until it is clear that the file is supported, then actually start transcoding it from scratch). There is no feasible way to automatically handle the situation. To get around this a --hwaccel_dec_blocked parameter has been added. If hardware decoding fails, check the log for a message similar this: "[vp9 @ 0x7fe910016080] No support for codec vp9 profile 0." If VP9 profile 0 is not supported, the parameter would be: --hwaccel_dec_blocked=VP9:0 This will tell FFmpegfs to decode the file in software. To block VP9 as a whole, the parameter would be --hwaccel_dec_blocked=VP9. To block both profile 0 and 1, use --hwaccel_dec_blocked=VP9:0:1. The parameter can be repeated to block even more codecs. * Bugfix: Issue #96: Fixed potential buffer overrun and crash when reading corrupted input files. * Enhancement: Issue #99: Report command line error if --desttype specifies audio format first, or if the second format is not audio only. Avoid misinterpretations. For example, --desttype=aiff+mov would create MOV files out of any input. Correct would be --desttype=mov+aiff which will create MOV files out of videos and AIFF from audio files, as expected. Important changes in 2.6 (2021-09-04): * No new features, just a bugfix. See V2.4 for details. Important changes in 2.5 (2021-06-18) * Feature: Issue #63 - Hardware acceleration for encoding/decoding is partly implemented, VAAPI/MMAL/OMX/V4L2 are currently available only. - Supported hardware: V4L2/VAAPI (Intel) and V4L2/MMAL/OMX (Raspberry). - VAAPI: H264, H265/HEVC, MPEG-2 and VP-8 decoding and H264 encoding. - VAAPI: MJPEG and VC-9 do not work (yet). - MMAL: H264, MPEG-2, MPEG-4 and VC1 decoding. - OMX: H264 encoding. - V4L2: H263, H264, H265, MPEG1/2/4, VC-1, VP8/9 encoding/decoding. * Feature: Added unit tests for hardware acceleration. Failing tests will report as SKIPPED and not fail the whole test. * Note: Which hardware en/decoder actually works depends on what your hardware supports. * Call for testers: Have a CUDA capable graphics adapter and interested in testing? Please write me an E-Mail. Important changes in 2.4 (2021-09-04) * Bugfix: Issue #90: Make sure that one keyframe gets inserted at the start of each HLS segment. Important changes in 2.3 (2021-06-11) * Enhancement: Issue #80: Open input video codec only if target supports video. Saves resources: no need to decode video frames if not used. * Enhancement: Issue #81: If source format has no audio, and the target supports no video (e.g.WAV/MP3), the files have shown up zero sized. These will now not be visible when doing ls. When trying to open them "File not found" will be returned. * Added "configure --enable-debug" to create binaries with debug symbols. Defaults to the optimised version. * Feature: Issue #73: Cue sheet tracks now play "gapless" if played in order. Whenever a track is started, the next track will automatically be transcoded as well. * Feature: Issue #66 and issue #82: Added cue sheet support. If a file with cue extension is found with the same name as a media file or if a cue sheet is embedded into it (a tag named CUESHEET), tracks defined in it will show up in a virtual directory. * Feature: Issue #83: Character conversion for cue sheet files. Automatically detects the character encoding of the cue sheet. and converts as necessary. * Feature: Issue #78: Duplicate ARTIST to ALBUMARTIST tag if empty * Feature: Issue #79: Added Docker support. * Fixed deprecation: 2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h Deprecated av_init_packet() * Fixed API compatitbility: Many pointers made const as of 2021-04-27. Although reasonable, this breaks API compatibility with versions older than 59.0.100, * Bugfix: find_original "fallback" method did not correctly handle the new filename format (extension added, not the original one replaced). * Bugfix: Issue #87: Segments are now properly separated, making sure that e.g. segment 3 only goes from 30 seconds up to 40 (including 30, but not 40 seconds). * Bugfix: Issue #88: HLS audio and video now stay in sync after longer playback (more than 30 minutes) or after seek operations. Important changes in 2.2 (2021-02-06) This is a maintenance release, bugfixes only. * Bugfix: Issue #75: Fix crash when opening mp3 output with Dolphin. * Bugfix: Issue #70: Possible crash in Buffer::init: Should not assert if duration is 0 (and thus segment count 0). Report internal error and go on. * Bugfix: Issue #70: Do not set duration to 0 from cache but leave unchanged. Caused HLS transcoding to fail if more than one transcoder was concurrently started. * Bugfix: Possible crash in transcoder_thread: Decoder object could have been used after being freed. * Bugfix: Stupid blooper. WAV and AIFF size was always calculated for a mono file, thus for stereo files resulting in only half the correct size. * Bugfix: Corrected documentation, "make checks" should read "make check", funny this went unnoticed for over 3 years... * Bugfix: Issue #74: Album arts were only copied from MP3/4 sources. Removed restriction, if the input file contains an album art it will be copied to the target (if supported, of course, e.g., to mp3 or mp4. Ogg is not yet supported because embedding album arts in Ogg can only be done by an unofficial workaround). * Bugfix: Issue #71:: Virtual directories were missing dot and dot-dot nodes. Important changes in 2.1 (2020-12-14) * Feature: Add BLURAY_VIDEO_FORMAT_2160P (UHD) * Feature: Implemented in 1.7, removed experimental state for --win_smb_fix now. Windows seems to access the files on Samba drives starting at the last 64K segment simply when the file is opened. Setting --win_smb_fix=1 will ignore these attempts (not decode the file up to this point). * Feature: --win_smb_fix now defaults to 1 (fix on by default). Has no effect if the drive is accessed directly or via Samba from Linux. * Bugfix: Fixed grammatical error in text: It's "access to", not "access at". * Bugfix: Did not transcode some source files with invalid DTS. * Bugfix: Cosmetical - No need to log date/time twice in syslog. * Bugfix: Cosmetical - Fix man page/online help for --recodesame parameter. * Bugfix: Report correct segment duration * Bugfix: Avoid crash if opening next HLS segment failed. Should not ignore this, but report it instead and stop transcoding. * Cosmetical: Log cache close action at trace level * Cosmetical: Shorter log entry when opening cache files Important changes in 2.0 (2020-09-13) Hurray! This is release 2.0! * Feature: No new features, new release 2.0 after feature freeze. * Bugfix: Issue #64 - Fixed playback stopping prematurely on some files. Important changes in 1.99 (2020-07-18) * Libav support has been dropped. There is no activity on https://git.libav.org/?p=libav.git since 21.08.2019, and some features that FFmpegfs required were already lagging behind FFmpeg API. Supporting Libav already bound a lot of my time that I could better spend on new features or fixes for FFmpegfs. * Feature: Add PNG/BMP/JPG frame sets to 'make check'. Checks number of files and cp operations in arbitrary order with wildcards and absolute file names. * Feature: Add HLS segments to 'make check'. Checks number of segments, the segment sizes and correct file names. * Feature: Moved FFmpeg capabilities (formats) from --version/-V command to new -caps/-c parameter to keep version information brief. * Bugfix: Issue #61 - fix timeout problem when accessing JPG in arbitrary order. * Bugfix: Issue #58 - Build list when ls/cp path/to/flles/00001.png is done. Access failed because the file was not found. * Bugfix: Issue #59 - *.m3u8 files were tried to be transcoded while they should simply be passed through. Added to ignore and pass through list. * Bugfix: Issue #57 - Correctly expand tilde when ~/ is used, for example in "ffmpegfs --logfile=~/ffmpegfs.log". * Bugfix: Issue #60 - Reopen cache file after it has been closed so that image frames can be accessed after the file has been fully decoded. Important changes in 1.98 (2020-04-15) * Cool, there's an online revivew on Linux Uprising, you can read it here: https://www.linuxuprising.com/2020/03/ffmpegfs-is-fuse-based-filesystem-for.html * Support: FFmpegfs has recently been added to Debian sid and Debian Bullseye. It is also available in Ubuntu 20.04 and Linux distributions based on it. * Feature: HLS: Introducing direct segment access. If e.g. segment no. 333 is opened, ffmpegfs will seek to this position and start transcoding the segment immediately. *Please note that this feature is still experimental and may produce unexpected results.* * Support: No longer creating a user/group ffmpegfs while "make install". The user is not really required, just bloats the system's user database. * Support: Reduced quality of the test video to make the distribution archive slimmer. A full HD (1920x1280) video at 60 frames per second was a bit of an overkill anyway. 720x400 at 30 fps should be enough for everybody. * Feature: Cache dir changed to ${XDG_CACHE_HOME:-~/.cache}/ffmpegfs (as specified in the XDG Base Directory Specification). Falls back to ${HOME:-~/.cache}/ffmpegfs if not defined. Only if executed with root privileges, "/var/cache/ffmpegfs" will be used (e.g. if invoked via /etc/fstab entry). * Feature: Changed PTS generation: For DVD/Blu-ray chapters they will not start at 0 for each chapter, but at the real time the chapter begins in its title. Fixes #51. * Feature: Issue #52 - Added --audiochannels option to select a different number of channels. Defaults to 2 (stereo), may be set to 1 to create mono files. Set to 0 to keep the original number of channels. * Bugfix: Issue #46 - Ensure the selected bitrate is used. Files could become much larger than expected. There's a strange FFmpeg API behaviour behind that, added the solution used in ffmpeg.c to fix. * Bugfix: Issue #48 - Properly handle incorrectly authored DVDs with missing SRI_END_OF_CELL marks after cells. * Bugfix: Issue #49 - Avoid creating HLS last segments shorter than 200 ms, avoid stalls when the total playtime is a multiple of the segment size, e.g. 10 seconds. * Bugfix: Issue #54 - Made sure no duplicate filenames get created. This changes the output name scheme, e.g. if two files xyz.ts and xyz.wmv exist and the destination type is mp4, two output files named xyz.ts.mp4 and xyz.wmv.mp4 appear. An additional file xyz.mp4 will show up with its real name. To continue using the old names, add --oldnamescheme=1 to the command line. * Bugfix: Online help and docs state --autocopy=OFF would turn the function off, but this actually was NONE. Changed to match documentation. * Known bug: Libav support for videos seems broken. Not sure if this will be fixed or Libav be simply dropped. Important changes in 1.97 (2020-01-21) * Support: * Feature: Added support for HLS (HTTP Live Streaming). Select desttype=hls to enable. * Feature: Added checks for mov/mp4/prores/webm video format. * Bugfix: Moved video deinterlace filtering before rescaling. Deinterlace does not work properly on rescaled videos, what a suprise. Especially caused strange results on Blu-ray sources, created blurred frames if downscaled from HD to SD (or lower) before deinterlacing. * Bugfix: Avoid EINVAL errors in case the cache file ends up at zero size. Minor problem, but ugly. * Bugfix: Cache structure was not properly initialised, causing invalid values in the database. * Bugfix: Properly handle case when start_time is AV_NOPTS_VALUE (frame sets only) * Bugfix: Corrected mistake in online help: clear-cache should be clear_cache instead. * Bugfix: Issue #43 - fix garbled first 12 frames. Enhanced overall picture quality. * Workaround: Issue #45 - fix garbled Blu-ray/DVD playback. Just a workaround, the problem is somewhere else. * Due to copyright uncertanties and because the public domain license used is controversal, all demo files in test/srcdir have been replaced with own works and put under the CC0 1.0 Universal license. * Known bug: Libav support for videos seems broken. Not sure if this will be fixed or Libav be simply dropped. Important changes in 1.96 (2019-11-18) This is a pre-release towards the new version 2.0 coming up soon. * Feature: Added version check and auto update to cache database. Older database versions will now automatically upgraded to the latest structure. * Feature: Transcode videos to frame works transparently for any input now (DVD, Blu-ray and VideoCD). * Bugfix: Fix for "prores can't allocate memory #41". * Bugfix: Do not shrink cache when reopening it. * Known bug: Important changes in 1.95 (2019-05-23) This is a pre-release towards the new version 2.0 coming up soon. * Feature: Added transcode videos to frame images option. Video files can be mounted to folders with jpg/png/bmp files for each video frame. * Feature: Reencode to same file format as source file. Normally files that already have the selected output format will not be recoded. Added --recodesame option to allow transcoding to the same format. Important changes in 1.11 (2020-04-15) * Cool, there's an online revivew on Linux Uprising, you can read it here: https://www.linuxuprising.com/2020/03/ffmpegfs-is-fuse-based-filesystem-for.html * Support: FFmpegfs has recently been added to Debian sid and Debian Bullseye. It is also available in Ubuntu 20.04 and Linux distributions based on it. * Support: No longer creating a user/group ffmpegfs while "make install". The user is not really required, just bloats the system's user database. * Support: Reduced quality of the test video to make the distribution archive slimmer. A full HD (1920x1280) video at 60 frames per second was a bit of an overkill anyway. 720x400 at 30 fps should be enough for everybody. * Feature: Cache dir changed to ${XDG_CACHE_HOME:-~/.cache}/ffmpegfs (as specified in the XDG Base Directory Specification). Falls back to ${HOME:-~/.cache}/ffmpegfs if not defined. Only if executed with root privileges, "/var/cache/ffmpegfs" will be used (e.g. if invoked via /etc/fstab entry). * Feature: Changed PTS generation: For DVD/Blu-ray chapters they will not start at 0 for each chapter, but at the real time the chapter begins in its title. * Feature: Issue #52 - Added --audiochannels option to select a different number of channels. Defaults to 2 (stereo), may be set to 1 to create mono files. Set to 0 to keep the original number of channels. * Bugfix: Issue #46 - Ensure the selected bitrate is used. Files could become much larger than expected. There's a strange FFmpeg API behaviour behind that, added the solution used in ffmpeg.c to fix. * Bugfix: Issue #48 - Properly handle incorrectly authored DVDs with missing SRI_END_OF_CELL marks after cells. * Bugfix: Issue #54 - Made sure no duplicate filenames get created. This changes the output name scheme, e.g. if two files xyz.ts and xyz.wmv exist and the destination type is mp4, two output files named xyz.ts.mp4 and xyz.wmv.mp4 appear. An additional file xyz.mp4 will show up with its real name. To continue using the old names, add --oldnamescheme=1 to the command line. * Bugfix: Online help and docs state --autocopy=OFF would turn the function off, but this actually was NONE. Changed to match documentation. * Libav support has been dropped. There is not activity on https://git.libav.org/?p=libav.git since 21.08.2019, and some features that FFmpegfs required were already lagging behind FFmpeg API. Supporting Libav already bound a lot of my time that I could better spend on new features or fixes for FFmpegfs. Important changes in 1.10 (2020-01-21) * Feature: Added checks for mov/mp4/prores/webm video format. * Bugfix: Moved video deinterlace filtering before rescaling. Deinterlace does not work properly on rescaled videos, what a suprise. Especially caused strange results on Blu-ray sources, created blurred frames if downscaled from HD to SD (or lower) before deinterlacing. * Bugfix: Avoid EINVAL errors in case the cache file ends up at zero size. Minor problem, but ugly. * Bugfix: Cache structure was not properly initialised, causing invalid values in the database. * Bugfix: Skip corrupted and discarded video frames to avoid blurps. * Bugfix: Corrected mistake in online help: clear-cache should be clear_cache instead. * Bugfix: Issue #43 - fix garbled first 12 frames. Enhanced overall picture quality. * Workaround: Issue #45 - fix garbled Blu-ray/DVD playback. Just a workaround, the problem is somewhere else. * Due to copyright uncertanties and because the public domain license used is controversal, all demo files in test/srcdir have been replaced with own works and put under the CC0 1.0 Universal license. * Known bug: Libav support for videos seems broken. Not sure if this will be fixed or Libav be simply dropped. Important changes in 1.9 (2019-11-18) * Support: Code has now been tested to work on ARMv7 (32 bit). * Feature: Documented the whole project with Doxygen. * Feature: Implemented a thread pool. File open (and thread starts) are now much faster than before. The thread pool also ensures that not more threads than configured are started. Defaults to 16x the number of processor cores available. * Feature: Added Apple Lossles Audio Compression (ALAC) support. * Feature: Added optional graphviz to configure, only required for Doxygen. * Feature: Updated INSTALL.md with missing prerequisites. Rearranged paragraphs, mentioning building from git first was confusing. * Feature: Moved cache directory from rather unusual /tmp/ffmpegfs location to /var/cache/ffmpegfs. * Bugfix: Fixed several warnings if compiled for 32 bit. * Bugfix: Removed unused xmlint prerequisite from configure. * Bugfix: Video and audio was out of sync on android devices. * Bugfix: Fix crash when Blu-ray video without audio was opened. * Bugfix: Cache files were left at the predicted size if it was larger than the result size. Size is now correctly capped to the correct result size. * Fixed deprecation in fpcompare: 2018-04-01 - f1805d160d - lavfi 7.14.100 - avfilter.h Deprecate use of avfilter_register(), avfilter_register_all(), avfilter_next(). Added av_filter_iterate(). Important changes in 1.8 (2019-05-23) This is a bugfix/maintenance release with only a few minor features added. * Feature: Added Doxygen documentation. * Feature: Report all errors together with error number. * Feature: More detailed error handling. * Feature: Added "make wipe-all" to really clean up all generated files. * Bugfix: Do not report "Not a directory" error for virtual directories. Not an error, it's a feature. * Bugfix: Fixed make dist: checks did not work. * Bugfix: Fixed make clean: some Doxgen files were not removed. * Bugfix: Properly catch "no memory" errors. * Bugfix: Deinterlacing filter only worked for YUV videos. * Bugfix: Removed the check for invalid chapters - did not work as expected. * Bugfix: Wrong bitrate for PAL (was 30 fps instead of 25 fps) and assume PAL if bits are invalid. * Bugfix: Fixed potential crash in DVD handling. * Bugfix: Do not add libavresample if libswresample is present. Saves build time. * Bugfix: Removed dependency on unused libdvdnav. Saves buildtime. * Bugfix: Fixed two (probably rare but possible) crashes. * Bugfix: Image now not converted when the destination type is a video format. Important changes in 1.7 (2019-02-26) * Feature: Windows seems to access the files on Samba drives starting at the last 64K segment simply when the file is opened. Setting --win_smb_fix=1 will ignore these attempts (not decode the file up to this point). * Feature: Added "Auto Stream Copy" option. This will copy audio or video streams to the output file rather than recoding. Reduces computing time and CPU load. * Feature: Sped up DVD/Blu-ray/VCD access drastically. FUSE calls getattr like crazy, using cached data now. * Feature: Extended stream copy: now any codec can be copied if supported by target container using --autocopy=MATCH option. * Feature: Added full virtual titles to DVD, Blu-ray and Video CD. Creating one virtual file per chapter is very nice for music disks as it allows access to every single title very easily. For movies it is better to have it in one piece. * Feature: Enhanced win_smb_fix=1 functionality to hopefully catch all Windows incidents. * Feature: Added min_dvd_chapter_duration parameter. DVD chapters shorter than this will be ignored. Defaults to 1 second. * Bugfix: Decimal seconds in duration was wrong. Fixed. * Bugfix: This was a bad one: The application crashed with a protection fault if there was not enough disk space on the cache drive. This is now properly reported and transcoding aborted. Thanks for Muf for reporting the issue. * Bugfix: Fixed crash when DVD nav packet was missing * Bugfix: Fixed clang compatibility. Project did not build with clang, this has gone unnoticed for a while... * Bugfix: Fixed terrible miscalculation of DVD play time. No one told me the values are BCD, not decimal... * Bugfix: Fixed conversion of DVD chapters consisting of more than one cell. * Bugfix: Fixed pts/dts generation * Known bug: Some Blurays are not recoded properly. The resulting files are much larger than expected, as a matter of fact larger than the source (birates > 27 MBit). Happens only for some sources. Important changes in 1.6 (2019-01-11) * Feature: Made all parameters like --profile case insensitive. * Feature: Added Apple MOV, Apple Prores and Apple AIFF formats. * Feature: Enable "Smart Transcoding": To enable, specify --desttype=AUDIO+VIDEO, e.g. --desttype=MOV+AIFF will automatically convert video files to MOV and audio to AIFF. * Feature: New parameter --level allows selecting a profile for Prores: proxy, lt, standard or hq. * Feature: Added MOV target format * Feature: Added AIFF target format * Feature: Smart conversion option ("Smart Transcode") * Feature: Added level option for Prores * Feature: Added Apple Prores support * Feature: Do not "upgrade" mono audio to stereo. Create mono audio stream. * Feature: Speed up DVD conversion significantly by using IFO data instead of probing each chapter. * Feature: Added DVD angle support.Chapters with angles will show as separate files. * Feature: Speed up Blu-ray conversion significantly by using the directory instead of probing each chapter. * Feature: Speed up VCD conversion. * Feature: EXPERIMENTAL: Windows seems to access the files on Samba drives starting at the last 64K segment simply when the file is opened. Setting win_smb_fix=1 will ignore these attempts (not decode the file up to this point). * Bugfix: Suspend/decoding timeouts did not work. Transcoding never timed out... * Bugfix: Fixed memory leak when using the deinterlace option. * Bugfix: Deinterlace did not always work. * Bugfix: Fixed memory leaks. * Bugfix: Report correct error if directory mounted twice. * Bugfix: Silence chatty log outputs. * Bugfix: Fixed GPF when accessed. * Bugfix: Fixed GPF when channel layout changed during playback. * Bugfix: If transcode failed, the error was sometimes not reported to the client process, causing it to hang. * Bugfix: Sometimes tried to create WebM files with zero width (failed to transcode). * Bugfix: Avoid mpeg-4 profile/pixel format mismatches. * Bugfix: Fixed creating zero sized files from DVDs when chapter playtime < 1 s. * Bugfix: Update timeout when client just holds the file open without reading. * Bugfix: Properly honour client exit (end wait for data). * Bugfix: Issue #23: do not block open with a read waiting for data. * Bugfix: WebM files had no duration. Added duration to Matroska container. * Bugfix: Fixed EOF detection for Blu-ray chapters. * Bugfix: Force mpeg format for DVDs to avoid misdetections. * Bugfix: Force mpegts format for Blurays to avoid misdetections. * Bugfix: Take DVD chapter duration from IFO as probed playtime from VOB segment is sometimes wrong. * Bugfix: Take Blu-ray chapter duration from directory as probed playtime from MTS segment is sometimes wrong. * Fixed deprecation: Important changes in 1.5 (2018-06-08) * Feature: Added WebM output format using VP9 video and Opus audio codec. * Feature: Input format check is now done by FFmpeg, that means if the input file is supported by FFmpeg, it will be transcoded. For performance, files are not opened and scanned, the file's extension is checked only (e.g. mp3, wav, rm etc.). * Feature: Replaced mutagen-inspect with internal code which also works with WAV and WebM. test_tags works for all formats now. python-mutagen is no longer required. * Feature: Check if sample rate or sample format is supported by audio codec and change if necessary. Avoid transcode fails e.g. if 44.1KHz sampling rate is requested but not supported (e.g. OPUS only supports 8K, 16K, 24K or 48K). Select nearest supported sample rate or sample format. Important changes in 1.4 (2018-05-30) * The timeout for suspend and abort have been changed. Decoding will now be suspended after 15 seconds (formerly 2 minutes). If no activity is detected after further 15 seconds (formerly 3 minutes) decoding will be aborted. This will only happen if the file is actively closed, so if playback is simply paused decoding will continue. The new limits proofed to be fine and reduce system load significantly. * Feature: Enhanced logging, added a colour profile to screen logs and raised or lowered some messages log levels appropriately. Also added a few informative log messages. * Feature: Blu-ray support: Detect Blu-ray files in directories and list separate chapters and titles as separate files. * Feature: DVD support: Detect IFO files and list separate DVD chapters or programs as separate files. * Feature: S/VCD support: Detect Video CDs in directories and list chapters as separate files. * Feature: Changed the fuse operations code module to c++ (minimise danger of memory leaks or buffer overruns) * Feature: Added --enablescript option that will add virtual index.php to every directory. It reads scripts/videotag.php from the ffmpegs binary directory. This can be very handy to test video playback. Of course, feel free to replace videotag.php with your own script. * Feature: By default ignore decoding errors so that transcoding does not stop just because a few frames are garbled. Can be disabled with the "decoding_errors" option. * Bugfix: Sometimes the pixel format could not be detected, raising an assert/application crash with newer FFmpeg API versions. Fixed by setting it to the output format. This will mostly ever create defect encoder results, but at least the FFmpegfs process will not be shut down writing a core file. * Bugfix: Set mp4 profile to high/3.1 to enable playback under enable playback under Win 7. * Bugfix: Fixed formatting of help text. * Workaround: Fixed Libav support: Deinterlace code did not compile. Should be possible to get it working, but for now it is simply disabled. But honestly I don't want to get it working. I am thinking of dropping Libav completely. * Fixed deprecation: 2018-xx-xx - xxxxxxx - lavf 58.9.100 - avformat.h Deprecate use of av_iformat_next(), av_oformat_next(). Important changes in 1.3 (2018-05-04) Changes in this release: * Feature: Added option to deinterlace video if required (will improve picture quality when viewed in browsers via HTML5. * Feature: Set stream duration from source file as hint for encoder. * Feature: Enhanced logging output for better readability. * Feature: Added pre-buffer option. Files will be decoded up to this limit first before made accessible. This ensures smooth startup. * Feature: Enhanced configure that checks whether libavresample or libswresample is present. * Feature: Improved recoding detection. Files are recoded if the source file size or timestamp changes, or different encoding options are selected (e.g. a different bitrate, vidoe size etc.). * Feature: Allow profiles for target browsers or players. Currently selectable: NONE no profile FF optimise for Firefox EDGE optimise for MS Edge and Internet Explorer > 11 IE optimise for MS Edge and Internet Explorer <= 11 CHROME Google Chrome SAFARI Apple Safari OPERA Opera MAXTHON Maxthon Defaults to NONE * Feature: enhanced error output for SQLite (if supported by library version) * Feature: copy album arts from source if supported by target format (MP3/4). Use --noalbumarts parameter to disable, e.g., for streaming files. * Bugfix: Only four lines of code: Avoid locking other clients when trying to get the size of a file currently being decoded. Also speeds up directory access quite significantly. * Bugfix: Fixed lost frames at end of files (files where cut short) * Bugfix: Fixed crash when video files were transcoded to audio only (e.g. mp3) * Bugfix: Make WAV size prediction more accurate * Bugfix: Recode existing cache files if not found in database (handle database inconsistencies). * Bugfix: Fixed sharing problems with sqlite database when accessed by multiple instances. * Bugfix: Files were truncated (mostly mp3) when cp'ed directly while transcoding. Now files will have the final size as expected. * Bugfix: Create valid WAV header for files transcoded on-the-fly. * Bugfix: Ensure a file gets recoded when the last attempt failed. * Bugfix: Ensure that transcoding errors get passed on to calling process, e.g. cp or rsync. Otherwise copy operations might not notice that the file is invalid. FFMpeg has evolved, many API parts have been deprecated and will be dropped soon. This release adopts these changes by conditionally using the new APIs from the FFMpeg version on that they are available. * Fixed deprecation: 2017-xx-xx - xxxxxxx - lavr 4.0.0 - avresample.h Deprecate the entire library. Merged years ago to provide compatibility with Libav, it remained unmaintained by the FFmpeg project and duplicated functionality provided by libswresample. swresample is the preferred library in FFMpeg, so it will be used if compiled with FFMpeg by default, even before being deprecated. With libav, which makes libavresample available only, the latter will be used. * Fixed deprecation: 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h Added a new audio/video encoding and decoding API with decoupled input and output -- avcodec_send_packet(), avcodec_receive_frame(), avcodec_send_frame() and avcodec_receive_packet(). * Fixed deprecation: 2018-01-xx - xxxxxxx - lavf 58.7.100 - avformat.h Deprecate AVFormatContext filename field which had limited length, use the new dynamically allocated url field instead. * Fixed deprecation: 2018-xx-xx - xxxxxxx - lavf 58.9.100 - avformat.h Deprecate use of av_register_input_format(), av_register_output_format(), av_register_all(), av_iformat_next(), av_oformat_next(). NOTE: av_iformat_next/av_oformat_next() lack a replacement (currently still used in tools/probetest.c "static void probe" in 58.10.100) * Fixed deprecation: 2018-xx-xx - xxxxxxx - lavc 58.10.100 - avcodec.h Deprecate use of avcodec_register(), avcodec_register_all(), av_codec_next(), av_register_codec_parser(), and av_parser_next(). Added av_codec_iterate() and av_parser_iterate(). * Fixed deprecation: 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h Added AVStream.codecpar, deprecate AVStream.codec. * Fixed deprecation: 2018-04-01 - f1805d160d - lavfi 7.14.100 - avfilter.h Deprecate use of avfilter_register(), avfilter_register_all(), avfilter_next(). Added av_filter_iterate(). nschlia@oblivion-software.de Important changes in 1.2 (2018-04-03) Changes in this release: * Fixed issue #4: File size was wrong when file was first accessed. * Fixed issue #5: Fixed crashes when transcoding. * Feature: None. This a bug fix release. nschlia@oblivion-software.de Important changes in 1.1 (2018-03-19) Changes in this release: * Fixed: make clean for checks did not delete all logs * Feature request #1: WAV output support (by github user Avuton) * Feature: OGG output support * Fixed: Did not write correct ID3v2 headers (thanks for the fix to gitbug user sdrik) * Feature: Include configure command line arguments and host OS in "-V" option. * Feature: Added --videoheight=HEIGHT and --videowidth=WIDTH parameters to allow video size change. If source video is larger it will be scaled down. If already smaller the size will be left as is. You should only set one parameter, if both are set the aspect ratio may be changed and the result look strange. * Feature: Added clear_cache option to clear the whole cache everytime the application is started. * Note: Dropped ISMV option as it did not work as expected, especially creating an mp4 on-the-fly did not work. The streamlined mp4 format already does the work. nschlia@oblivion-software.de Important changes in 1.00 (2018-01-19) Changes in this release: * Cheers! Made it! This is release version 1.0, the first production release. * Fixed compile for FFmpeg versions prior to 3.0. nschlia@oblivion-software.de Important changes in 0.95 (2017-12-29) NOTE: The cache format has changed and is not compatible with prior versions. Please do a "rm -Rf /tmp/ffmpegfs" before starting this version to create a new cache. TODO: see new TODO file from now on. Changes in this release: * Heureka! This is the first beta release towards V1.0. From now on only bug fixes will go into the code. * Implemented automatic cache maintenance (ensure cache size, disk space, delete outdated entries). * Fixed checks ("make check" works now. Known problem: mp4 sizes seem to differ when using different FFmpeg releases. Will be addressed soon.). * Fixed mp3 format (The mp3s were completely braindead.). * Fixed suspend timeout coming although file currently accessed. * Fixed crash during shutdown because sqlite was used too late. * Fixed handling of multiple destination types in same cache. * Properly pass on errno/save and store in cache database. * Additional database fields for target parameters (bitrate, error code etc.). Will be used in later versions to detect if a file needs re-transcoding. * Files will be recoded if source file changes (size or timestamp). * Report compiler version when run with -v parameter. * Set gcc optimisation higer (now -o3). nschlia@oblivion-software.de Important changes in 0.94 (2017-12-19) Changes in this release: * Decided to rename mp3fs to FFmpegfs because the project has grown far beyond a simple clone of mp3fs and the changes it will surely never be pushed back to the parent project. A big random applause to the makers of mp3fs for giving me a good head start! * Cache management: Keep cache size within limit, ensure min. disk space, prune oldest entries * Fixed many crashes * Fixed memory leaks * Fixed mp4 check suite * Added mp3 check suite * Added --disable_cache parameter * Added --prune_cache parameter. Starts cache pruning manually. * TODO: Add intelligent stream copy, e.g., if transcoding a transport stream that already represents a H264 video stream it would be possible to simply repackage it to a mp4 container without recoding. * TODO: Cover art support still missing nschlia@oblivion-software.de Important changes in 0.92-FFmpeg (2017-12-14) Changes in this release: * Files will now be decoded to /tmp/ instead of memory. * Full support for video files (mp4 target only). * Removed --statcachesize option as caching will be handled by new disk cache. * Fixed sync problems while accessing the same file concurrently * Fixed many crashes due to incorrect usage of FFmpeg * Cache directory user defineable * Delete cache entries after predefined time * Detect if file has changed and re-transcode * Limit cache size * Suspend transcode after n seconds * Exit transcode after n seconds * TODO: Add intelligent stream copy, e.g., if transcoding a transport stream that already represents a H264 video stream it would be possible to simply repackage it to a mp4 container without recoding. nschlia@oblivion-software.de Important changes in 0.91-FFmpeg (2017-10-04) Added FFmpeg for decoding and encoding. Changes in this release: * Replaced decoding and encoding with FFmpeg. * Removed discrete FLAC/Ogg/MP3 libraries. FFmpeg supports a wide range of formats, including these, so they are no longer required. * TODO: Supports a huge number of file formats now (run "mp3fs --codecs" for a list). * The standard target format is mp4 now (use "-o mp3" or "--output mp3" to change back to mp3). * Many other formats are possible, e.g. "-o ogg", they will be added later because due to the manner in which Fuse requires access to the files they need to be implemented with care. * See manual or run "mp3fs -h" for even more new options. * Run "mp3fs -f" to get a list of all supported input formats. * Caching of file attributes is now the default as if "--statcachesize=500" was given. Added "--statcachesize=0" to turn off (not really a bright idea, though). * TODO: Files will now be decoded to /tmp/ instead of memory if larger than 100MB. Added "--maxmemcache=X" to change. Examples for X: 80000, 80MB or 1GB. Do not set to a too large value as you may run out of memory. * TODO: Support for video files. * TODO: Add intelligent stream copy, e.g., if transcoding a transport stream that already represents a H264 video stream it would be possible to simply repackage it to a mp4 container without recoding. nschlia@oblivion-software.de Important changes in 0.91 (2014-05-14) This is mainly bug fixes. Changes in this release: * Fixed a segfault caused by an overflow reading the list of available decoders. * A number of problems with the previous distribution tar are now fixed. * The output of `mp3fs --version` has been made more complete. Important changes in 0.9 (2014-04-06) This is a major new release, and brings us very close to a 1.0 release! Changes in this release: * All transcoding code has been completely rewritten. Encoding and decoding have been abstracted out into base classes defining interfaces that can be implemented by different codec classes, with just a FLAC decoder and MP3 encoder at the moment. * The build system has been modified as well to support this usage. * A number of small bugs or code inefficiencies have been fixed. Important changes in 0.32 (2012-06-18) This release has a lot of bug fixes and some code cleanup. Changes in this release: * The file size calculation should always be correct. * A crash affecting programs like scp that might try to access past the end of the file has been fixed. * Too many other little fixes were made to list here. See the ChangeLog for full details. Important changes in 0.31 (2011-12-04) This is a minor update, with bug fixes and a new feature. Changes in this release: * The ReplayGain support added earlier now can be configured through the command line. * Filename translation (from .flac to .mp3) is now fixed on filesystems such as XFS that do not populate dirent.d_type. * A couple other minor bugs fixes and changes were made. Important changes in 0.30 (2010-12-01) This is a major new release, and brings mp3fs much closer to an eventual 1.0 release. Changes in this release: * Support for additional metadata tags has been added. (From Gregor Zurowski) * Documentation improvements: the help message is more useful, and a man page has been added. * Choosing bitrate is now done with a command-line or mount option, rather than the old comma syntax. * A new option to select LAME encoding quality is now available. (From Gregor Zurowski) * Debug output can be enabled at runtime. * Old external libraries included in distribution (StringIO, talloc) have been removed and replaced. * Numerous bug fixes have been made. (Some from Gregor Zurowski) ... 0.01 Initial release 06/Aug/2006 ======================================================================== Copyright (C) 2010-2014 K. Henriksson Copyright (C) 2017-2026 Norbert Schlia (FFmpeg support) This documentation may be distributed under the GNU Free Documentation License (GFDL) 1.3 or later with no invariant sections, or alternatively under the GNU General Public License (GPL) version 3 or later. ffmpegfs-2.50/FEATURES.md0000664000175000017500000005064415052412650010471 Hardware Acceleration ===================== The hardware acceleration feature depends heavily on the hardware used. As this is a personal project, I cannot go out and buy and test all possible devices. So I'll have to rely on you to report your issues so we can iron them out. Even different hardware supporting the same API may behave differently. Sometimes the format range is not the same, sometimes subfeatures are missing, and so on. ## How It Works Acceleration is done by specialised graphics adapters. The FFmpeg API can use several types using a range of APIs. As of today, even cheap on-board chips can do hardware acceleration. Here is an incomplete list. Hardware acceleration using hardware buffered frames: * VAAPI: Intel, AMD (Decoders: H.264, MPEG-2, MPEG-4 part 2, VC-1. H.265, H.265 10-bit on recent devices. Encoder: H.264, H.265, MPJEPG, MPEG-2, VP8/9, MPEG-4 part 2 can probably be enabled.) * VDPAU: Nividia, AMD (H.264, MPEG-1/2/4, and VC-1) * CUDA: Compute Unified Device Architecture (Decoders: VP9, H.264, MPEG-2, MPEG-4. Encoding: H.264, H.265), see https://developer.nvidia.com/ffmpeg and https://en.wikipedia.org/wiki/CUDA * QSV: QuickSync, see https://trac.ffmpeg.org/wiki/Hardware/QuickSync * OPENCL: Open Standard for Parallel Programming of Heterogeneous Systems, see https://trac.ffmpeg.org/wiki/HWAccelIntro#OpenCL * VULKAN: Low-overhead, cross-platform 3D graphics and computing API, requires Libavutil >= 56.30.100, see https://en.wikipedia.org/wiki/Vulkan_(API) These use software frames: * v4l2m2m: Intel (Encoders: H.263, H.264, H.265, MPEG-4, VP8). Decoders: H.263, H.264, H.265, MPEG-1, MPEG-2, MPEG-4, VC-1, VP8, VP9.) * OpenMAX: Encoding on Raspberry (H.264, MPEG-4. Requires key to unlock.) * MMAL: Decoding on Raspberry (H.264, MPEG-2, MPEG-4, VC-1. Requires key to unlock.) More information can be found at https://trac.ffmpeg.org/wiki/HWAccelIntro. ## Current Implementation ### Supported Hardware Acceleration APIs These APIs are implemented and tested. VAAPI mostly targets Intel hardware, but there are other hardware vendors that offer support now. MMAL and OpenMAX are supported by Raspberry PI boards. | API | Decode | Encode | Description | Details see | | --------- | ------ | ------ | ----------------------------------------------- | ------------------------------------------------------------ | | **VAAPI** | x | x | Video Acceleration API (VA-API), formerly Intel | https://en.wikipedia.org/wiki/Video_Acceleration_API
https://trac.ffmpeg.org/wiki/Hardware/VAAPI | | **MMAL** | | x | Multimedia Abstraction Layer by Broadcom | https://github.com/techyian/MMALSharp/wiki/What-is-MMAL%3F
http://www.jvcref.com/files/PI/documentation/html/ | | **OMX** | x | | OpenMAX (Open Media Acceleration) | https://en.wikipedia.org/wiki/OpenMAX | #### Tested On | System | CPU | GPU | APIs | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------- | ------------ | | Debian 10 | Intel Core i5-6500 CPU @ 3.20GHz | Intel HD Graphics 530 (rev 06) | VAAPI | | Debian 11 | Intel Core i5-8250U CPU @ 1.60GHz | Intel UHD Graphics 620 (rev 07) | VAAPI | | Debian 11 | Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz | NVIDIA GP108M, GeForce MX330 | VAAPI | | Raspbian 10
Raspberry Pi 2 Model B Rev 1.1
Raspberry Pi 3 Model B Plus Rev 1.3 |
ARMv7 Processor rev 5 (v7l)
ARMv7 Processor rev 4 (v7l) | | OpenMAX/MMAL | ## Planned Hardware Acceleration APIs There are several more APIs that could be added. Currently, this is not possible due to a lack of hardware. | API | Decode | Encode | Notes | Details see | | ----------- | ------ | ------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | **CUDA** | | | Compute Unified Device Architecture | https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html
https://en.wikipedia.org/wiki/CUDA
https://developer.nvidia.com/ffmpeg | | **OPENCL** | | | Open Standard for Parallel Programming of Heterogeneous Systems | https://trac.ffmpeg.org/wiki/HWAccelIntro#OpenCL | | **VDPAU** | | | Video Decode and Presentation API for Unix | https://en.wikipedia.org/wiki/VDPAU | | **QSV** | | | QuickSync | https://trac.ffmpeg.org/wiki/Hardware/QuickSync | | **V4L2M2M** | | | v4l2 mem to mem (Video4linux) | | | **VULKAN** | | | Low-overhead, cross-platform 3D graphics and computing API, requires Libavutil >= 56.30.100 | https://en.wikipedia.org/wiki/Vulkan_(API) | ## Hardware Encoding This version has been tested with VAAPI (Debian) and OpenMAX (Raspberry). It may be possible that other APIs work, but this has not been confirmed yet. To enable hardware support, use these parameters respectively (of course, use only one): ``` --hwaccel_enc=VAAPI --hwaccel_enc=OMX ``` If your system supports VAAPI: * It could be possible that the rendering device on your system goes by a different name than the default "/dev/dri/renderD128". You can use the --hwaccel_enc_device parameter to set it. * Depending on what your renderer supports, setting a bitrate will fail. On my test system, for example, CQG (Constant Quantisation parameter) is the only valid rendering control mode. The driver ignores bitrate settings and accepts the qp option only. As found in libavcodec/vaapi_encode.c: Rate control mode selection: * If the user has set a mode explicitly with the rc_mode option, use it and fail if it is not available. * If an explicit QP option has been set, use CQP. * If the codec is CQ-only, use CQP. * If the QSCALE avcodec option is set, use CQP. * If bitrate and quality are both set, try QVBR. * If quality is set, try ICQ, then CQP. * If bitrate and maxrate are set and have the same value, try CBR. * If a bitrate is set, try AVBR, then VBR, then CBR. * If no bitrate is set, try ICQ, then CQP. At the moment, this is hardwired into the code. None of these values can be controlled now by the command line. This is planned, of course, but not implemented in the current version yet. ## Hardware Decoding This version has been tested with VAAPI (Debian) and MMAL (Raspberry). It may be possible that other APIs work, but this has not been confirmed yet. To enable hardware support, use these parameters respectively (of course, use only one): ``` --hwaccel_dec=VAAPI --hwaccel_dec=MMAL ``` If your system supports VAAPI: - It could be possible that the rendering device on your system goes by a different name than the default "/dev/dri/renderD128". You can use the --hwaccel_enc_device parameter to set it. On slow machines like the Raspberry, this should give an extra kick and also relieve the CPU from load. On faster machines, this impact may be smaller, yet noticeable. The decoding part is a bit tricky. If encoding is set to hardware, and this hardware is there and capable of encoding, it will work. If hardware decoding is possible, it depends on the source file. Therefore, the file needs to be checked first and then it needs to be decided if hardware acceleration can be used or if a fallback to software is required. FFmpeg requires that to be set via the command line, but FFmpegfs must be able to decide that automatically. ### TODOs Doing both de- and encoding in hardware can make costly transfers of frames between software and hardware memory unneccessary. It is not clear, at the moment, if it is possible to keep the frames in hardware as FFmpegfs does some processing with the frames (for example, rescaling or deinterlacing), which probably cannot be done without transferring buffers from hardware to software memory and vice versa. We'll see. As seen above, selecting a target bitrate turns out to be a bit tricky. I'll have to work out a way to reach the desired bitrate in any case (no matter if the hardware supports CQP, VBR, CBR, ICQ, or AVBR). On the other hand, everything seems to work and there are no show stoppers in sight. Sheesh, wiping the sweat off my chin :) HTTP Live Streaming =================== FFmpegfs supports HLS (HTTP Live Streaming). FFmpegfs will create transport stream (ts) segments and the required m3u8 playlists. For your convenience, it will also offer a virtual test.html file that can playback the segments using the hls.js library (see https://github.com/video-dev/hls.js/). To use the new HLS feature, invoke FFmpegfs with: ffmpegfs -f $HOME/test/in $HOME/test/out -o allow_other,ro,desttype=hls Please note that this will only work over http because most browsers refuse to load multimedia files from the local file system, so you need to publish the directory on a web server. Security restrictions prevent direct playback from a disk. Simply navigate to the directory and open test.html. Cue Sheets ========== Cue sheets, or cue sheet files, were first introduced for the CDRWIN CD/DVD burning software. Basically, they are used to define a CD or DVD track layout. Today, they are supported by a wide range of optical disk authoring applications and, moreover, media players. When a media file is accompanied by a cue sheet, its contents are read and a virtual directory with separate tracks is created. The cue sheet file must have the same name but the extension ".cue" instead. It can also be embedded into the media file. The directory is named after the source media, with an additional ".tracks" extension. If several media files with different extensions exist, for example, different formats, several ".tracks" directories will be visible. Example: myfile.mp4 myfile.ogv myfile.cue If the destination type is TS, the following files and directories will appear: myfile.mp4 myfile.mp4.ts myfile.ogv myfile.ogv.ts myfile.cue myfile.mp4.tracks/ myfile.ogv.tracks/ Tracks defined in the cue sheet will show up in the *.tracks subdirectories. Selecting Audio and Video Codecs ========== Some new codec combinations are now possible (the default codecs are in bold): | Formats | Audio Codecs | Video Codecs | | ------- | ----------------- | ---------------------------- | | MP4 | **AAC**, MP3 | **H264**, H265, MPEG1, MPEG2 | | WebM | **OPUS**, VORBIS | **VP9**, VP8 | | MOV | **AAC**, AC3, MP3 | **H264**, H265, MPEG1, MPEG2 | | TS, HLS | **AAC**, AC3, MP3 | **H264**, H265, MPEG1, MPEG2 | For audio, the codec can be selected with --audiocodec. For videos, use --videocodec. Without these parameters, FFmpegfs will use the codecs as before (no change). Please note that hardware acceleration might not work, e.g., my hardware encoder supports H264 but not H265. So even though H265 creates much smaller files, it takes 10 times longer to transcode. Building A Docker Container ========== FFmpegfs can run under Docker. A Dockerfile is provided to build a container for FFmpegfs. Change to the "docker" directory and run docker build --build-arg BRANCH=master -t nschlia/ffmpegfs . Depending on the machine speed, this will take quite a while. After the command is completed, the container can be started with docker run --rm \ --cgroupns host \ --name=ffmpegfs \ --device /dev/fuse \ --cap-add SYS_ADMIN \ --security-opt apparmor:unconfined \ -v /path/to/source:/src:ro \ -v /path/to/output:/dst:rshared \ nschlia/ffmpegfs \ -f --log_stderr --audiobitrate=256K -o allow_other,ro,desttype=mp3,log_maxlevel=INFO Of course, */path/to/source* must be changed to a directory with multi-media files and */path/to/output* to where the converted files should be visible. The output type may be changed to MP4 or whatever is desired. Auto Copy ========= "Auto copy" performs intelligent stream copy. For example, if transcoding a transport stream that already represents a H264 video and/or AAC audio stream, it is possible to simply repackage it to an mp4 container without recoding. This is very efficient because it does not require as much computing as de- and encoding, and it also does not degrade quality because the original file remains essentially unchanged. The function detects if the target format supports the source codec and simply remuxes the stream even if recoding from one format (for example, TS) to another (for example, MOV, MP4). There are three options: | Option | Description | | ------ | ------------------------------------------------------------ | | OFF | no auto copy. | | LIMIT | only auto copy if the target file will not become significantly larger. | | ALWAYS | auto copy whenever possible, even if the target file becomes larger. | Smart Transcoding ================= Smart transcoding can create different output formats for video and audio files. For example, video files can be converted to ProRes and audio files to AIFF. Of course, combinations like MP4/MP3 or WebM/WAV are possible but do not make sense, as MP4 or WebM work perfectly with audio-only content. To use the new feature, simply specify a video and audio file type, separated by a "+" sign. For example, *--desttype=mov+aiff* will convert video files to Apple Quicktime MOV and audio only files to AIFF. This can be handy if the results are consumed, for example, by some Apple editing software, which is very picky about the input format. *Notes* 1. The first format must be a video codec, and the second must be an audio codec. For example, *--desttype=wav+mp4* is invalid, and instead it should be *--desttype=mp4+wav*. 2. Smart transcoding currently determines the output format by taking the input format type into account, e.g., an MP3 would be recoded to AIFF, an MP4 to MOV even if the input MP4 does not contain a video stream. The input format should be scanned for streams and the output selected appropriately: An MP4 with video should be transcoded to MOV, an MP4 with audio only to AIFF. See [Issue #86] (https://github.com/nschlia/ffmpegfs/issues/86) for details. Transcoding To Frame Images ========================= To transcode a video to frame images, set the destination type to JPG, PNG, or BMP. This will convert videos into virtual folders with one image for each frame. ``` $ ls /storage/videos video1.mp4 video2.mov $ ffmpegfs /storage/videos /mnt/ffmpegfs $ find /mnt/ffmpegfs /mnt/ffmpegfs/video1.mp4/00001.png /mnt/ffmpegfs/video1.mp4/00002.png ... /mnt/ffmpegfs/video1.mov/00001.png /mnt/ffmpegfs/video1.mov/00002.png ``` A Few Words On ProRes ===================== Apple's ProRes is a so-called intermediate format, intended for post-production editing. It combines the highest possible quality while still saving some disk space and not requiring high-performance disk systems. On the other hand, this means that ProRes encoded videos will become quite large—for example, a 60-minute video may require up to 25 GB. It is not for target audience use, and certainly not suitable for internet streaming. Also, please keep in mind that when using lossy source input formats, the quality will not get better, but the files can be fed into software like Final Cut Pro, which only accepts a small number of input formats. Transcoding Subtitles ===================== Closed captions are converted to the output files, if possible. There are two general subtitle formats: text and bitmap. Subtitle transcoding is currently only possible from text to text or bitmap to bitmap. It may be relatively easy to convert text to bitmap, but not vice versa. This would require some sort of OCR and could become arbitrarily complex. That may work well for Latin alphabets, but there are others. Guess what would happen with Georgian, Indian, Chinese, or Arabic... | Output Format | Subtitle Codec | Format | | ---------------- | ------------------------------------------------------ | ------ | | MP4, MOV, ProRes | MOV Text (Apple Text Media Handler) | Text | | WebM | WebVTT Subtitles (Web Video Text Tracks Format) | Text | | TS, HLS | DVB Subtitles | Bitmap | | MKV | ASS (Advanced SSA), SubRip Subtitles, WebVTT Subtitles | Text | | MKV | DVB Subtitles | Bitmap | Matroska (MKV) supports a wide range of subtitle formats, both text and bitmap. FFmpegfs automatically selects the best matching output codec. MKV would be the best choice to cover all input subtitle formats. ## External Subtitle Files Subtitles can reside in separate files. These must have the same filename but the extension "srt" for ASS/SubRip or "vtt" for WebVTT. The language can be defined with a second level extension, e.g. "mediafile.en.srt" would define the contents as "English". There is no convention for the language name, so it could even be the full language name like "mediafile.french.srt" or similar. Example | Filename | Contents | | -------------- | -------------------------- | | myvideo.mkv | Video | | myvideo.de.srt | German Subtitles | | myvideo.en.srt | English Subtitles | | myvideo.es.srt | Spanish Subtitles | | myvideo.fr.srt | French Subtitles | | myvideo.hu.srt | Hungarian Subtitles | | myvideo.it.srt | Italian Subtitles | | myvideo.jp.srt | Japanese Subtitles | | myvideo.srt | Unknown Language Subtitles | **TODO:** * Maybe FFmpeg could convert text to bitmap for TS/HLS if the input format is not bitmapped. MP4 Format Profiles ================== The MP4 container has several derivative formats that are not compatible with all target audiences. To successfully feed the resulting files into, for example, MS Edge, the subformat must be different than for Firefox, unfortunately. The --profile option allows you to select the format. | Profile | OS | Target | Remarks | | ------- | --------------------- | ------------------------------ | ------------------------------ | | NONE | all | VLC, Windows Media Player etc. | Playback (default) | | FF | Linux, Win10, Android | Firefox | OK: Playback while transcoding | | | Win7 | Firefox | OK: Playback while transcoding | | EDGE | Win10 | MS Edge, IE > 11 | OK: Playback while transcoding | | | Win10 Mobile | | OK: Playback while transcoding | | IE | Win10 | MS IE <= 11 | OK: Playback while transcoding | | | Win7 | | Must decode first (1) | | CHROME | all | Google Chrome | Must decode first (1) | | SAFARI | Win | Apple Safari | Must decode first (1) | | OPERA | All | Opera | Must decode first (1) | | MAXTHON | Win | Maxthon | Must decode first (1) | (1) * An error message appears when the file is opened while transcoding. * Must start again when the file is transcoded. * It works fine when the file is loaded directly from the buffer. This all boils down to the fact that Firefox and Edge are the only browsers that support the necessary extensions to start playback while still transcoding. In most cases, files will not play if they are not properly optimised. ffmpegfs-2.50/TODO0000644000175000017500000000044715052412650007413 FFmpegfs TODOs ============== * I'll have many more ideas when I wake up and find them nice to have. * Any additional features that you may desire. * I am working on a Windows version. * DASH support is on my list, but the format is complicated and I currently do not have the time for that.  ffmpegfs-2.50/configure.ac0000664000175000017500000002330715215723104011213 # Process this file with autoconf to produce a configure script. # Sets up package and initializes build system. AC_PREREQ(2.57) AC_INIT([FFMPEGFS], [2.50]) AC_CONFIG_SRCDIR([src/ffmpegfs.cc]) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_AUX_DIR([config]) # Get configure arguments configure_args="$*" AC_DEFINE_UNQUOTED([CONFIGURE_ARGS], ["$configure_args"], [Arguments passed to configure]) AM_INIT_AUTOMAKE([foreign subdir-objects]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) # AC_CANONICAL_HOST is needed to access the 'host_os' variable AC_CANONICAL_HOST AC_DEFINE_UNQUOTED([HOST_OS], ["$host_os"], [Host operating system]) # Checks for programs AC_PROG_CC AC_PROG_CXX AC_PROG_RANLIB # Checks for typedefs, structures, and compiler characteristics. AC_CHECK_HEADER_STDBOOL AC_TYPE_INT32_T AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T # Checks for header files. #AC_CHECK_HEADERS([fcntl.h stdlib.h string.h unistd.h sstream climits cstring]) # Check integer size AC_CHECK_SIZEOF(int) AC_CHECK_SIZEOF(long) AC_CHECK_SIZEOF([off_t]) #AC_CHECK_SIZEOF([size_t] AS_IF([test "$ac_cv_sizeof_int" -lt 4], AC_MSG_ERROR([The size of an integer of type "int" must be at least 4 bytes.])) dnl Figure out which format string to use for time_t AC_CHECK_SIZEOF([time_t],,[#include ]) AC_MSG_CHECKING([size of time_t type]) AS_IF([test "$ac_cv_sizeof_time_t" = "$ac_cv_sizeof_long"], [AC_MSG_RESULT(long) AC_DEFINE([FFMPEGFS_FORMAT_TIME_T], ["ld"], [format string for time_t])], [AC_MSG_RESULT(int) AC_DEFINE([FFMPEGFS_FORMAT_TIME_T], ["d"], [format string for time_t])]) dnl Figure out which format string to use for pthread_t AC_CHECK_SIZEOF([pthread_t],,[#include ]) AC_MSG_CHECKING([size of pthread_t type]) AS_IF([test "$ac_cv_sizeof_pthread_t" = "$ac_cv_sizeof_long"], [AC_MSG_RESULT(long) AC_DEFINE([FFMPEGFS_FORMAT_PTHREAD_T], ["lx"], [format string for pthread_t])], [AC_MSG_RESULT(int) AC_DEFINE([FFMPEGFS_FORMAT_PTHREAD_T], ["x"], [format string for pthread_t])]) # Large file support AC_SYS_LARGEFILE # Define POSIX standard conformance AC_DEFINE([_POSIX_C_SOURCE], [200809L], [Define the POSIX version]) # Bug#1037653: Fix build with GCC-13 AC_DEFINE([__STDC_CONSTANT_MACROS], [], [Must be defined for build with GCC 13]) # This is because there are PKG_CHECK_MODULES calls inside conditionals. PKG_PROG_PKG_CONFIG # Checks for packages which use pkg-config. PKG_CHECK_MODULES([chardet], [chardet >= 1.0.4]) PKG_CHECK_MODULES([fuse3], [fuse3 >= 3.4.1]) PKG_CHECK_MODULES([libcue], [libcue >= 2.1.0]) # Checks for sqlite3 # API misses a lot of functionality if not fairly recent PKG_CHECK_MODULES([sqlite3], [sqlite3 >= 3.7.13]) AC_SEARCH_LIBS([sqlite3_errstr], [sqlite3], [AC_DEFINE([HAVE_SQLITE_ERRSTR], [1], [libsqlite3 has sqlite3_errstr() function.])], []) AC_SEARCH_LIBS([sqlite3_db_cacheflush], [sqlite3], [AC_DEFINE([HAVE_SQLITE_CACHEFLUSH], [1], [libsqlite3 has sqlite3_db_cacheflush() function.])], []) AC_SEARCH_LIBS([sqlite3_expanded_sql], [sqlite3], [AC_DEFINE([HAVE_SQLITE_EXPANDED_SQL], [1], [libsqlite3 has sqlite3_expanded_sql() function.])], []) # # Check for programs used when building manpages and help. # # Check for a2x (convert asciidoc to another format) AC_PATH_PROG(A2X, a2x) AC_CHECK_PROG(HAVE_A2X, a2x, "yes", "no") AS_IF([test "$HAVE_A2X" = "yes"], [], [AC_MSG_ERROR([a2x could not be found. Install asciidoc to fix. Or asciidoc-base instead to save disc space.])]) # Check for w3m (html -> text) AC_PATH_PROG(W3M, w3m) AC_CHECK_PROG(HAVE_W3M, w3m, "yes", "no") AS_IF([test "$HAVE_W3M" = "yes"], [], [AC_MSG_ERROR([w3m could not be found. To fix, kindly install.])]) dnl FFmpeg support checks # FFmpeg 4.1.8 "al-Khwarizmi" # # This version is packaged with Debian Buster. This is the lowest version # we support. # # 4.1.8 was released on 2021-10-17. It is the latest stable FFmpeg release # from the 4.1 release branch, which was cut from master on 2018-11-02. # # It includes the following library versions: # # libavutil 56. 22.100 # libavcodec 58. 35.100 # libavformat 58. 20.100 # libavdevice 58. 5.100 (not used) # libavfilter 7. 40.101 # libswscale 5. 3.100 # libswresample 3. 3.100 # libpostproc 55. 3.100 (not used) # # May work with older versions but this is not guaranteed. PKG_CHECK_MODULES([libavutil], [libavutil >= 56.22.100], [PKG_CHECK_MODULES([libavcodec], [libavcodec >= 58.35.100], [PKG_CHECK_MODULES([libavformat], [libavformat >= 58.20.100], [PKG_CHECK_MODULES([libavfilter], [libavfilter >= 7.40.101], [PKG_CHECK_MODULES([libswscale], [libswscale >= 5.3.100], [PKG_CHECK_MODULES([libswresample], [libswresample >= 3.3.100], [AC_DEFINE([HAVE_FFMPEG], [1], [Use FFMPEG libraries.])] ) ] ) ] ) ] ) ] ) ] ) # Optional libraries dnl Check for libdvdread AC_ARG_WITH([libdvd], [AS_HELP_STRING([--with-libdvd], [support using libdvd @<:@default=check@:>@])], [], [with_libdvd=check]) AS_CASE(["$with_libdvd"], [yes], [PKG_CHECK_MODULES([libdvdread], [dvdread >= 5.0.0], [HAVE_LIBDVDREAD=1])], [no], [], [PKG_CHECK_MODULES([libdvdread], [dvdread >= 5.0.0], [HAVE_LIBDVDREAD=1], [HAVE_LIBDVDREAD=0])]) AM_CONDITIONAL([USE_LIBDVD], [test "$with_libdvd" != "no" -a "0$HAVE_LIBDVDREAD" -eq 1]) AM_CONDITIONAL([HINT_LIBDVD], [test "$with_libdvd" != "no" -a "0$HAVE_LIBDVDREAD" -eq 0]) dnl Check for libbluray AC_ARG_WITH([libbluray], [AS_HELP_STRING([--with-libbluray], [support using libbluray @<:@default=check@:>@])], [], [with_libbluray=check]) AS_CASE(["$with_libbluray"], [yes], [PKG_CHECK_MODULES([libbluray], [libbluray >= 0.6.2], [HAVE_LIBBLURAY=1])], [no], [], [PKG_CHECK_MODULES([libbluray], [libbluray >= 0.6.2], [HAVE_LIBBLURAY=1], [HAVE_LIBBLURAY=0])]) AM_CONDITIONAL([USE_LIBBLURAY], [test "$with_libbluray" != "no" -a "0$HAVE_LIBBLURAY" -eq 1]) AM_CONDITIONAL([HINT_LIBBLURAY], [test "$with_libbluray" != "no" -a "0$HAVE_LIBBLURAY" -eq 0]) # Optional profiling support AC_ARG_ENABLE([perftools], AS_HELP_STRING([--enable-perftools], [link ffmpegfs with Google perftools/tcmalloc heap profiler, default=no]), [case "${enableval}" in yes) perftools=yes ;; no) perftools=no ;; *) AC_MSG_ERROR([bad value ${enableval} for --enable-perftools]) ;; esac],[perftools=no]) PERFTOOLS_CFLAGS="" PERFTOOLS_CXXFLAGS="" PERFTOOLS_LIBS="" AS_IF([test x"$perftools" = xyes], [AC_CHECK_LIB([tcmalloc_and_profiler], [HeapProfilerStart], [AC_DEFINE([HAVE_PERFTOOLS], [1], [Link with Google perftools/tcmalloc heap profiler.]) PERFTOOLS_CFLAGS="-fno-omit-frame-pointer" PERFTOOLS_CXXFLAGS="-fno-omit-frame-pointer" PERFTOOLS_LIBS="-Wl,--push-state,--no-as-needed -ltcmalloc_and_profiler -Wl,--pop-state"], [AC_MSG_ERROR([--enable-perftools requested, but libtcmalloc_and_profiler could not be linked. Install the gperftools development package.])])]) AC_SUBST([PERFTOOLS_CFLAGS]) AC_SUBST([PERFTOOLS_CXXFLAGS]) AC_SUBST([PERFTOOLS_LIBS]) AM_CONDITIONAL([ENABLE_PERFTOOLS], [test x"$perftools" = xyes]) # Check for libvcd AC_ARG_WITH([libvcd], [AS_HELP_STRING([--with-libvcd], [support using libvcd @<:@default=check@:>@])], [], [with_libvcd=check]) AM_CONDITIONAL([USE_LIBVCD], [test "$with_libvcd" != "no"]) AM_COND_IF([USE_LIBVCD], [AC_MSG_RESULT([Internal S/VCD support enabled... yes])], [AC_MSG_RESULT([Internal S/VCD support enabled... no])]) # Check for doxygen. If not installed, go on, but make doxy won't work. AC_CHECK_PROGS([DOXYGEN], [doxygen]) if test -z "$DOXYGEN"; then AC_MSG_WARN([Doxygen could not be found; we must continue without it. It is advised to be installed if you intend to use "make doxy".]) fi # Check for curl. If not installed, go on, but make doxy won't work. AC_CHECK_PROGS([CURL], [curl]) if test -z "$CURL"; then AC_MSG_WARN([curl could not be found; we must continue without it. It is advised to be installed if you intend to use "make doxy".]) fi # Check for dot (graphviz). If not installed, go on, but make doxy won't work. AC_CHECK_PROGS([GRAPHVIZ], [dot]) if test -z "$GRAPHVIZ"; then AC_MSG_WARN([dot could not be found; we must continue without it. It is advised to be installed if you intend to use "make doxy".]) fi AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [Enable debugging, default=no]), [case "${enableval}" in yes) debug=true ;; no) debug=false ;; *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;; esac],[debug=false]) AM_CONDITIONAL([DEBUG], [test x"$debug" = x"true"]) AC_ARG_ENABLE([changelog], AS_HELP_STRING([--enable-changelog], [Turn on changelog, default=no]), [case "${enableval}" in yes) changelog=yes ;; no) changelog=no ;; *) AC_MSG_ERROR([bad value ${enableval} for --enable-changelog]) ;; esac],[changelog=false]) AM_CONDITIONAL([NOCHANGELOG], [test x$changelog = xno]) # Outputs resulting files. AC_CONFIG_FILES([Makefile src/Makefile test/Makefile]) dnl Extra version AC_ARG_WITH([extra-version], [AS_HELP_STRING([--with-extra-version=STRING], [append STRING to version])], [EXTRA_VERSION="${withval}"], [EXTRA_VERSION=""]) AC_SUBST([EXTRA_VERSION]) AM_CONDITIONAL([ENABLE_EXTRA_VERSION], [test -n "$EXTRA_VERSION"]) AC_OUTPUT # Hints on options AM_COND_IF([HINT_LIBDVD], [AC_MSG_NOTICE([HINT: For DVD support, install the libdvdread development packages. Consult INSTALL.md for more information.])]) AM_COND_IF([HINT_LIBBLURAY], [AC_MSG_NOTICE([HINT: For Blu-ray support, install the libbluray development package. Consult INSTALL.md for more information.])]) ffmpegfs-2.50/PROBLEMS.md0000644000175000017500000000727715052412650010500 # Fixing Problems ## General problems accessing a file Due to the nature in which FFmpegfs works, if anything goes wrong, it can only report a general error. That means, when there is a problem accessing a file, copying or opening it, you will get an "Invalid argument" or "Operation not permitted". Not really informative. There is no way to pass the original result code through the file system. What you will have to do is refer to the syslog, system journal, or to the FFmpeg log itself, optionally at a higher verbosity. If you are unable to resolve the problem yourself, feel free to create [an issue](https://github.com/nschlia/ffmpegfs/issues), stating what you have done and what has happened. Do not forget to add logs (preferably at a higher verbosity) and, if possible, a description of how to recreate the issue. It would also be helpful to have an example of the media file that is generating the error. Copyrighted material should never be sent, nevertheless. ## Transcoding too slow Slow transcoding can be caused by a variety of factors, including a slow CPU or network issues. This might help with decoding and encoding speed: [Building FFmpeg with optimisations](INSTALL.md#building-ffmpeg-with-optimisations). ## Lock ups when accessed through Samba When accessed from a Samba drive, the pending read can lock the whole share, causing Windows Explorer and even KDE Dolphin to freeze. Any access from the same machine to that share is blocked. Even "ls" is not possible and blocks until the data is returned. It seems others had the same problem: http://samba.2283325.n4.nabble.com/Hangs-Accessing-fuse-filesystem-in-Windows-through-Samba-td4681904.html Adding this to the [global] config in smb.conf fixes that: oplocks = no level2 oplocks = no aio read size = 1 The "aio read size" parameter may be moved to the share config: aio read size = 1 ## rsync, Beyond Compare and other tools Some copy tools do not get along very well with dynamically generated files, as in [Issue #23: Partial transcode of some files](https://github.com/nschlia/ffmpegfs/issues/22). Under Linux, it is best to use (optionally with the -r parameter) cp -upv /path/to/source /path/to/target This will copy all missing or changed files without missing parts. On the Windows side, Windows Explorer or copy/xcopy work. Beyond Compare and comparable tools might only duplicate the expected size without responding to changes in size. Please take note of the "-p" parameter, which means "—preserve=mode,ownership,timestamps." It appears that files may occasionally be copied with zero size if it is absent. ## Open hls.html from disk to play HLS output. The majority of browsers forbid playing back files from disc. You may put them into a website directory, however sometimes https must be used in order to allow playback. **To enable disk playback in Firefox:** - Open about:config - Set security.fileuri.strict_origin_policy to false. ## Songs get cut short If you are using SAMBA or NFS and songs don't play all the way through, you're in trouble. It occurs when files are transcoded live, but never when a file is retrieved from cache. This is because the final size seldom comes out exactly as expected. Size information is only available up front when a cached file is returned. If the outcome is smaller than expected, SAMBA fills files with zeros; if the outcome is larger than expected, SAMBA chops off the remaining data. Unlike SAMBA, NFS sends either the right file or one that has been chopped or padded randomly. You can do this as many times as you like, with a different result each time. There doesn't appear to be a solution to that as of now. Perhaps NFS or SAMBA can be set up to handle that, but I'm not sure how. ffmpegfs-2.50/HISTORY.md0000664000175000017500000007336415215723104010420 History ======= ### New in 2.50 (2026-06-12): - **Bugfix:** Fixed audio/video synchronization issues during transcoding. - **Bugfix:** Fixed HLS playback stopping unexpectedly, especially near the final segment. - Refactored FFmpeg resource handling to use RAII wrappers for AVPacket, AVFormatContext, AVAudioFifo, AVDictionary, SwrContext, and SwsContext. - Removed deprecated av_init_packet() usage and switched packet handling to allocation-based AVPacket lifetime management. - Improved cleanup of FFmpeg input/output contexts, including custom AVIOContext handling and error paths during context allocation. - Improved audio FIFO, resampler, scaler, and dictionary lifetime management to reduce manual cleanup code and avoid leaks on error paths. - Cleaned up transcoder shutdown paths by centralizing FFmpeg resource cleanup in dedicated wrapper classes. - **Bugfix:** Fixed retained FFmpeg deinterlace filter graphs when the output pipeline is rebuilt, especially after HLS seeks. Old filter graphs are now released before reinitialisation and when closing the current output, preventing stale filter pointers and retained filter buffers. - **Bugfix:** Fixed HLS playback stopping unexpectedly, especially near the last HLS segment. - **Bugfix:** Improved HLS segment finalisation and state cleanup between segment transitions. - **Bugfix:** Improved packet/frame lifetime handling to avoid stale state during HLS playback. - Added a `make help` target to list the available build, test, installation, documentation and maintenance targets. - Added `./configure --enable-perftools` to build ffmpegfs with Google Perftools heap profiling support. - **Bugfix:** Fixed HLS playback for 10-bit UHD/HDR sources by converting H.264 HLS output to yuv420p/8-bit. This avoids unsupported H.264 High10 streams in browser-based players such as hls.js. - Added elapsed-time reporting to successful transcode completion messages, showing the total transcoding time in milliseconds. - **Bugfix:** Fixed a race condition in transcoder thread start-up which could allow the same cache entry to be transcoded more than once concurrently. - Consolidated transcoder completion logging so each worker now emits one clear final status message, including elapsed runtime for successful transcodes. - **Bugfix:** Fixed HLS cache recovery so stale segment metadata no longer leaves playback waiting indefinitely when the corresponding cache file is missing, empty, or otherwise unusable. - Improved HLS segment recovery by explicitly restarting the transcoder when a segment is marked as available but its cache file is missing, empty, or otherwise unusable. - **Bugfix:** Fixed HLS segment availability checks so incomplete cache entries no longer make segments appear available when those segments were never actually produced. - Added additional safeguards for stale decoder state to avoid leaving cache entries permanently marked as decoding when no active worker is available to repair them. - **Bugfix:** Ignored HLS seek requests that are too close to the beginning of a stream so transcoding starts at segment 1 instead of creating an avoidable partial cache set. - Added an HLS cache regression test that pre-populates the cache, re-reads all generated segments, and verifies that cached segment output remains stable across repeated reads. - **Bugfix:** Fixed HLS cache test log naming so wrapper scripts which already contain the `_hls` suffix no longer generate duplicate `_hls_hls` builtin log files. - **Bugfix:** Fixed `distclean`/`distcheck` failures caused by incorrectly named HLS test log files being left behind in the test build directory. - Refactored transcoder stream and output initialisation into smaller helper functions, making the setup flow easier to maintain. - Improved `AVCodecContext` ownership handling during stream, output, and frame-set setup so failed initialisation paths no longer leak codec contexts. - Hardened output/cache setup with additional validation and null checks to avoid partially initialised stream state and provide clearer error handling. - Improved duration metadata handling for stream-copy and album-art output so invalid or missing input timing information is no longer used. - Improved test cleanup by using explicit ffmpegfs-owned temporary directory names and guarded removal logic, avoiding stale anonymous `/tmp` directories after interrupted or parallel test runs. - Improved deinterlacing quality by replacing `yadif` with `bwdif` while keeping one output frame per input frame. This improves playback smoothness for interlaced sources without changing the output frame rate or requiring changes to timestamp, FPS, or HLS segment timing logic. ### New in 2.18 (2026-04-10): - **Feature:** Added ALAC profile for iTunes (`--desttype=ALAC --profile=ITUNES`). Playback of the file will not commence until it is fully recoded; however, it can be played in iTunes. - **Feature:** Implemented a validation check for the combination of TYPE and PROFILE in `--desttype=TYPE --profile=PROFILE`. - Updated Dockerfile to include FUSE 3. - **Bugfix:** Fix error with new FFmpeg API: *"Option 'pix_fmts' is not a runtime option and so cannot be set after the object has been initialized"*. - **Fixed deprecation:** Replace `avcodec_get_supported_config()`. - **Fixed deprecation:** Remove `avcodec_close()`. - **Fixed deprecation:** Remove `av_format_inject_global_side_data()`. - **Fixed deprecation:** Replace `std::codecvt` with `iconv` in `read_file`. - **Bugfix:** `reserve()` only guarantees capacity, not size → writing via `.data()` is undefined behaviour. Using `resize()` makes the memory usable. - As `strerror()` is not thread-safe, use `strerror_r()` where available. - `strncpy` likes to copy without NUL → terminate explicitly. - **Bugfix:** Issue [#173](https://github.com/nschlia/ffmpegfs/issues/173): Fixed output directory no showing complete list of files under Debian 13. - **Bugfix:** Updated Dockerfile for Trixie - **Bugfix:** Closes [#1115015](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1115015): Fix build with FFmpeg 8 (already applied in Debian via NMU) - **Bugfix:** Closes [#1119414](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1119414) Changed configure.ac and makefile.am to preserve the default build flags **New in 2.17 (2024-11-10):** - **Bugfix:** Issue [#164](https://github.com/nschlia/ffmpegfs/issues/164): Fixed incorrectly discarded HLS seek requests. - **Bugfix:** Wrong error message fixed when an invalid audio/video codec was selected. The message should rather say "unsupported codec" instead of talking about "sample format not supported.". - **Bugfix:** Issue [#162](https://github.com/nschlia/ffmpegfs/issues/162): If not present, add time stamps to the copied streams. - Changed quality from 34 to 40 for hardware encoded video streams to create slightly smaller files. - [Closes#1084487:](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1084487): Moved from the Fuse 2 to the Fuse 3 API. **New in 2.16 (2024-06-10):** - **Bugfix:** Issue [#160](https://github.com/nschlia/ffmpegfs/issues/160): Fix build with FFmpeg 7.0. [Debian Bug #1072412](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1072412). write_packet() now with const buffer as of Libavformat 61+. - Fixed deprecation: 2014-05-18 - 68c0518 / fd05602 - lavc 55.63.100 / 55.52.0 - avcodec.h Add avcodec_free_context(). From now on it should be used for freeing AVCodecContext. - Fixed deprecation: 2023-05-15 - 7d1d61cc5f5 - lavc 60 - avcodec.h Depreate AVCodecContext.ticks_per_frame in favor of AVCodecContext.framerate (encoding) and AV_CODEC_PROP_FIELDS (decoding). **New in 2.15 (2024-02-03):** - **Bugfix:** Issue [#151](https://github.com/nschlia/ffmpegfs/issues/151): Fixed autocopy STRICT never triggers for video streams - **Bugfix:** Issue [#153](https://github.com/nschlia/ffmpegfs/issues/153): The --include_extensions parameter now contains a description, which was previously missing from the manual and online help. - **Issue** [#149](https://github.com/nschlia/ffmpegfs/issues/149): 2023-05-04 - xxxxxxxxxx - lavu 58.7.100 - frame.h Deprecate AVFrame.interlaced_frame, AVFrame.top_field_first, and AVFrame.key_frame. Add AV_FRAME_FLAG_INTERLACED, AV_FRAME_FLAG_TOP_FIELD_FIRST, and AV_FRAME_FLAG_KEY flags as replacement. - **Issue** [#149](https://github.com/nschlia/ffmpegfs/issues/149): 2023-05-04 - xxxxxxxxxx - lavu 58.7.100 - frame.h Deprecate AVFrame.interlaced_frame, AVFrame.top_field_first, and AVFrame.key_frame. Add AV_FRAME_FLAG_INTERLACED, AV_FRAME_FLAG_TOP_FIELD_FIRST, and AV_FRAME_FLAG_KEY flags as replacement. - **Issue** [#149](https://github.com/nschlia/ffmpegfs/issues/149): 2021-09-20 - dd846bc4a91 - lavc 59.8.100 - avcodec.h codec.h Deprecate AV_CODEC_FLAG_TRUNCATED and AV_CODEC_CAP_TRUNCATED, as they are redundant with parsers. - Issue [#136](https://github.com/nschlia/ffmpegfs/issues/136): The CMake build files have been removed. Support was never more than experimental, and CMake lacks a good uninstall option. Will stick to automake system from now on. **New in 2.14 (2023-06-15):** - **Bugfix:** Issue [#141](https://github.com/nschlia/ffmpegfs/issues/141): Improved memory management by allocating several times the average size of allocations. This prevents obtaining tiny portions over and over again. Additionally, after the file is opened, grab the entire expected memory block rather than doing a tiny allocation initially, followed by a larger allocation. - **Bugfix:** Avoid race conditions that cause the inter-process semaphore creation to fail for the second process. - **Bugfix:** Issue [#119](https://github.com/nschlia/ffmpegfs/issues/119): If a seek request is still open after EOF, restart transcoding. - **Bugfix:** Issue [#119](https://github.com/nschlia/ffmpegfs/issues/119): To prevent frame/segment creation errors, the frame set and HLS code has been updated. - **Bugfix:** Avoid crashes during shutdown if cache objects have already been closed. - **Bugfix:** Issue [#119](https://github.com/nschlia/ffmpegfs/issues/119): The AVSEEK_FLAG_FRAME set should be used to seek to frames when building frame sets. Otherwise, output images may vary if searched for or continuously decoded. - **Bugfix:** The conversion of PTS to frame number and vice versa for frame sets was incorrect if TBR did not equal frames per second. - **Bugfix:** Fixed seek requests that are being ignored with frame sets. - **Bugfix:** When transferring from cache to the Fuse buffer, avoid a possible 1 byte overrun. - **Bugfix:** Issue [#143](https://github.com/nschlia/ffmpegfs/issues/143): To avoid occasional EPERM failures, missing synchronisation objects were added. - **Bugfix:** Issue [#144](https://github.com/nschlia/ffmpegfs/issues/144): To fix the crashes that may have been caused by them, the variables impacted by a potential threading issue were marked as "volatile." - **Bugfix:** [Closes#1037653:](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1037653) Fix build with GCC-13 - **Bugfix:** Update docker build for Debian Bookworm - **Enhancement:** Record milliseconds for every log event. - **Enhancement:** make check: added a file size check to frame set tests. - **Optimisation:** When reopening after invalidating the cache, the size remained at 0. The original size is now once again reserved in order to prevent reallocations. - **Optimisation:** To avoid reallocations, save enough space in the cache buffer to hold the entire frame set. - **Optimisation:** Checking folders to see if they can be transcoded is completely pointless. Directories are now immediately skipped. - To avoid problems with logfile viewers, renamed built-in logfiles to *_builtin.log (removing the double extension). **New in 2.13 (2023-01-15):** - **Feature:** Added --prebuffer_time parameter. Files will be decoded until the buffer contains the specified playing time, allowing playback to start smoothly without lags. Works similar to --prebuffer_size but gives better control because it does not depend on the bit rate. An example: when set to 25 seconds for HLS transcoding, this will make sure that at least 2 complete segments will be available once the file is released and visible. - **Feature:** Issue [#140](https://github.com/nschlia/ffmpegfs/issues/140): Filtering the files that will be encoded has been added. A comma-separated list of extensions is specified by the *—include_extensions* parameter. These file extensions are the only ones that will be transcoded. The entries support shell wildcard patterns. - **Feature:** The --hide_extensions parameter syntax has been extended. The entries now support shell wildcard patterns. - **Bugfix:** Issue [#139](https://github.com/nschlia/ffmpegfs/issues/139): Additional files could be added using the *—extensions* parameter. However, this is no longer necessary; in the past, a file's extension determined whether or not it would be transcoded. Files with unknown extensions would be ignored. The extension is no longer important because FFmpegfs now examines all input files and recognises transcodable files by the format. The outdated *—extensions* argument was removed without substitution. - **Bugfix:** Fixed crash when implode() function was called with an empty string. Happened with Windows GCC 11.3.0 only. ### Version 2.12 released **New in 2.12 (2022-08-27):** - The code has been run through clang-tidy to detect areas that could be updated to C++17 and to find areas that are prone to bugs or are inefficient. Many problems could be fixed. Sometimes a few lines of code can take the place of many. Some components function far more effectively than they did in the past. C++17 is cool! I must purchase a t-shirt. - **Bugfix:** In get prores bitrate(), a crash that might have happened under unusual circumstances has been corrected. If the best match resolution could not be found, array access out-of-bounds could happen. - **Bugfix:** Several unlikely, but potential problems that could have happened when subtitle decoding failed or delayed video/audio packets couldn't be decoded have been fixed. - **Bugfix:** An internal problem could cause the application to crash. Should never happen, though. Fixed anyway. - **Bugfix:** Sometimes, the last segment's estimated size was incredibly small - about 2 bytes. Each segment should have the same predicted size as is is calculated simply by dividing the projected size of the entire file by the number of segments. Following transcoding, the size was accurate. ### Version 2.11 released **New in 2.11 (2022-06-16):** * **Feature:** [Issue #86](https://github.com/nschlia/ffmpegfs/issues/86): Smart transcode now detects if a source file is audio only and uses the correct target format. For example, with --destination=webm+mp3, if one MP4 input file contains a video stream and another an audio stream only, the resulting files will be WebM (for the video input) and mp3 for the audio only file. * **Feature:** [Issue #137](https://github.com/nschlia/ffmpegfs/issues/137): Add --no_subtitles option to turn subtitles off. * **Bugfix:** Smart encode selected the video format for cue sheet tracks, regardless of the input format. This has been fixed now. * **Bugfix:** Fix a crash that occurs when a DVD/Blu-ray is transcoded to audio only. * **Bugfix:** If the track performer field in the cuesheet is blank, try album performer instead. * **Bugfix:** Failing to mount Fuse during "make check" went unnoticed as the result code (which was supposed to be 99) was actually 0. Return the correct result code, failing the operation as expected. * **Bugfix:** The Docker build command contained a "make check" which actually failed altogether. Step has been removed. "make check" mounts Fuse, but this requires privileges that do not exist during "docker build". * **Bugfix:** On error, mremap () returns MAP_FAILED rather than NULL. Fixed a check for incorrect error conditions, which could cause the application to crash or return illogical error messages. * **Bugfix:** [Issue #119](https://github.com/nschlia/ffmpegfs/issues/119): Fix a problem that caused frame set generation to fail sometimes. It seems to be related to the nremap() issue. * Generally revisited documentation, logging, and display texts. Improved grammar, formatting, and fixed quite a few typos that escaped all proofreading sessions. * The FFmpeg API INFO and DEBUG level log output has been changed to the FFmpegfs DEBUG level. What FFmpeg considers "INFO" is far too chatty. * Frequent memory reallocations when creating HLS segments have been reduced to speed up processing. * Optimised logging to save CPU time by not formatting log entries that are not written anyway at their log level. * Logging has been revised to shorten file paths and remove mount, input, and cache paths. Log the additional portion only to reduce log file size and improve readability. * **Bugfix:** To fix the build with GCC 12, add the missing include headers (closes: [#1012925](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1012925)). ### Version 2.10 released **New in 2.10 (2022-04-26):** * **Feature:** [Issue #123](https://github.com/nschlia/ffmpegfs/issues/123): New command line option to hide files by extension. Example: `--hide_extensions=jpg,png,cue` would stop covers and cue sheets from showing up. * **Feature:** [Issue #120](https://github.com/nschlia/ffmpegfs/issues/120): Added subtitle support. Subtitle streams can now also be transcoded to the output files. Separate SRT or VTT files will be incorporated as subtitle streams. * **Bugfix:** Fixed memory leak in encode_audio_frame(). * **Bugfix:** [Issue #122](https://github.com/nschlia/ffmpegfs/issues/122): Last song was missing from cuesheet files. * **Bugfix:** [Issue #129](https://github.com/nschlia/ffmpegfs/issues/129): Files remained zero size when previously transcoded. * **Bugfix:** [Issue #130](https://github.com/nschlia/ffmpegfs/issues/130): Fix file sizes can be incorrectly reported by ls but are correct when data is read. * **Bugfix:** Duration was not saved in cache SQLite database. * **Bugfix:** [Issue #131](https://github.com/nschlia/ffmpegfs/issues/131): Sometimes video parameters for some Blu-ray or DVD chapters cannot be detected by FFmpeg API. Transcode then fails - fixed by using data from the Blu-ray directory or DVD IFO instead. * Lowest supported FFmpeg API version raised to 4.1.8 "al-Khwarizmi". * Dropped libavresample support, library was removed from FFmpeg API after 3.4.9. * Deprecated previous channel layout API based on uint64 bitmasks. * Deprecated swr_alloc_set_opts() and swr_build_matrix(). * Going C++17 now: The packet queue has been recoded in C++17 to support external subtitles files. As C++17 is required now, why not go all the way: Starting to replace legacy C++ and somewhat C-like parts with real C++. * Using std::shared_ptr to ensure proper memory allocation/free. ### Version 2.9 released **New in 2.9 (2022-02-16):** * **Feature:** [Issue #97](https://github.com/nschlia/ffmpegfs/issues/97): Added options to chose different codecs. The audio codec can be selected with --audiocodec, for videos use --videocodec. * **Feature:** [Issue #109](https://github.com/nschlia/ffmpegfs/issues/109): Allow user defined file extensions for source files. By default, only standard extensions are accepted, i.e., mp4, ts, avi etc. Arbitrary file extensions can be defined now, e.g. using --extensions=xxx,abc,yxz,aaa to also convert files ending with .xxx, .abc and so on. * **Feature:** [Issue #121](https://github.com/nschlia/ffmpegfs/issues/121): Added MKV support. New format can be selected with --desttype=mkv. * **Bugfix:** [Issue #112](https://github.com/nschlia/ffmpegfs/issues/112): Fixed Docker detection. * **Bugfix:** [Issue #110](https://github.com/nschlia/ffmpegfs/issues/110): Docker build command failed, added missing libchardet and allow libdvdread4 or *8 to be used, whatever available. * **Bugfix:** Fixed crash when video had no audio. * **Bugfix:** [Issue #112](https://github.com/nschlia/ffmpegfs/issues/112): Fixed access problems with frame sets and HLS. * **Bugfix:** Issue #119: Fixed problem that caused frame set generation to sometimes fail. * **Bugfix:** Fixed JPG frame set generation. Suddenly FF_COMPLIANCE_UNOFFICIAL was required to have FFmpeg API accept the codec. * **Enhancement:** [Issue #67](https://github.com/nschlia/ffmpegfs/issues/67): Enhanced file size prediction. * **Bugfix:** Need to synchronise screen log. Concurrent entries by separate threads produced garbled output. * **Bugfix:** Avoid creating an HLS segment number which is out of bounds (higher than the expected number of segments). * **Bugfix:** Removed QMake support, replaced with CMake. ### Version 2.8 released **New in 2.8 (2021-11-29):** * **Bugfix:** [Issue #102](https://github.com/nschlia/ffmpegfs/issues/102): Not all SQL queries where case sensitive, causing cache confusion. Several database entries were created, but only one was updated. Made all queries case sensitive. * **Bugfix:** [Issue #91](https://github.com/nschlia/ffmpegfs/issues/91): Fixed HLS problems with cache causing garbled videos and hick-ups in audio. * **Enhancement**: [Issue #103](https://github.com/nschlia/ffmpegfs/issues/103): If requested HLS segment is less than X (adjustable) seconds away, discard seek request. Segment would be available very soon anyway, and that seek makes a re-transcode necessary. Can be set with *--min_seek_time_diff*. Defaults to 30 seconds. * **Feature**: [Issue #105](https://github.com/nschlia/ffmpegfs/issues/105): Added Free Lossless Audio Codec (FLAC) support. Activate with *--desttype=FLAC*. * **Feature:** [Issue #101](https://github.com/nschlia/ffmpegfs/issues/101): Sample format for audio files can be selected via command line with *--audiosamplefmt*. Possible values are 0 to use the predefined setting, 8, 16, 32, 64 for integer format, F16, F32, F64 for floating point. Not all formats are supported by all destination types, selecting an invalid format will be reported as error and a list of values printed. Defaults to 0 (Use same as source or the predefined format of the destination if source format is not possible). ### Version 2.7 released **New in 2.7 (2021-11-08):** * **Bugfix:** [Issue #92](https://github.com/nschlia/ffmpegfs/issues/92): Fixed crash when hardware decoding failed. The problem is that the FFmpeg API very late reports that it cannot decode the file in hardware. To find out about that, the source file must be decoded until the first video frame is encountered. It would be very time consuming to do this on every file (decode until it is clear that the file is supported, then actually start transcoding it from scratch). There is no feasible way to automatically handle the situation. To get around this a --hwaccel_dec_blocked parameter has been added. If hardware decoding fails, check the log for a message similar this: "[vp9 @ 0x7fe910016080] No support for codec vp9 profile 0." If VP9 profile 0 is not supported, the parameter would be: --hwaccel_dec_blocked=VP9:0 This will tell FFmpegfs to decode the file in software. To block VP9 as a whole, the parameter would be --hwaccel_dec_blocked=VP9. To block both profile 0 and 1, use --hwaccel_dec_blocked=VP9:0:1. The parameter can be repeated to block even more codecs. * **Bugfix:** [Issue #96](https://github.com/nschlia/ffmpegfs/issues/96): Fixed potential buffer overrun and crash when reading corrupted input files. * **Enhancement:** [Issue #99](https://github.com/nschlia/ffmpegfs/issues/99): Report command line error if --desttype specifies audio format first, or if the second format is not audio only. Avoid misinterpretations. For example, --desttype=aiff+mov would create MOV files out of any input. Correct would be --desttype=mov+aiff which will create MOV files out of videos and AIFF from audio files, as expected. ### Version 2.6 released **New in 2.6 (2021-09-04):** * No new features, just a bugfix. See V2.4 for details. ### Version 2.5 released **New in 2.5 (2021-06-18):** * **Feature**: [Issue #63](https://github.com/nschlia/ffmpegfs/issues/63) - Hardware acceleration for encoding/decoding is partly implemented, VAAPI/MMAL/OMX/V4L2 are currently available only. - Supported hardware: V4L2/VAAPI (Intel) and V4L2/MMAL/OMX (Raspberry). - VAAPI: H264, H265/HEVC, MPEG-2 and VP-8 decoding and H264 encoding. - VAAPI: MJPEG and VC-9 do not work (yet). - MMAL: H264, MPEG-2, MPEG-4 and VC1 decoding. - OMX: H264 encoding. - V4L2: H263, H264, H265, MPEG1/2/4, VC-1, VP8/9 encoding/decoding. * **Feature**: Added unit tests for hardware acceleration. Failing tests will report as *SKIPPED* and not fail the whole test. * **Note**: Which hardware en/decoder actually works depends on what your hardware supports. * **Call for testers**: Have a CUDA capable graphics adapter and interested in testing? Please write me an e-mail. ### Version 2.4 released **New in 2.4 (2021-09-04):** * **Bugfix:** [Issue #90](https://github.com/nschlia/ffmpegfs/issues/90): Make sure that one keyframe gets inserted at the start of each HLS segment. ### Version 2.3 released **New in 2.3 (2021-06-11):** * **Enhancement:** [Issue #80](https://github.com/nschlia/ffmpegfs/issues/80): Open input video codec only if target supports video. Saves resources: no need to decode video frames if not used. * **Enhancement:** [Issue #81](https://github.com/nschlia/ffmpegfs/issues/81): If source format has no audio, and the target supports no video (e.g.WAV/MP3), the files have shown up zero sized. These will now not be visible when doing ls. When trying to open them "File not found" will be returned. * **Added** "configure --enable-debug" to create binaries with debug symbols. Defaults to the optimised version. * **Feature:** [Issue #73](https://github.com/nschlia/ffmpegfs/issues/73) Cue sheet tracks now play "gapless" if played in order. Whenever a track is started, the next track will automatically be transcoded as well. * **Feature:** [Issue #66](https://github.com/nschlia/ffmpegfs/issues/66) and [issue #82](https://github.com/nschlia/ffmpegfs/issues/82): Added cue sheet support. If a file with cue extension is found with the same name as a media file or if a cue sheet is embedded into it (a tag named CUESHEET), tracks defined in it will show up in a virtual directory. * **Feature:** [Issue #83](https://github.com/nschlia/ffmpegfs/issues/83): Character conversion for cue sheet files. Automatically detects the character encoding of the cue sheet. and converts as necessary. * **Feature:** [Issue #78](https://github.com/nschlia/ffmpegfs/issues/78): Duplicate ARTIST to ALBUMARTIST tag if empty. * **Feature:** [Issue #79](https://github.com/nschlia/ffmpegfs/issues/79): Added Docker support. See [Build A Docker Container](FEATURES.md#build-a-docker-container) how to use it. * **Fixed deprecation:** 2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h Deprecated av_init_packet() * **Fixed API compatitibility:** Many pointers made const as of 2021-04-27. Although reasonable, this breaks API compatibility with versions older than 59.0.100, * **Bugfix:** find_original "fallback" method did not correctly handle the new filename format (extension added, not the original one replaced). * **Bugfix:** [Issue #87](https://github.com/nschlia/ffmpegfs/issues/87): Segments are now properly separated, making sure that e.g. segment 3 only goes from 30 seconds up to 40 (including 30, but not 40 seconds). * **Bugfix:** [Issue #88](https://github.com/nschlia/ffmpegfs/issues/88): HLS audio and video now stay in sync after longer playback (more than 30 minutes) or after seek operations. ### Version 2.2 released **New in 2.2 (2021-02-06):** * **Note**: This is planned as a maintenance version, no new features but bug fixes only. * **Bugfix:** [Issue #75](https://github.com/nschlia/ffmpegfs/issues/75): Fix crash when opening mp3 output with Dolphin. * **Bugfix**: Possible crash in transcoder_thread: Decoder object could have been used after being freed. * **Bugfix:** Stupid blooper. WAV and AIFF size was always calculated for a mono file, thus for stereo files only half the correct size. * **Bugfix:** [Issue #70](https://github.com/nschlia/ffmpegfs/issues/70): Possible crash in Buffer::init: Should not assert if duration is 0 (and thus segment count 0). Report internal error and go on. * **Bugfix:** [Issue #70](https://github.com/nschlia/ffmpegfs/issues/70): Do not set duration to 0 from cache but leave unchanged. Caused HLS transcoding to fail if more than one transcoder was concurrently started. * **Bugfix:** Corrected documentation, "make checks" should read "make check", funny this went unnoticed for over 3 years... * **Bugfix:** [Issue #74](https://github.com/nschlia/ffmpegfs/issues/74): Album arts were only copied from MP3/4 sources. Removed restriction, if the input file contains an album art it will be copied to the target (if supported, of course, e.g., to mp3 or mp4. Ogg is not yet supported because embedding album arts in Ogg can only be done by an unofficial workaround). * **Bugfix:** [Issue #71](https://github.com/nschlia/ffmpegfs/issues/71): Virtual directories were missing dot and dot-dot nodes. ### Version 2.1 released **New in 2.1 (2020-12-14):** * **Feature**: Add BLURAY_VIDEO_FORMAT_2160P (UHD) * **Feature**: Implemented in 1.7, removed experimental state for --win_smb_fix now. Windows seems to access the files on Samba drives starting at the last 64K segment simply when the file is opened. Setting --win_smb_fix=1 will ignore these attempts (not decode the file up to this point). * **Feature**: --win_smb_fix now defaults to 1 (fix on by default). Has no effect if the drive is accessed directly or via Samba from Linux. * **Bugfix**: Fixed grammatical error in text: It's "access to", not "access at". * **Bugfix**: Did not transcode some source files with invalid DTS. * **Bugfix**: Cosmetical - No need to log date/time twice in syslog. * **Bugfix**: Cosmetical - Fix man page/online help for --recodesame parameter. * **Bugfix**: Report correct segment duration * **Bugfix**: Avoid crash if opening next HLS segment failed. Should not ignore this, but report it instead and stop transcoding. * **Cosmetical**: Log cache close action at trace level * **Cosmetical**: Shorter log entry when opening cache files ffmpegfs-2.50/INSTALL.md0000664000175000017500000003106315175426212010360 Installation Instructions for FFmpegfs ====================================== Installation from the repository -------------------------------- ### Debian Bullseye FFmpegfs has been added to Debian 11 Bullseye so it is available as a binary distribution. **On Debian 11 Bullseye, you get V2.2 by simply doing** apt-get install ffmpegfs Newer FFmpegfs versions can be installed on Debian 11 Bullseye from Bullseye Backports and sid (which is not recommended and therefore not described here). To enable Bullseye Backports: echo "deb http://deb.debian.org/debian bullseye-backports main" | sudo tee /etc/apt/sources.list.d/backports.list sudo apt-get update To install FFmpegfs from backports, run `apt-get install ffmpegfs/bullseye-backports` or `apt-get install -t bullseye-backports ffmpegfs`. **For Ubuntu 20.04 or newer and Linux distributions based on it this is** apt-get install ffmpegfs ### Debian Buster FFmpegfs can be installed on Debian 10 Buster from Buster Backports and sid (which is not recommended and therefore not described here). To enable Buster Backports: echo "deb http://deb.debian.org/debian buster-backports main" | sudo tee /etc/apt/sources.list.d/backports.list sudo apt-get update Then install FFmpegfs: sudo apt-get -t buster-backports install ffmpegfs ### Other Distributions For Arch Linux and Manjaro, it can be found in the Arch User Repository (AUR). It is available as either the latest stable version or the latest code from GIT. Building FFmpegfs yourself -------------------------- ### Prerequisites For encoding and decoding, FFmpegfs utilises the FFmpeg multi media framework. The following libraries are necessary: * gcc and g++ compilers * fuse3 (>= 3.4.1) * sqlite3 (>= 3.7.13) FFmpeg 4.1.8 "al-Khwarizmi" or newer: * libavutil (>= 56.22.100) * libavcodec (>= 58.35.100) * libavformat (>= 58.20.100) * libavfilter (>= 7.40.101) * libswscale (>= 5.3.100) * libswresample (>= 3.3.100) For optional DVD support, you need the following libraries: * libdvdread (>= 5.0.0) * libdvdnav (>= 5.0.0) For optional Blu-ray support, you need the following libraries: * libbluray (>= 0.6.2) The commands to only install the first set of prerequisites come next. For more information, see the "Supported Linux Distributions" chapter in README.md. **On Debian:** apt-get install gcc g++ make pkg-config asciidoc-base w3m xxd apt-get install fuse3 libfuse3-dev libsqlite3-dev libavcodec-dev libavformat-dev libswresample-dev libavutil-dev libswscale-dev libavfilter-dev libcue-dev libchardet-dev To get DVD support: apt-get install libdvdread-dev libdvdnav-dev To get Blu-ray support: apt-get install libbluray-dev To use "make doxy" (build Doxygen documentation): apt-get install doxygen graphviz curl To use "make check" (run test suite): apt-get install libchromaprint-dev bc **On Suse** (please read notes before continuing): zypper install gcc gcc-c++ zypper install fuse3-devel libsqlite3-devel libavcodec-devel libavformat-devel libswresample-devel libavutil-devel libswscale-devel libcue-devel libchardet-devel To get DVD support: zypper install libdvdread-devel libdvdnav-devel To get Blu-ray support: zypper install libbluray-devel Suse includes non-proprietary codecs with FFmpeg only, namely mp3, AAC and H264 are *not* available which renders this library next to usesless. But FFmpeg can be built from source, see https://trac.ffmpeg.org/wiki/CompilationGuide and check "FFmpeg compile notes" below. **On Red Hat:** yum install gcc g++ yum install fuse3-devel sqlite-devel libcue-devel libchardet-devel To get DVD support: yum install libdvdread-devel libdvdnav-devel To get Blu-ray support: yum install libbluray-devel FFmpeg is not provided by Red Hat from its repositories. It must be built from source code. See this guide: https://trac.ffmpeg.org/wiki/CompilationGuide/Centos If you want to build the documentation, you will find "asciidoc" missing from the Red Hat repositories. To get it, use a beta repository: yum --enablerepo=rhel-7-server-optional-beta-rpms install asciidoc **On Funtoo Linux:** To get fuse support and chromaprint (for make check): emerge sys-fs/fuse3 emerge media-libs/chromaprint To get FFmpeg with H264 etc. support, specify some "USE flags" when doing emerge: Create a file /etc/portage/package.use, for example "vi vi /etc/portage/package.use" and add this line: media-video/ffmpeg mp3 x264 opus vorbis vpx This will enable H264, mp3, Opus and WebM support. Next... emerge media-libs/openh264 emerge media-sound/twolame emerge media-video/ffmpeg to build FFmpeg. ### Building FFmpegfs from source code #### Building from tar ball Get the most recent release archive here: https://github.com/nschlia/ffmpegfs/releases Alternatively, utilise these instructions, being sure to download the most recent version: ``` curl -s https://api.github.com/repos/nschlia/ffmpegfs/releases/latest | \ grep "browser_download_url.*gz" | \ cut -d : -f 2,3 | \ tr -d \" | \ wget -i - ``` Unpack the file, and then cd to the source directory. To build and install, run: ./configure make make install Build and execute the test suite by doing: make check This will test audio conversion, tagging and size prediction. #### Building from GIT Clone the most recent version from Github to build from GIT and stay up to date: ``` git clone https://github.com/nschlia/ffmpegfs.git ``` Or from the mirror server: ``` git clone https://salsa.debian.org/nschlia/ffmpegfs.git ``` **If building from git, you'll need these additional prerequisites:** * autoconf * automake * asciidoc (or at least asciidoc-base to save disk space) * w3m * docbook-xml For those who are lazy like me, simply copy and issue this command to get all the prerequisites: apt-get install gcc g++ make pkg-config autoconf automake asciidoc-base docbook-xml xsltproc w3m libchromaprint-dev bc doxygen graphviz FFmpegfs uses the GNU build system, so you'll need to run first: ./autogen.sh If you are downloading a release, this has already been done for you. To build and install, run: ./configure make make install To build and run the test suite, do: make check ### Switching between repository version and source builds It is easy to switch between both worlds. You can do that as many times as you want to, but alas, the cache directory will be cleared every time. But it will be rebuilt in the background, so this will almost go unnoticed. To switch from repository to a source build do ``` apt-get remove ffmpegfs ``` Then follow the steps under "Building FFmpegfs yourself". If you do not remove the repository version, your self-build could inadvertently be up- or downgraded when a new version becomes available from the repository. To switch from a source build to a repository installation, change to the build directory and do ``` make uninstall ``` Then follow the steps under "Installation from repository". ### Building Documentation The help can be created in doc or html format by running make help-pdf make help-html respectively. Building FFmpeg ------------------------------- ### Building FFmpeg with optimisations The precompiled package of FFmpeg available for Debian, Ubuntu, etc. is built with common options so that it can run on many processors. To leverage its full potential, it may be useful to build it with optimisation options for the target CPU. The resulting binaries may not run on other computers. FFmpeg must be built with at least libx264, libfdk_aac, and libmp3lame support. Other libraries, for example, ogg, Windows Media, or FLAC, must be added when these formats should be used as sources. **Attention!** Remember to do "apt remove ffmpeg" before proceeding with the next steps to avoid maleficent blends of custom and official distribution binaries. For a minimum build that contains all the libraries required by FFmpegfs and SSL support for convenience use: configure \ --extra-cflags='-march=native' \ --extra-cflags=-Ofast \ --enable-runtime-cpudetect \ --disable-static \ --enable-gpl \ --enable-shared \ --enable-version3 \ --enable-nonfree \ --enable-pthreads \ --enable-postproc \ --enable-openssl \ --enable-libvpx \ --enable-libvorbis \ --enable-swresample \ --enable-libmp3lame \ --enable-libtheora \ --enable-libxvid \ --enable-libx264 Fix the complaints by configure, i.e., installing the required development packages, then make install This works for me. Decoding runs much faster with these settings. **NOTE:** Depending on the source formats you have, it may be required to add additional libraries. Trouble Shooting ---------------- ### Mounting via /etc/fstab fails An fstab line with fuse.ffmpegfs file system fails with a strange message when attempted to mount with "mount -av". No log entries, no other hints... ``` mount: wrong fs type, bad option, bad superblock on /mnt/sdf1/mp3base1, missing codepage or helper program, or other error ​ In some cases useful info is found in syslog - try ​ dmesg | tail or so. ``` Fuse is missing! Do... apt-get install fuse ### fuse: failed to exec fusermount: No such file or directory Trying to mount as any other than root, the message fuse: failed to exec fusermount: No such file or directory is printed. Fuse is missing! Do... apt-get install fuse ### "ERROR: libmp3lame >= 3.98.3 not found" If you run into this "ERROR: libmp3lame >= 3.98.3 not found" although you have built and installed libmp3lame you may find a solution here: https://stackoverflow.com/questions/35937403/error-libmp3lame-3-98-3-not-found ### autogen.sh displays "possibly undefined macro" Running autoreconf --install configure.ac:46: error: possibly undefined macro: AC_DEFINE If this token and others are legitimate, please use m4_pattern_allow. See the Autoconf documentation. autoreconf: /usr/bin/autoconf failed with exit status: 1 You are probably missing out on pkg-config; either it is not installed or not in path. "apt-get install pkg-config" (on Debian or equivalent on other Linux distributions) should help. ### If the videotag.php script does not work under PHP7 The script runs fine under PHP5, but when upgrading to PHP7 (or using PHP7) it suddenly stops showing the list of files. Check the Apache2 error.log, you might see this: "PHP Fatal error: Uncaught Error: Call to undefined function utf8_encode() in index.php" This is because, for some reason, utf8_encode() has been moved to the XML library. Just do (or similar): apt-get install php7.0-xml systemctl restart apache2 And your troubles should be gone. ### "make check": all audio checks fail Log files contain "bc: command not found", so the command line communicator is missing. Fix it by installing (or similar): apt-get install bc ### Make reports "/bin/sh: a2x: command not found" You are missing out on asciidoc. To install it, do (or similar): apt-get install asciidoc-base That should fix it. ### "make help-pdf" reports "non-zero exit status 127" Running "make help-pdf" fails like this: $ make -s help-pdf GEN ffmpegfs.1.pdf a2x: ERROR: "fop" -fo "ffmpegfs.1.fo" -pdf "ffmpegfs.1.pdf" returned non-zero exit status 127 make: *** [Makefile:918: ffmpegfs.1.pdf] Error 1 This happens when "fop" is missing, a command line wrapper for the Java version of fop. apt-get install fop That should do it. ### Make reports xmllint" --nonet --noout --valid "/home/jenkins/dev/ffmpegfs/ffmpegfs.1.xml" returned non-zero exit To find out more, run "make V=1". If you see something like (sic) "validity error : Validation failed: no DTD found !", this means that xmlint cannot access a DTD file because it is run with the --nonet option. This can be solved by apt-get install docbook-xml which will make the required files available offline. ### libbluray fails to load libbluray.jar When you see this message while accessing blurays: bdj.c:340: libbluray-j2se-0.9.3.jar not found. bdj.c:466: BD-J check: Failed to load libbluray.jar To get rid of this message, simply install "libbluray-bdj". This will make it go away. Though not necessary, as to read the Blu-ray tracks, Java support is not required, so this is simply cosmetical. Copyright --------- This fork with FFmpeg support copyright \(C) 2017-2026 Norbert Schlia (nschlia@oblivion-software.de). Based on work Copyright \(C) 2006-2008 David Collett, 2008-2013 K. Henriksson. This is free software: you are free to change and redistribute it under the terms of the GNU General Public License (GPL) version 3 or later. ffmpegfs-2.50/aclocal.m40000664000175000017500000017171415215723143010576 # generated automatically by aclocal 1.17 -*- Autoconf -*- # Copyright (C) 1996-2024 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.72],, [m4_warning([this file was generated for autoconf 2.72. You have another version of autoconf. It may work, but is not guaranteed to. If you have problems, you may need to regenerate the build system entirely. To do so, use the procedure documented by the package, typically 'autoreconf'.])]) # pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- # serial 12 (pkg-config-0.29.2) dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA dnl 02111-1307, USA. dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a dnl configuration script generated by Autoconf, you may include it under dnl the same distribution terms that you use for the rest of that dnl program. dnl PKG_PREREQ(MIN-VERSION) dnl ----------------------- dnl Since: 0.29 dnl dnl Verify that the version of the pkg-config macros are at least dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's dnl installed version of pkg-config, this checks the developer's version dnl of pkg.m4 when generating configure. dnl dnl To ensure that this macro is defined, also add: dnl m4_ifndef([PKG_PREREQ], dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) dnl dnl See the "Since" comment for each macro you use to see what version dnl of the macros you require. m4_defun([PKG_PREREQ], [m4_define([PKG_MACROS_VERSION], [0.29.2]) m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) dnl ---------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])dnl PKG_PROG_PKG_CONFIG dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------------------------------- dnl Since: 0.18 dnl dnl Check to see whether a particular set of modules exists. Similar to dnl PKG_CHECK_MODULES(), but does not set variables or print errors. dnl dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) dnl only at the first occurrence in configure.ac, so if the first place dnl it's called might be skipped (such as if it is within an "if", you dnl have to call PKG_CHECK_EXISTS manually AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) dnl --------------------------------------------- dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting dnl pkg_failed based on the result. m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])dnl _PKG_CONFIG dnl _PKG_SHORT_ERRORS_SUPPORTED dnl --------------------------- dnl Internal check to see if pkg-config supports short errors. AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])dnl _PKG_SHORT_ERRORS_SUPPORTED dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl -------------------------------------------------------------- dnl Since: 0.4.0 dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES might not happen, you should be sure to include an dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $2]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])dnl PKG_CHECK_MODULES dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------------------- dnl Since: 0.29 dnl dnl Checks for existence of MODULES and gathers its build flags with dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags dnl and VARIABLE-PREFIX_LIBS from --libs. dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to dnl include an explicit call to PKG_PROG_PKG_CONFIG in your dnl configure.ac. AC_DEFUN([PKG_CHECK_MODULES_STATIC], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl _save_PKG_CONFIG=$PKG_CONFIG PKG_CONFIG="$PKG_CONFIG --static" PKG_CHECK_MODULES($@) PKG_CONFIG=$_save_PKG_CONFIG[]dnl ])dnl PKG_CHECK_MODULES_STATIC dnl PKG_INSTALLDIR([DIRECTORY]) dnl ------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable pkgconfigdir as the location where a module dnl should install pkg-config .pc files. By default the directory is dnl $libdir/pkgconfig, but the default can be changed by passing dnl DIRECTORY. The user can override through the --with-pkgconfigdir dnl parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_INSTALLDIR dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) dnl -------------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable noarch_pkgconfigdir as the location where a dnl module should install arch-independent pkg-config .pc files. By dnl default the directory is $datadir/pkgconfig, but the default can be dnl changed by passing DIRECTORY. The user can override through the dnl --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_NOARCH_INSTALLDIR dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------- dnl Since: 0.28 dnl dnl Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])dnl PKG_CHECK_VAR dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------ dnl dnl Prepare a "--with-" configure option using the lowercase dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and dnl PKG_CHECK_MODULES in a single macro. AC_DEFUN([PKG_WITH_MODULES], [ m4_pushdef([with_arg], m4_tolower([$1])) m4_pushdef([description], [m4_default([$5], [build with ]with_arg[ support])]) m4_pushdef([def_arg], [m4_default([$6], [auto])]) m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) m4_case(def_arg, [yes],[m4_pushdef([with_without], [--without-]with_arg)], [m4_pushdef([with_without],[--with-]with_arg)]) AC_ARG_WITH(with_arg, AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, [AS_TR_SH([with_]with_arg)=def_arg]) AS_CASE([$AS_TR_SH([with_]with_arg)], [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], [auto],[PKG_CHECK_MODULES([$1],[$2], [m4_n([def_action_if_found]) $3], [m4_n([def_action_if_not_found]) $4])]) m4_popdef([with_arg]) m4_popdef([description]) m4_popdef([def_arg]) ])dnl PKG_WITH_MODULES dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ----------------------------------------------- dnl dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES dnl check._[VARIABLE-PREFIX] is exported as make variable. AC_DEFUN([PKG_HAVE_WITH_MODULES], [ PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) AM_CONDITIONAL([HAVE_][$1], [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) ])dnl PKG_HAVE_WITH_MODULES dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------------------ dnl dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make dnl and preprocessor variable. AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], [ PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) ])dnl PKG_HAVE_DEFINE_WITH_MODULES # Copyright (C) 2002-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # Automake X.Y traces this macro to ensure aclocal.m4 has been # generated from the m4 files accompanying Automake X.Y. # (This private macro should not be called outside this file.) AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.17' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. m4_if([$1], [1.17], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) # _AM_AUTOCONF_VERSION(VERSION) # ----------------------------- # aclocal traces this macro to find the Autoconf version. # This is a private macro too. Using m4_define simplifies # the logic in aclocal, which can simply ignore this definition. m4_define([_AM_AUTOCONF_VERSION], []) # AM_SET_CURRENT_AUTOMAKE_VERSION # ------------------------------- # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], [AM_AUTOMAKE_VERSION([1.17])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- # Copyright (C) 2001-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to # '$srcdir', '$srcdir/..', or '$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is '.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` ]) # AM_COND_IF -*- Autoconf -*- # Copyright (C) 2008-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_COND_IF # _AM_COND_ELSE # _AM_COND_ENDIF # -------------- # These macros are only used for tracing. m4_define([_AM_COND_IF]) m4_define([_AM_COND_ELSE]) m4_define([_AM_COND_ENDIF]) # AM_COND_IF(COND, [IF-TRUE], [IF-FALSE]) # --------------------------------------- # If the shell condition COND is true, execute IF-TRUE, otherwise execute # IF-FALSE. Allow automake to learn about conditional instantiating macros # (the AC_CONFIG_FOOS). AC_DEFUN([AM_COND_IF], [m4_ifndef([_AM_COND_VALUE_$1], [m4_fatal([$0: no such condition "$1"])])dnl _AM_COND_IF([$1])dnl if test -z "$$1_TRUE"; then : m4_n([$2])[]dnl m4_ifval([$3], [_AM_COND_ELSE([$1])dnl else $3 ])dnl _AM_COND_ENDIF([$1])dnl fi[]dnl ]) # AM_CONDITIONAL -*- Autoconf -*- # Copyright (C) 1997-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_CONDITIONAL(NAME, SHELL-CONDITION) # ------------------------------------- # Define a conditional. AC_DEFUN([AM_CONDITIONAL], [AC_PREREQ([2.52])dnl m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl AC_SUBST([$1_TRUE])dnl AC_SUBST([$1_FALSE])dnl _AM_SUBST_NOTMAKE([$1_TRUE])dnl _AM_SUBST_NOTMAKE([$1_FALSE])dnl m4_define([_AM_COND_VALUE_$1], [$2])dnl if $2; then $1_TRUE= $1_FALSE='#' else $1_TRUE='#' $1_FALSE= fi AC_CONFIG_COMMANDS_PRE( [if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then AC_MSG_ERROR([[conditional "$1" was never defined. Usually this means the macro was only invoked conditionally.]]) fi])]) # Copyright (C) 1999-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be # written in clear, in which case automake, when reading aclocal.m4, # will think it sees a *use*, and therefore will trigger all it's # C support machinery. Also note that it means that autoscan, seeing # CC etc. in the Makefile, will ask for an AC_PROG_CC use... # _AM_DEPENDENCIES(NAME) # ---------------------- # See how the compiler implements dependency checking. # NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". # We try a few techniques and use that to set a single cache variable. # # We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was # modified to invoke _AM_DEPENDENCIES(CC); we would have a circular # dependency, and given that the user is not expected to run this macro, # just rely on AC_PROG_CC. AC_DEFUN([_AM_DEPENDENCIES], [AC_REQUIRE([AM_SET_DEPDIR])dnl AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl AC_REQUIRE([AM_MAKE_INCLUDE])dnl AC_REQUIRE([AM_DEP_TRACK])dnl m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], [$1], [CXX], [depcc="$CXX" am_compiler_list=], [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], [$1], [UPC], [depcc="$UPC" am_compiler_list=], [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], [depcc="$$1" am_compiler_list=]) AC_CACHE_CHECK([dependency style of $depcc], [am_cv_$1_dependencies_compiler_type], [if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_$1_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` fi am__universal=false m4_case([$1], [CC], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac], [CXX], [case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac]) for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_$1_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_$1_dependencies_compiler_type=none fi ]) AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) AM_CONDITIONAL([am__fastdep$1], [ test "x$enable_dependency_tracking" != xno \ && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) ]) # AM_SET_DEPDIR # ------------- # Choose a directory name for dependency files. # This macro is AC_REQUIREd in _AM_DEPENDENCIES. AC_DEFUN([AM_SET_DEPDIR], [AC_REQUIRE([AM_SET_LEADING_DOT])dnl AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl ]) # AM_DEP_TRACK # ------------ AC_DEFUN([AM_DEP_TRACK], [AC_ARG_ENABLE([dependency-tracking], [dnl AS_HELP_STRING( [--enable-dependency-tracking], [do not reject slow dependency extractors]) AS_HELP_STRING( [--disable-dependency-tracking], [speeds up one-time build])]) if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH])dnl _AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl AC_SUBST([am__nodep])dnl _AM_SUBST_NOTMAKE([am__nodep])dnl ]) # Generate code to set up dependency tracking. -*- Autoconf -*- # Copyright (C) 1999-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], [{ # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. AS_CASE([$CONFIG_FILES], [*\'*], [eval set x "$CONFIG_FILES"], [*], [set x $CONFIG_FILES]) shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`AS_DIRNAME(["$am_mf"])` am_filepart=`AS_BASENAME(["$am_mf"])` AM_RUN_LOG([cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles]) || am_rc=$? done if test $am_rc -ne 0; then AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE="gmake" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking).]) fi AS_UNSET([am_dirpart]) AS_UNSET([am_filepart]) AS_UNSET([am_mf]) AS_UNSET([am_rc]) rm -f conftest-deps.mk } ])# _AM_OUTPUT_DEPENDENCY_COMMANDS # AM_OUTPUT_DEPENDENCY_COMMANDS # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # # This code is only required when automatic dependency tracking is enabled. # This creates each '.Po' and '.Plo' makefile fragment that we'll need in # order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) # Do all the work for Automake. -*- Autoconf -*- # Copyright (C) 1996-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This macro actually does too much. Some checks are only needed if # your package does certain things. But this isn't really a big deal. dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC]) [_AM_PROG_CC_C_O ]) # AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) # AM_INIT_AUTOMAKE([OPTIONS]) # ----------------------------------------------- # The call with PACKAGE and VERSION arguments is the old style # call (pre autoconf-2.50), which is being phased out. PACKAGE # and VERSION should now be passed to AC_INIT and removed from # the call to AM_INIT_AUTOMAKE. # We support both call styles for the transition. After # the next Automake release, Autoconf can make the AC_INIT # arguments mandatory, and then we can depend on a new Autoconf # release and drop the old call support. AC_DEFUN([AM_INIT_AUTOMAKE], [AC_PREREQ([2.65])dnl m4_ifdef([_$0_ALREADY_INIT], [m4_fatal([$0 expanded multiple times ]m4_defn([_$0_ALREADY_INIT]))], [m4_define([_$0_ALREADY_INIT], m4_expansion_stack)])dnl dnl Autoconf wants to disallow AM_ names. We explicitly allow dnl the ones we care about. m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl # test to see if srcdir already configured if test -f $srcdir/config.status; then AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi AC_SUBST([CYGPATH_W]) # Define the identity of the package. dnl Distinguish between old-style and new-style calls. m4_ifval([$2], [AC_DIAGNOSE([obsolete], [$0: two- and three-arguments forms are deprecated.]) m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl AC_SUBST([PACKAGE], [$1])dnl AC_SUBST([VERSION], [$2])], [_AM_SET_OPTIONS([$1])dnl dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. m4_if( m4_ifset([AC_PACKAGE_NAME], [ok]):m4_ifset([AC_PACKAGE_VERSION], [ok]), [ok:ok],, [m4_fatal([AC_INIT should be called with package and version arguments])])dnl AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl _AM_IF_OPTION([no-define],, [AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl # Some tools Automake needs. AC_REQUIRE([AM_SANITY_CHECK])dnl AC_REQUIRE([AC_ARG_PROGRAM])dnl AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) AM_MISSING_PROG([AUTOCONF], [autoconf]) AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) AM_MISSING_PROG([AUTOHEADER], [autoheader]) AM_MISSING_PROG([MAKEINFO], [makeinfo]) AC_REQUIRE([AM_PROG_INSTALL_SH])dnl AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl AC_REQUIRE([AC_PROG_MKDIR_P])dnl # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # AC_SUBST([mkdir_p], ['$(MKDIR_P)']) # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AM_SET_LEADING_DOT])dnl _AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], [_AM_PROG_TAR([v7])])]) _AM_IF_OPTION([no-dependencies],, [AC_PROVIDE_IFELSE([AC_PROG_CC], [_AM_DEPENDENCIES([CC])], [m4_define([AC_PROG_CC], m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AM_DEPENDENCIES([CXX])], [m4_define([AC_PROG_CXX], m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJC], [_AM_DEPENDENCIES([OBJC])], [m4_define([AC_PROG_OBJC], m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], [_AM_DEPENDENCIES([OBJCXX])], [m4_define([AC_PROG_OBJCXX], m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl ]) # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi AC_SUBST([CTAGS]) if test -z "$ETAGS"; then ETAGS=etags fi AC_SUBST([ETAGS]) if test -z "$CSCOPE"; then CSCOPE=cscope fi AC_SUBST([CSCOPE]) AC_REQUIRE([_AM_SILENT_RULES])dnl dnl The testsuite driver may need to know about EXEEXT, so add the dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. AC_CONFIG_COMMANDS_PRE(dnl [m4_provide_if([_AM_COMPILER_EXEEXT], [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl AC_REQUIRE([_AM_PROG_RM_F]) AC_REQUIRE([_AM_PROG_XARGS_N]) dnl The trailing newline in this macro's definition is deliberate, for dnl backward compatibility and to allow trailing 'dnl'-style comments dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. ]) dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further dnl mangled by Autoconf and run in a shell conditional statement. m4_define([_AC_COMPILER_EXEEXT], m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) # When config.status generates a header, we must update the stamp-h file. # This file resides in the same directory as the config header # that is generated. The stamp files are numbered to have different names. # Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the # loop where config.status creates the headers, so we can generate # our stamp files there. AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], [# Compute $1's index in $config_headers. _am_arg=$1 _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) # Copyright (C) 2001-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_SH # ------------------ # Define $install_sh. AC_DEFUN([AM_PROG_INSTALL_SH], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi AC_SUBST([install_sh])]) # Copyright (C) 2003-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # Check whether the underlying file-system supports filenames # with a leading dot. For instance MS-DOS doesn't. AC_DEFUN([AM_SET_LEADING_DOT], [rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AC_SUBST([am__leading_dot])]) # Check to see how 'make' treats includes. -*- Autoconf -*- # Copyright (C) 2001-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MAKE_INCLUDE() # ----------------- # Check whether make has an 'include' directive that can support all # the idioms we need for our automatic dependency tracking code. AC_DEFUN([AM_MAKE_INCLUDE], [AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) AS_CASE([$?:`cat confinc.out 2>/dev/null`], ['0:this is the am__doit target'], [AS_CASE([$s], [BSD], [am__include='.include' am__quote='"'], [am__include='include' am__quote=''])]) if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* AC_MSG_RESULT([${_am_result}]) AC_SUBST([am__include])]) AC_SUBST([am__quote])]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it is modern enough. # If it is, set am_missing_run to use it, otherwise, to nothing. AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= AC_MSG_WARN(['missing' script is too old or missing]) fi ]) # Helper functions for option handling. -*- Autoconf -*- # Copyright (C) 2001-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_MANGLE_OPTION(NAME) # ----------------------- AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) # -------------------- # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), [1])]) # _AM_SET_OPTIONS(OPTIONS) # ------------------------ # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) # _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) # ------------------------------------------- # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) # Copyright (C) 1999-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_CC_C_O # --------------- # Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC # to automatically call this. AC_DEFUN([_AM_PROG_CC_C_O], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([compile])dnl AC_LANG_PUSH([C])dnl AC_CACHE_CHECK( [whether $CC understands -c and -o together], [am_cv_prog_cc_c_o], [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done rm -f core conftest* unset am_i]) if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi AC_LANG_POP([C])]) # For backward compatibility. AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) # Copyright (C) 2022-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_RM_F # --------------- # Check whether 'rm -f' without any arguments works. # https://bugs.gnu.org/10828 AC_DEFUN([_AM_PROG_RM_F], [am__rm_f_notfound= AS_IF([(rm -f && rm -fr && rm -rf) 2>/dev/null], [], [am__rm_f_notfound='""']) AC_SUBST(am__rm_f_notfound) ]) # Copyright (C) 2001-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_RUN_LOG(COMMAND) # ------------------- # Run COMMAND, save the exit status in ac_status, and log it. # (This has been adapted from Autoconf's _AC_RUN_LOG macro.) AC_DEFUN([AM_RUN_LOG], [{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD (exit $ac_status); }]) # Check to make sure that the build environment is sane. -*- Autoconf -*- # Copyright (C) 1996-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SLEEP_FRACTIONAL_SECONDS # ---------------------------- AC_DEFUN([_AM_SLEEP_FRACTIONAL_SECONDS], [dnl AC_CACHE_CHECK([whether sleep supports fractional seconds], am_cv_sleep_fractional_seconds, [dnl AS_IF([sleep 0.001 2>/dev/null], [am_cv_sleep_fractional_seconds=yes], [am_cv_sleep_fractional_seconds=no]) ])]) # _AM_FILESYSTEM_TIMESTAMP_RESOLUTION # ----------------------------------- # Determine the filesystem's resolution for file modification # timestamps. The coarsest we know of is FAT, with a resolution # of only two seconds, even with the most recent "exFAT" extensions. # The finest (e.g. ext4 with large inodes, XFS, ZFS) is one # nanosecond, matching clock_gettime. However, it is probably not # possible to delay execution of a shell script for less than one # millisecond, due to process creation overhead and scheduling # granularity, so we don't check for anything finer than that. (See below.) AC_DEFUN([_AM_FILESYSTEM_TIMESTAMP_RESOLUTION], [dnl AC_REQUIRE([_AM_SLEEP_FRACTIONAL_SECONDS]) AC_CACHE_CHECK([filesystem timestamp resolution], am_cv_filesystem_timestamp_resolution, [dnl # Default to the worst case. am_cv_filesystem_timestamp_resolution=2 # Only try to go finer than 1 sec if sleep can do it. # Don't try 1 sec, because if 0.01 sec and 0.1 sec don't work, # - 1 sec is not much of a win compared to 2 sec, and # - it takes 2 seconds to perform the test whether 1 sec works. # # Instead, just use the default 2s on platforms that have 1s resolution, # accept the extra 1s delay when using $sleep in the Automake tests, in # exchange for not incurring the 2s delay for running the test for all # packages. # am_try_resolutions= if test "$am_cv_sleep_fractional_seconds" = yes; then # Even a millisecond often causes a bunch of false positives, # so just try a hundredth of a second. The time saved between .001 and # .01 is not terribly consequential. am_try_resolutions="0.01 0.1 $am_try_resolutions" fi # In order to catch current-generation FAT out, we must *modify* files # that already exist; the *creation* timestamp is finer. Use names # that make ls -t sort them differently when they have equal # timestamps than when they have distinct timestamps, keeping # in mind that ls -t prints the *newest* file first. rm -f conftest.ts? : > conftest.ts1 : > conftest.ts2 : > conftest.ts3 # Make sure ls -t actually works. Do 'set' in a subshell so we don't # clobber the current shell's arguments. (Outer-level square brackets # are removed by m4; they're present so that m4 does not expand # ; be careful, easy to get confused.) if ( set X `[ls -t conftest.ts[12]]` && { test "$[]*" != "X conftest.ts1 conftest.ts2" || test "$[]*" != "X conftest.ts2 conftest.ts1"; } ); then :; else # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". _AS_ECHO_UNQUOTED( ["Bad output from ls -t: \"`[ls -t conftest.ts[12]]`\""], [AS_MESSAGE_LOG_FD]) AC_MSG_FAILURE([ls -t produces unexpected output. Make sure there is not a broken ls alias in your environment.]) fi for am_try_res in $am_try_resolutions; do # Any one fine-grained sleep might happen to cross the boundary # between two values of a coarser actual resolution, but if we do # two fine-grained sleeps in a row, at least one of them will fall # entirely within a coarse interval. echo alpha > conftest.ts1 sleep $am_try_res echo beta > conftest.ts2 sleep $am_try_res echo gamma > conftest.ts3 # We assume that 'ls -t' will make use of high-resolution # timestamps if the operating system supports them at all. if (set X `ls -t conftest.ts?` && test "$[]2" = conftest.ts3 && test "$[]3" = conftest.ts2 && test "$[]4" = conftest.ts1); then # # Ok, ls -t worked. If we're at a resolution of 1 second, we're done, # because we don't need to test make. make_ok=true if test $am_try_res != 1; then # But if we've succeeded so far with a subsecond resolution, we # have one more thing to check: make. It can happen that # everything else supports the subsecond mtimes, but make doesn't; # notably on macOS, which ships make 3.81 from 2006 (the last one # released under GPLv2). https://bugs.gnu.org/68808 # # We test $MAKE if it is defined in the environment, else "make". # It might get overridden later, but our hope is that in practice # it does not matter: it is the system "make" which is (by far) # the most likely to be broken, whereas if the user overrides it, # probably they did so with a better, or at least not worse, make. # https://lists.gnu.org/archive/html/automake/2024-06/msg00051.html # # Create a Makefile (real tab character here): rm -f conftest.mk echo 'conftest.ts1: conftest.ts2' >conftest.mk echo ' touch conftest.ts2' >>conftest.mk # # Now, running # touch conftest.ts1; touch conftest.ts2; make # should touch ts1 because ts2 is newer. This could happen by luck, # but most often, it will fail if make's support is insufficient. So # test for several consecutive successes. # # (We reuse conftest.ts[12] because we still want to modify existing # files, not create new ones, per above.) n=0 make=${MAKE-make} until test $n -eq 3; do echo one > conftest.ts1 sleep $am_try_res echo two > conftest.ts2 # ts2 should now be newer than ts1 if $make -f conftest.mk | grep 'up to date' >/dev/null; then make_ok=false break # out of $n loop fi n=`expr $n + 1` done fi # if $make_ok; then # Everything we know to check worked out, so call this resolution good. am_cv_filesystem_timestamp_resolution=$am_try_res break # out of $am_try_res loop fi # Otherwise, we'll go on to check the next resolution. fi done rm -f conftest.ts? # (end _am_filesystem_timestamp_resolution) ])]) # AM_SANITY_CHECK # --------------- AC_DEFUN([AM_SANITY_CHECK], [AC_REQUIRE([_AM_FILESYSTEM_TIMESTAMP_RESOLUTION]) # This check should not be cached, as it may vary across builds of # different projects. AC_MSG_CHECKING([whether build environment is sane]) # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[[\\\"\#\$\&\'\`$am_lf]]*) AC_MSG_ERROR([unsafe absolute working directory name]);; esac case $srcdir in *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). am_build_env_is_sane=no am_has_slept=no rm -f conftest.file for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file if ( set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$[]*" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi test "$[]2" = conftest.file ); then am_build_env_is_sane=yes break fi # Just in case. sleep "$am_cv_filesystem_timestamp_resolution" am_has_slept=yes done AC_MSG_RESULT([$am_build_env_is_sane]) if test "$am_build_env_is_sane" = no; then AC_MSG_ERROR([newly created file is older than distributed files! Check your system clock]) fi # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= AS_IF([test -e conftest.file || grep 'slept: no' conftest.file >/dev/null 2>&1],, [dnl ( sleep "$am_cv_filesystem_timestamp_resolution" ) & am_sleep_pid=$! ]) AC_CONFIG_COMMANDS_PRE( [AC_MSG_CHECKING([that generated files are newer than configure]) if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi AC_MSG_RESULT([done])]) rm -f conftest.file ]) # Copyright (C) 2009-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SILENT_RULES # ---------------- # Enable less verbose build rules support. AC_DEFUN([_AM_SILENT_RULES], [AM_DEFAULT_VERBOSITY=1 AC_ARG_ENABLE([silent-rules], [dnl AS_HELP_STRING( [--enable-silent-rules], [less verbose build output (undo: "make V=1")]) AS_HELP_STRING( [--disable-silent-rules], [verbose build output (undo: "make V=0")])dnl ]) dnl dnl A few 'make' implementations (e.g., NonStop OS and NextStep) dnl do not support nested variable expansions. dnl See automake bug#9928 and bug#10237. am_make=${MAKE-make} AC_CACHE_CHECK([whether $am_make supports nested variables], [am_cv_make_support_nested_variables], [if AS_ECHO([['TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi]) AC_SUBST([AM_V])dnl AM_SUBST_NOTMAKE([AM_V])dnl AC_SUBST([AM_DEFAULT_V])dnl AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl AC_SUBST([AM_DEFAULT_VERBOSITY])dnl AM_BACKSLASH='\' AC_SUBST([AM_BACKSLASH])dnl _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl dnl Delay evaluation of AM_DEFAULT_VERBOSITY to the end to allow multiple calls dnl to AM_SILENT_RULES to change the default value. AC_CONFIG_COMMANDS_PRE([dnl case $enable_silent_rules in @%:@ ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; esac if test $am_cv_make_support_nested_variables = yes; then dnl Using '$V' instead of '$(V)' breaks IRIX make. AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi ])dnl ]) # AM_SILENT_RULES([DEFAULT]) # -------------------------- # Set the default verbosity level to DEFAULT ("yes" being less verbose, "no" or # empty being verbose). AC_DEFUN([AM_SILENT_RULES], [AC_REQUIRE([_AM_SILENT_RULES]) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1])]) # Copyright (C) 2001-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor 'install' (even GNU) is that you can't # specify the program used to strip binaries. This is especially # annoying in cross-compiling environments, where the build's strip # is unlikely to handle the host's binaries. # Fortunately install-sh will honor a STRIPPROG variable, so we # always use install-sh in "make install-strip", and initialize # STRIPPROG with the value of the STRIP variable (set by the user). AC_DEFUN([AM_PROG_INSTALL_STRIP], [AC_REQUIRE([AM_PROG_INSTALL_SH])dnl # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. if test "$cross_compiling" != no; then AC_CHECK_TOOL([STRIP], [strip], :) fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) # Copyright (C) 2006-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_SUBST_NOTMAKE(VARIABLE) # --------------------------- # Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. # This macro is traced by Automake. AC_DEFUN([_AM_SUBST_NOTMAKE]) # AM_SUBST_NOTMAKE(VARIABLE) # -------------------------- # Public sister of _AM_SUBST_NOTMAKE. AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- # Copyright (C) 2004-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_TAR(FORMAT) # -------------------- # Check how to create a tarball in format FORMAT. # FORMAT should be one of 'v7', 'ustar', or 'pax'. # # Substitute a variable $(am__tar) that is a command # writing to stdout a FORMAT-tarball containing the directory # $tardir. # tardir=directory && $(am__tar) > result.tar # # Substitute a variable $(am__untar) that extract such # a tarball read from stdin. # $(am__untar) < result.tar # AC_DEFUN([_AM_PROG_TAR], [# Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AC_SUBST([AMTAR], ['$${TAR-tar}']) # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' m4_if([$1], [v7], [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], [m4_case([$1], [ustar], [# The POSIX 1988 'ustar' format is defined with fixed-size fields. # There is notably a 21 bits limit for the UID and the GID. In fact, # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 # and bug#13588). am_max_uid=2097151 # 2^21 - 1 am_max_gid=$am_max_uid # The $UID and $GID variables are not portable, so we need to resort # to the POSIX-mandated id(1) utility. Errors in the 'id' calls # below are definitely unexpected, so allow the users to see them # (that is, avoid stderr redirection). am_uid=`id -u || echo unknown` am_gid=`id -g || echo unknown` AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) if test x$am_uid = xunknown; then AC_MSG_WARN([ancient id detected; assuming current UID is ok, but dist-ustar might not work]) elif test $am_uid -le $am_max_uid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) if test x$gm_gid = xunknown; then AC_MSG_WARN([ancient id detected; assuming current GID is ok, but dist-ustar might not work]) elif test $am_gid -le $am_max_gid; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) _am_tools=none fi], [pax], [], [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) # Go ahead even if we have the value already cached. We do so because we # need to set the values for the 'am__tar' and 'am__untar' variables. _am_tools=${am_cv_prog_tar_$1-$_am_tools} for _am_tool in $_am_tools; do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do AM_RUN_LOG([$_am_tar --version]) && break done am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x $1 -w "$$tardir"' am__tar_='pax -L -x $1 -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H $1 -L' am__tar_='find "$tardir" -print | cpio -o -H $1 -L' am__untar='cpio -i -H $1 -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_$1}" && break # tar/untar a dummy directory, and stop if the command works. rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) rm -rf conftest.dir if test -s conftest.tar; then AM_RUN_LOG([$am__untar /dev/null 2>&1 && break fi done rm -rf conftest.dir AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) AC_MSG_RESULT([$am_cv_prog_tar_$1])]) AC_SUBST([am__tar]) AC_SUBST([am__untar]) ]) # _AM_PROG_TAR # Copyright (C) 2022-2024 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # _AM_PROG_XARGS_N # ---------------- # Check whether 'xargs -n' works. It should work everywhere, so the fallback # is not optimized at all as we never expect to use it. AC_DEFUN([_AM_PROG_XARGS_N], [AC_CACHE_CHECK([xargs -n works], am_cv_xargs_n_works, [dnl AS_IF([test "`echo 1 2 3 | xargs -n2 echo`" = "1 2 3"], [am_cv_xargs_n_works=yes], [am_cv_xargs_n_works=no])]) AS_IF([test "$am_cv_xargs_n_works" = yes], [am__xargs_n='xargs -n'], [dnl am__xargs_n='am__xargs_n () { shift; sed "s/ /\\n/g" | while read am__xargs_n_arg; do "$@" "$am__xargs_n_arg"; done; }' ])dnl AC_SUBST(am__xargs_n) ]) ffmpegfs-2.50/configure0000775000175000017500000113004615215723144010640 #! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.72 for FFMPEGFS 2.50. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case e in #( e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else case e in #( e) exitcode=1; echo positional parameters were not saved. ;; esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else case e in #( e) as_have_required=no ;; esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi ;; esac fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf "%s\n" "$0: This script requires a shell more modern than all" printf "%s\n" "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi ;; esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' t clear :clear s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='FFMPEGFS' PACKAGE_TARNAME='ffmpegfs' PACKAGE_VERSION='2.50' PACKAGE_STRING='FFMPEGFS 2.50' PACKAGE_BUGREPORT='' PACKAGE_URL='' ac_unique_file="src/ffmpegfs.cc" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= enable_year2038=no ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS ENABLE_EXTRA_VERSION_FALSE ENABLE_EXTRA_VERSION_TRUE EXTRA_VERSION NOCHANGELOG_FALSE NOCHANGELOG_TRUE DEBUG_FALSE DEBUG_TRUE GRAPHVIZ CURL DOXYGEN USE_LIBVCD_FALSE USE_LIBVCD_TRUE ENABLE_PERFTOOLS_FALSE ENABLE_PERFTOOLS_TRUE PERFTOOLS_LIBS PERFTOOLS_CXXFLAGS PERFTOOLS_CFLAGS HINT_LIBBLURAY_FALSE HINT_LIBBLURAY_TRUE USE_LIBBLURAY_FALSE USE_LIBBLURAY_TRUE libbluray_LIBS libbluray_CFLAGS HINT_LIBDVD_FALSE HINT_LIBDVD_TRUE USE_LIBDVD_FALSE USE_LIBDVD_TRUE libdvdread_LIBS libdvdread_CFLAGS libswresample_LIBS libswresample_CFLAGS libswscale_LIBS libswscale_CFLAGS libavfilter_LIBS libavfilter_CFLAGS libavformat_LIBS libavformat_CFLAGS libavcodec_LIBS libavcodec_CFLAGS libavutil_LIBS libavutil_CFLAGS HAVE_W3M W3M HAVE_A2X A2X sqlite3_LIBS sqlite3_CFLAGS libcue_LIBS libcue_CFLAGS fuse3_LIBS fuse3_CFLAGS chardet_LIBS chardet_CFLAGS PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG RANLIB am__fastdepCXX_FALSE am__fastdepCXX_TRUE CXXDEPMODE ac_ct_CXX CXXFLAGS CXX am__fastdepCC_FALSE am__fastdepCC_TRUE CCDEPMODE am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE am__include DEPDIR OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC host_os host_vendor host_cpu host build_os build_vendor build_cpu build am__xargs_n am__rm_f_notfound AM_BACKSLASH AM_DEFAULT_VERBOSITY AM_DEFAULT_V AM_V CSCOPE ETAGS CTAGS am__untar am__tar AMTAR am__leading_dot SET_MAKE AWK mkdir_p MKDIR_P INSTALL_STRIP_PROGRAM STRIP install_sh MAKEINFO AUTOHEADER AUTOMAKE AUTOCONF ACLOCAL VERSION PACKAGE CYGPATH_W am__isrc INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL am__quote' ac_subst_files='' ac_user_opts=' enable_option_checking enable_silent_rules enable_dependency_tracking enable_largefile with_libdvd with_libbluray enable_perftools with_libvcd enable_debug enable_changelog with_extra_version enable_year2038 ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS CXX CXXFLAGS CCC PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR chardet_CFLAGS chardet_LIBS fuse3_CFLAGS fuse3_LIBS libcue_CFLAGS libcue_LIBS sqlite3_CFLAGS sqlite3_LIBS libavutil_CFLAGS libavutil_LIBS libavcodec_CFLAGS libavcodec_LIBS libavformat_CFLAGS libavformat_LIBS libavfilter_CFLAGS libavfilter_LIBS libswscale_CFLAGS libswscale_LIBS libswresample_CFLAGS libswresample_LIBS libdvdread_CFLAGS libdvdread_LIBS libbluray_CFLAGS libbluray_LIBS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: '$ac_option' Try '$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF 'configure' configures FFMPEGFS 2.50 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, 'make install' will install all the files in '$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify an installation prefix other than '$ac_default_prefix' using '--prefix', for instance '--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/ffmpegfs] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of FFMPEGFS 2.50:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") --enable-dependency-tracking do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build --disable-largefile omit support for large files --enable-perftools link ffmpegfs with Google perftools/tcmalloc heap profiler, default=no --enable-debug Enable debugging, default=no --enable-changelog Turn on changelog, default=no --enable-year2038 support timestamps after 2038 Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-libdvd support using libdvd [default=check] --with-libbluray support using libbluray [default=check] --with-libvcd support using libvcd [default=check] --with-extra-version=STRING append STRING to version Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CXX C++ compiler command CXXFLAGS C++ compiler flags PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path chardet_CFLAGS C compiler flags for chardet, overriding pkg-config chardet_LIBS linker flags for chardet, overriding pkg-config fuse3_CFLAGS C compiler flags for fuse3, overriding pkg-config fuse3_LIBS linker flags for fuse3, overriding pkg-config libcue_CFLAGS C compiler flags for libcue, overriding pkg-config libcue_LIBS linker flags for libcue, overriding pkg-config sqlite3_CFLAGS C compiler flags for sqlite3, overriding pkg-config sqlite3_LIBS linker flags for sqlite3, overriding pkg-config libavutil_CFLAGS C compiler flags for libavutil, overriding pkg-config libavutil_LIBS linker flags for libavutil, overriding pkg-config libavcodec_CFLAGS C compiler flags for libavcodec, overriding pkg-config libavcodec_LIBS linker flags for libavcodec, overriding pkg-config libavformat_CFLAGS C compiler flags for libavformat, overriding pkg-config libavformat_LIBS linker flags for libavformat, overriding pkg-config libavfilter_CFLAGS C compiler flags for libavfilter, overriding pkg-config libavfilter_LIBS linker flags for libavfilter, overriding pkg-config libswscale_CFLAGS C compiler flags for libswscale, overriding pkg-config libswscale_LIBS linker flags for libswscale, overriding pkg-config libswresample_CFLAGS C compiler flags for libswresample, overriding pkg-config libswresample_LIBS linker flags for libswresample, overriding pkg-config libdvdread_CFLAGS C compiler flags for libdvdread, overriding pkg-config libdvdread_LIBS linker flags for libdvdread, overriding pkg-config libbluray_CFLAGS C compiler flags for libbluray, overriding pkg-config libbluray_LIBS linker flags for libbluray, overriding pkg-config Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF FFMPEGFS configure 2.50 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_cxx_try_compile LINENO # ---------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_compile # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache # variable VAR accordingly. ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) eval "$3=no" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof ($2)) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof (($2))) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) eval "$3=yes" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_type # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_find_intX_t LINENO BITS VAR # ----------------------------------- # Finds a signed integer type with width BITS, setting cache variable VAR # accordingly. ac_fn_c_find_intX_t () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for int$2_t" >&5 printf %s "checking for int$2_t... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) eval "$3=no" # Order is important - never check a type that is potentially smaller # than half of the expected target width. for ac_type in int$2_t 'int' 'long int' \ 'long long int' 'short int' 'signed char'; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default enum { N = $2 / 2 - 1 }; int main (void) { static int test_array [1 - 2 * !(0 < ($ac_type) ((((($ac_type) 1 << N) << N) - 1) * 2 + 1))]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default enum { N = $2 / 2 - 1 }; int main (void) { static int test_array [1 - 2 * !(($ac_type) ((((($ac_type) 1 << N) << N) - 1) * 2 + 1) < ($ac_type) ((((($ac_type) 1 << N) << N) - 1) * 2 + 2))]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) case $ac_type in #( int$2_t) : eval "$3=yes" ;; #( *) : eval "$3=\$ac_type" ;; esac ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if eval test \"x\$"$3"\" = x"no" then : else case e in #( e) break ;; esac fi done ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_find_intX_t # ac_fn_c_find_uintX_t LINENO BITS VAR # ------------------------------------ # Finds an unsigned integer type with width BITS, setting cache variable VAR # accordingly. ac_fn_c_find_uintX_t () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for uint$2_t" >&5 printf %s "checking for uint$2_t... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) eval "$3=no" # Order is important - never check a type that is potentially smaller # than half of the expected target width. for ac_type in uint$2_t 'unsigned int' 'unsigned long int' \ 'unsigned long long int' 'unsigned short int' 'unsigned char'; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int main (void) { static int test_array [1 - 2 * !((($ac_type) -1 >> ($2 / 2 - 1)) >> ($2 / 2 - 1) == 3)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : case $ac_type in #( uint$2_t) : eval "$3=yes" ;; #( *) : eval "$3=\$ac_type" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if eval test \"x\$"$3"\" = x"no" then : else case e in #( e) break ;; esac fi done ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_find_uintX_t # ac_fn_c_try_run LINENO # ---------------------- # Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that # executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: program exited with status $ac_status" >&5 printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status ;; esac fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_compute_int LINENO EXPR VAR INCLUDES # -------------------------------------------- # Tries to find the compile-time value of EXPR in a program that includes # INCLUDES, setting VAR accordingly. Returns whether the value could be # computed ac_fn_c_compute_int () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if test "$cross_compiling" = yes; then # Depending upon the size, compute the lo and hi bounds. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) >= 0)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_lo=0 ac_mid=0 while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) <= $ac_mid)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_hi=$ac_mid; break else case e in #( e) as_fn_arith $ac_mid + 1 && ac_lo=$as_val if test $ac_lo -le $ac_mid; then ac_lo= ac_hi= break fi as_fn_arith 2 '*' $ac_mid + 1 && ac_mid=$as_val ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) < 0)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_hi=-1 ac_mid=-1 while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) >= $ac_mid)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_lo=$ac_mid; break else case e in #( e) as_fn_arith '(' $ac_mid ')' - 1 && ac_hi=$as_val if test $ac_mid -le $ac_hi; then ac_lo= ac_hi= break fi as_fn_arith 2 '*' $ac_mid && ac_mid=$as_val ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done else case e in #( e) ac_lo= ac_hi= ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext # Binary search between lo and hi bounds. while test "x$ac_lo" != "x$ac_hi"; do as_fn_arith '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo && ac_mid=$as_val cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) <= $ac_mid)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_hi=$ac_mid else case e in #( e) as_fn_arith '(' $ac_mid ')' + 1 && ac_lo=$as_val ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done case $ac_lo in #(( ?*) eval "$3=\$ac_lo"; ac_retval=0 ;; '') ac_retval=1 ;; esac else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 static long int longval (void) { return $2; } static unsigned long int ulongval (void) { return $2; } #include #include int main (void) { FILE *f = fopen ("conftest.val", "w"); if (! f) return 1; if (($2) < 0) { long int i = longval (); if (i != ($2)) return 1; fprintf (f, "%ld", i); } else { unsigned long int i = ulongval (); if (i != ($2)) return 1; fprintf (f, "%lu", i); } /* Do not output a trailing newline, as this causes \r\n confusion on some platforms. */ return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : echo >>conftest.val; read $3 &5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by FFMPEGFS $as_me 2.50, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf "%s\n" "$as_me: caught signal $ac_signal" printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See 'config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Does the compiler advertise C89 conformance? Do not test the value of __STDC__, because some compilers set it to 0 while being otherwise adequately conformant. */ #if !defined __STDC__ # error "Compiler does not advertise C89 conformance" #endif #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (char **p, int i) { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* C89 style stringification. */ #define noexpand_stringify(a) #a const char *stringified = noexpand_stringify(arbitrary+token=sequence); /* C89 style token pasting. Exercises some of the corner cases that e.g. old MSVC gets wrong, but not very hard. */ #define noexpand_concat(a,b) a##b #define expand_concat(a,b) noexpand_concat(a,b) extern int vA; extern int vbee; #define aye A #define bee B int *pvA = &expand_concat(v,aye); int *pvbee = &noexpand_concat(v,bee); /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' /* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif // See if C++-style comments work. #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Work around memory leak warnings. free (ia); // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[0] = argv[0][0]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || dynamic_array[ni.number - 1] != 543); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' /* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " # Test code for whether the C++ compiler supports C++98 (global declarations) ac_cxx_conftest_cxx98_globals=' // Does the compiler advertise C++98 conformance? #if !defined __cplusplus || __cplusplus < 199711L # error "Compiler does not advertise C++98 conformance" #endif // These inclusions are to reject old compilers that // lack the unsuffixed header files. #include #include // and are *not* freestanding headers in C++98. extern void assert (int); namespace std { extern int strcmp (const char *, const char *); } // Namespaces, exceptions, and templates were all added after "C++ 2.0". using std::exception; using std::strcmp; namespace { void test_exception_syntax() { try { throw "test"; } catch (const char *s) { // Extra parentheses suppress a warning when building autoconf itself, // due to lint rules shared with more typical C programs. assert (!(strcmp) (s, "test")); } } template struct test_template { T const val; explicit test_template(T t) : val(t) {} template T add(U u) { return static_cast(u) + val; } }; } // anonymous namespace ' # Test code for whether the C++ compiler supports C++98 (body of main) ac_cxx_conftest_cxx98_main=' assert (argc); assert (! argv[0]); { test_exception_syntax (); test_template tt (2.0); assert (tt.add (4) == 6.0); assert (true && !false); } ' # Test code for whether the C++ compiler supports C++11 (global declarations) ac_cxx_conftest_cxx11_globals=' // Does the compiler advertise C++ 2011 conformance? #if !defined __cplusplus || __cplusplus < 201103L # error "Compiler does not advertise C++11 conformance" #endif namespace cxx11test { constexpr int get_val() { return 20; } struct testinit { int i; double d; }; class delegate { public: delegate(int n) : n(n) {} delegate(): delegate(2354) {} virtual int getval() { return this->n; }; protected: int n; }; class overridden : public delegate { public: overridden(int n): delegate(n) {} virtual int getval() override final { return this->n * 2; } }; class nocopy { public: nocopy(int i): i(i) {} nocopy() = default; nocopy(const nocopy&) = delete; nocopy & operator=(const nocopy&) = delete; private: int i; }; // for testing lambda expressions template Ret eval(Fn f, Ret v) { return f(v); } // for testing variadic templates and trailing return types template auto sum(V first) -> V { return first; } template auto sum(V first, Args... rest) -> V { return first + sum(rest...); } } ' # Test code for whether the C++ compiler supports C++11 (body of main) ac_cxx_conftest_cxx11_main=' { // Test auto and decltype auto a1 = 6538; auto a2 = 48573953.4; auto a3 = "String literal"; int total = 0; for (auto i = a3; *i; ++i) { total += *i; } decltype(a2) a4 = 34895.034; } { // Test constexpr short sa[cxx11test::get_val()] = { 0 }; } { // Test initializer lists cxx11test::testinit il = { 4323, 435234.23544 }; } { // Test range-based for int array[] = {9, 7, 13, 15, 4, 18, 12, 10, 5, 3, 14, 19, 17, 8, 6, 20, 16, 2, 11, 1}; for (auto &x : array) { x += 23; } } { // Test lambda expressions using cxx11test::eval; assert (eval ([](int x) { return x*2; }, 21) == 42); double d = 2.0; assert (eval ([&](double x) { return d += x; }, 3.0) == 5.0); assert (d == 5.0); assert (eval ([=](double x) mutable { return d += x; }, 4.0) == 9.0); assert (d == 5.0); } { // Test use of variadic templates using cxx11test::sum; auto a = sum(1); auto b = sum(1, 2); auto c = sum(1.0, 2.0, 3.0); } { // Test constructor delegation cxx11test::delegate d1; cxx11test::delegate d2(); cxx11test::delegate d3(45); } { // Test override and final cxx11test::overridden o1(55464); } { // Test nullptr char *c = nullptr; } { // Test template brackets test_template<::test_template> v(test_template(12)); } { // Unicode literals char const *utf8 = u8"UTF-8 string \u2500"; char16_t const *utf16 = u"UTF-8 string \u2500"; char32_t const *utf32 = U"UTF-32 string \u2500"; } ' # Test code for whether the C compiler supports C++11 (complete). ac_cxx_conftest_cxx11_program="${ac_cxx_conftest_cxx98_globals} ${ac_cxx_conftest_cxx11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_cxx_conftest_cxx98_main} ${ac_cxx_conftest_cxx11_main} return ok; } " # Test code for whether the C compiler supports C++98 (complete). ac_cxx_conftest_cxx98_program="${ac_cxx_conftest_cxx98_globals} int main (int argc, char **argv) { int ok = 0; ${ac_cxx_conftest_cxx98_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" # Auxiliary files required by this configure script. ac_aux_files="compile config.guess config.sub missing install-sh" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}/config" # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; esac fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers src/config.h" # Get configure arguments configure_args="$*" printf "%s\n" "#define CONFIGURE_ARGS \"$configure_args\"" >>confdefs.h am__api_version='1.17' # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir ;; esac fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf "%s\n" "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether sleep supports fractional seconds" >&5 printf %s "checking whether sleep supports fractional seconds... " >&6; } if test ${am_cv_sleep_fractional_seconds+y} then : printf %s "(cached) " >&6 else case e in #( e) if sleep 0.001 2>/dev/null then : am_cv_sleep_fractional_seconds=yes else case e in #( e) am_cv_sleep_fractional_seconds=no ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_sleep_fractional_seconds" >&5 printf "%s\n" "$am_cv_sleep_fractional_seconds" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking filesystem timestamp resolution" >&5 printf %s "checking filesystem timestamp resolution... " >&6; } if test ${am_cv_filesystem_timestamp_resolution+y} then : printf %s "(cached) " >&6 else case e in #( e) # Default to the worst case. am_cv_filesystem_timestamp_resolution=2 # Only try to go finer than 1 sec if sleep can do it. # Don't try 1 sec, because if 0.01 sec and 0.1 sec don't work, # - 1 sec is not much of a win compared to 2 sec, and # - it takes 2 seconds to perform the test whether 1 sec works. # # Instead, just use the default 2s on platforms that have 1s resolution, # accept the extra 1s delay when using $sleep in the Automake tests, in # exchange for not incurring the 2s delay for running the test for all # packages. # am_try_resolutions= if test "$am_cv_sleep_fractional_seconds" = yes; then # Even a millisecond often causes a bunch of false positives, # so just try a hundredth of a second. The time saved between .001 and # .01 is not terribly consequential. am_try_resolutions="0.01 0.1 $am_try_resolutions" fi # In order to catch current-generation FAT out, we must *modify* files # that already exist; the *creation* timestamp is finer. Use names # that make ls -t sort them differently when they have equal # timestamps than when they have distinct timestamps, keeping # in mind that ls -t prints the *newest* file first. rm -f conftest.ts? : > conftest.ts1 : > conftest.ts2 : > conftest.ts3 # Make sure ls -t actually works. Do 'set' in a subshell so we don't # clobber the current shell's arguments. (Outer-level square brackets # are removed by m4; they're present so that m4 does not expand # ; be careful, easy to get confused.) if ( set X `ls -t conftest.ts[12]` && { test "$*" != "X conftest.ts1 conftest.ts2" || test "$*" != "X conftest.ts2 conftest.ts1"; } ); then :; else # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". printf "%s\n" ""Bad output from ls -t: \"`ls -t conftest.ts[12]`\""" >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "ls -t produces unexpected output. Make sure there is not a broken ls alias in your environment. See 'config.log' for more details" "$LINENO" 5; } fi for am_try_res in $am_try_resolutions; do # Any one fine-grained sleep might happen to cross the boundary # between two values of a coarser actual resolution, but if we do # two fine-grained sleeps in a row, at least one of them will fall # entirely within a coarse interval. echo alpha > conftest.ts1 sleep $am_try_res echo beta > conftest.ts2 sleep $am_try_res echo gamma > conftest.ts3 # We assume that 'ls -t' will make use of high-resolution # timestamps if the operating system supports them at all. if (set X `ls -t conftest.ts?` && test "$2" = conftest.ts3 && test "$3" = conftest.ts2 && test "$4" = conftest.ts1); then # # Ok, ls -t worked. If we're at a resolution of 1 second, we're done, # because we don't need to test make. make_ok=true if test $am_try_res != 1; then # But if we've succeeded so far with a subsecond resolution, we # have one more thing to check: make. It can happen that # everything else supports the subsecond mtimes, but make doesn't; # notably on macOS, which ships make 3.81 from 2006 (the last one # released under GPLv2). https://bugs.gnu.org/68808 # # We test $MAKE if it is defined in the environment, else "make". # It might get overridden later, but our hope is that in practice # it does not matter: it is the system "make" which is (by far) # the most likely to be broken, whereas if the user overrides it, # probably they did so with a better, or at least not worse, make. # https://lists.gnu.org/archive/html/automake/2024-06/msg00051.html # # Create a Makefile (real tab character here): rm -f conftest.mk echo 'conftest.ts1: conftest.ts2' >conftest.mk echo ' touch conftest.ts2' >>conftest.mk # # Now, running # touch conftest.ts1; touch conftest.ts2; make # should touch ts1 because ts2 is newer. This could happen by luck, # but most often, it will fail if make's support is insufficient. So # test for several consecutive successes. # # (We reuse conftest.ts[12] because we still want to modify existing # files, not create new ones, per above.) n=0 make=${MAKE-make} until test $n -eq 3; do echo one > conftest.ts1 sleep $am_try_res echo two > conftest.ts2 # ts2 should now be newer than ts1 if $make -f conftest.mk | grep 'up to date' >/dev/null; then make_ok=false break # out of $n loop fi n=`expr $n + 1` done fi # if $make_ok; then # Everything we know to check worked out, so call this resolution good. am_cv_filesystem_timestamp_resolution=$am_try_res break # out of $am_try_res loop fi # Otherwise, we'll go on to check the next resolution. fi done rm -f conftest.ts? # (end _am_filesystem_timestamp_resolution) ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_filesystem_timestamp_resolution" >&5 printf "%s\n" "$am_cv_filesystem_timestamp_resolution" >&6; } # This check should not be cached, as it may vary across builds of # different projects. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 printf %s "checking whether build environment is sane... " >&6; } # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[\\\"\#\$\&\'\`$am_lf]*) as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; esac case $srcdir in *[\\\"\#\$\&\'\`$am_lf\ \ ]*) as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). am_build_env_is_sane=no am_has_slept=no rm -f conftest.file for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file if ( set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$*" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi test "$2" = conftest.file ); then am_build_env_is_sane=yes break fi # Just in case. sleep "$am_cv_filesystem_timestamp_resolution" am_has_slept=yes done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_build_env_is_sane" >&5 printf "%s\n" "$am_build_env_is_sane" >&6; } if test "$am_build_env_is_sane" = no; then as_fn_error $? "newly created file is older than distributed files! Check your system clock" "$LINENO" 5 fi # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= if test -e conftest.file || grep 'slept: no' conftest.file >/dev/null 2>&1 then : else case e in #( e) ( sleep "$am_cv_filesystem_timestamp_resolution" ) & am_sleep_pid=$! ;; esac fi rm -f conftest.file test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. # By default was 's,x,x', remove it if useless. ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' program_transform_name=`printf "%s\n" "$program_transform_name" | sed "$ac_script"` # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 printf "%s\n" "$as_me: WARNING: 'missing' script is too old or missing" >&2;} fi if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 printf "%s\n" "$STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 printf "%s\n" "$ac_ct_STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a race-free mkdir -p" >&5 printf %s "checking for a race-free mkdir -p... " >&6; } if test -z "$MKDIR_P"; then if test ${ac_cv_path_mkdir+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in mkdir gmkdir; do for ac_exec_ext in '' $ac_executable_extensions; do as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext" || continue case `"$as_dir$ac_prog$ac_exec_ext" --version 2>&1` in #( 'mkdir ('*'coreutils) '* | \ *'BusyBox '* | \ 'mkdir (fileutils) '4.1*) ac_cv_path_mkdir=$as_dir$ac_prog$ac_exec_ext break 3;; esac done done done IFS=$as_save_IFS ;; esac fi test -d ./--version && rmdir ./--version if test ${ac_cv_path_mkdir+y}; then MKDIR_P="$ac_cv_path_mkdir -p" else # As a last resort, use plain mkdir -p, # in the hope it doesn't have the bugs of ancient mkdir. MKDIR_P='mkdir -p' fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 printf "%s\n" "$MKDIR_P" >&6; } for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AWK+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 printf "%s\n" "$AWK" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$AWK" && break done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`printf "%s\n" "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval test \${ac_cv_prog_make_${ac_make}_set+y} then : printf %s "(cached) " >&6 else case e in #( e) cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make ;; esac fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } SET_MAKE= else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AM_DEFAULT_VERBOSITY=1 # Check whether --enable-silent-rules was given. if test ${enable_silent_rules+y} then : enableval=$enable_silent_rules; fi am_make=${MAKE-make} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 printf %s "checking whether $am_make supports nested variables... " >&6; } if test ${am_cv_make_support_nested_variables+y} then : printf %s "(cached) " >&6 else case e in #( e) if printf "%s\n" 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 printf "%s\n" "$am_cv_make_support_nested_variables" >&6; } AM_BACKSLASH='\' am__rm_f_notfound= if (rm -f && rm -fr && rm -rf) 2>/dev/null then : else case e in #( e) am__rm_f_notfound='""' ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking xargs -n works" >&5 printf %s "checking xargs -n works... " >&6; } if test ${am_cv_xargs_n_works+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "`echo 1 2 3 | xargs -n2 echo`" = "1 2 3" then : am_cv_xargs_n_works=yes else case e in #( e) am_cv_xargs_n_works=no ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_xargs_n_works" >&5 printf "%s\n" "$am_cv_xargs_n_works" >&6; } if test "$am_cv_xargs_n_works" = yes then : am__xargs_n='xargs -n' else case e in #( e) am__xargs_n='am__xargs_n () { shift; sed "s/ /\\n/g" | while read am__xargs_n_arg; do "" "$am__xargs_n_arg"; done; }' ;; esac fi if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." am__isrc=' -I$(srcdir)' # test to see if srcdir already configured if test -f $srcdir/config.status; then as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE='ffmpegfs' VERSION='2.50' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf "%s\n" "#define VERSION \"$VERSION\"" >>confdefs.h # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # mkdir_p='$(MKDIR_P)' # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. # Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AMTAR='$${TAR-tar}' # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar pax cpio none' am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi if test -z "$ETAGS"; then ETAGS=etags fi if test -z "$CSCOPE"; then CSCOPE=cscope fi AM_DEFAULT_VERBOSITY=0 # AC_CANONICAL_HOST is needed to access the 'host_os' variable # Make sure we can run config.sub. $SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 printf %s "checking build system type... " >&6; } if test ${ac_cv_build+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5 ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 printf "%s\n" "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 printf %s "checking host system type... " >&6; } if test ${ac_cv_host+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5 fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 printf "%s\n" "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac printf "%s\n" "#define HOST_OS \"$host_os\"" >>confdefs.h # Checks for programs ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. # So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else case e in #( e) ac_file='' ;; esac fi if test -z "$ac_file" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See 'config.log' for more details" "$LINENO" 5; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) # catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will # work properly (i.e., refer to 'conftest.exe'), while it won't with # 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); if (!f) return 1; return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use '--host'. See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext \ conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else case e in #( e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 printf %s "checking whether $CC understands -c and -o together... " >&6; } if test ${am_cv_prog_cc_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done rm -f core conftest* unset am_i ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 printf "%s\n" "$am_cv_prog_cc_c_o" >&6; } if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 printf %s "checking whether ${MAKE-make} supports the include directive... " >&6; } cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } case $?:`cat confinc.out 2>/dev/null` in #( '0:this is the am__doit target') : case $s in #( BSD) : am__include='.include' am__quote='"' ;; #( *) : am__include='include' am__quote='' ;; esac ;; #( *) : ;; esac if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 printf "%s\n" "${_am_result}" >&6; } # Check whether --enable-dependency-tracking was given. if test ${enable_dependency_tracking+y} then : enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi depcc="$CC" am_compiler_list= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CC_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CC_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CC_dependencies_compiler_type=none fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 printf "%s\n" "$am_cv_CC_dependencies_compiler_type" >&6; } CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then am__fastdepCC_TRUE= am__fastdepCC_FALSE='#' else am__fastdepCC_TRUE='#' am__fastdepCC_FALSE= fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test -z "$CXX"; then if test -n "$CCC"; then CXX=$CCC else if test -n "$ac_tool_prefix"; then for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 printf "%s\n" "$CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CXX" && break done fi if test -z "$CXX"; then ac_ct_CXX=$CXX for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CXX="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 printf "%s\n" "$ac_ct_CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CXX" && break done if test "x$ac_ct_CXX" = x; then CXX="g++" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX fi fi fi fi # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C++" >&5 printf %s "checking whether the compiler supports GNU C++... " >&6; } if test ${ac_cv_cxx_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 printf "%s\n" "$ac_cv_cxx_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test $ac_compiler_gnu = yes; then GXX=yes else GXX= fi ac_test_CXXFLAGS=${CXXFLAGS+y} ac_save_CXXFLAGS=$CXXFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 printf %s "checking whether $CXX accepts -g... " >&6; } if test ${ac_cv_prog_cxx_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_g=yes else case e in #( e) CXXFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : else case e in #( e) ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cxx_werror_flag=$ac_save_cxx_werror_flag ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 printf "%s\n" "$ac_cv_prog_cxx_g" >&6; } if test $ac_test_CXXFLAGS; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then CXXFLAGS="-g -O2" else CXXFLAGS="-g" fi else if test "$GXX" = yes; then CXXFLAGS="-O2" else CXXFLAGS= fi fi ac_prog_cxx_stdcxx=no if test x$ac_prog_cxx_stdcxx = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++11 features" >&5 printf %s "checking for $CXX option to enable C++11 features... " >&6; } if test ${ac_cv_prog_cxx_cxx11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cxx_cxx11=no ac_save_CXX=$CXX cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_cxx_conftest_cxx11_program _ACEOF for ac_arg in '' -std=gnu++11 -std=gnu++0x -std=c++11 -std=c++0x -qlanglvl=extended0x -AA do CXX="$ac_save_CXX $ac_arg" if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_cxx11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cxx_cxx11" != "xno" && break done rm -f conftest.$ac_ext CXX=$ac_save_CXX ;; esac fi if test "x$ac_cv_prog_cxx_cxx11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cxx_cxx11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx11" >&5 printf "%s\n" "$ac_cv_prog_cxx_cxx11" >&6; } CXX="$CXX $ac_cv_prog_cxx_cxx11" ;; esac fi ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx11 ac_prog_cxx_stdcxx=cxx11 ;; esac fi fi if test x$ac_prog_cxx_stdcxx = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++98 features" >&5 printf %s "checking for $CXX option to enable C++98 features... " >&6; } if test ${ac_cv_prog_cxx_cxx98+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cxx_cxx98=no ac_save_CXX=$CXX cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_cxx_conftest_cxx98_program _ACEOF for ac_arg in '' -std=gnu++98 -std=c++98 -qlanglvl=extended -AA do CXX="$ac_save_CXX $ac_arg" if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_cxx98=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cxx_cxx98" != "xno" && break done rm -f conftest.$ac_ext CXX=$ac_save_CXX ;; esac fi if test "x$ac_cv_prog_cxx_cxx98" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cxx_cxx98" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx98" >&5 printf "%s\n" "$ac_cv_prog_cxx_cxx98" >&6; } CXX="$CXX $ac_cv_prog_cxx_cxx98" ;; esac fi ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx98 ac_prog_cxx_stdcxx=cxx98 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu depcc="$CXX" am_compiler_list= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CXX_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CXX_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CXX_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CXX_dependencies_compiler_type=none fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_CXX_dependencies_compiler_type" >&5 printf "%s\n" "$am_cv_CXX_dependencies_compiler_type" >&6; } CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then am__fastdepCXX_TRUE= am__fastdepCXX_FALSE='#' else am__fastdepCXX_TRUE='#' am__fastdepCXX_FALSE= fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 printf "%s\n" "$RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 printf "%s\n" "$ac_ct_RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi # Checks for typedefs, structures, and compiler characteristics. ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf "%s\n" "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h fi ac_fn_c_check_type "$LINENO" "_Bool" "ac_cv_type__Bool" "$ac_includes_default" if test "x$ac_cv_type__Bool" = xyes then : printf "%s\n" "#define HAVE__BOOL 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdbool.h that conforms to C99 or later" >&5 printf %s "checking for stdbool.h that conforms to C99 or later... " >&6; } if test ${ac_cv_header_stdbool_h+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* "true" and "false" should be usable in #if expressions and integer constant expressions, and "bool" should be a valid type name. Although C99 requires bool, true, and false to be macros, C23 and C++11 overrule that, so do not test for that. Although C99 requires __bool_true_false_are_defined and _Bool, C23 says they are obsolescent, so do not require them. */ #if !true #error "'true' is not true" #endif #if true != 1 #error "'true' is not equal to 1" #endif char b[true == 1 ? 1 : -1]; char c[true]; #if false #error "'false' is not false" #endif #if false != 0 #error "'false' is not equal to 0" #endif char d[false == 0 ? 1 : -1]; enum { e = false, f = true, g = false * true, h = true * 256 }; char i[(bool) 0.5 == true ? 1 : -1]; char j[(bool) 0.0 == false ? 1 : -1]; char k[sizeof (bool) > 0 ? 1 : -1]; struct sb { bool s: 1; bool t; } s; char l[sizeof s.t > 0 ? 1 : -1]; /* The following fails for HP aC++/ANSI C B3910B A.05.55 [Dec 04 2003]. */ bool m[h]; char n[sizeof m == h * sizeof m[0] ? 1 : -1]; char o[-1 - (bool) 0 < 0 ? 1 : -1]; /* Catch a bug in an HP-UX C compiler. See https://gcc.gnu.org/ml/gcc-patches/2003-12/msg02303.html https://lists.gnu.org/r/bug-coreutils/2005-11/msg00161.html */ bool p = true; bool *pp = &p; int main (void) { bool ps = &s; *pp |= p; *pp |= ! p; /* Refer to every declared value, so they cannot be discarded as unused. */ return (!b + !c + !d + !e + !f + !g + !h + !i + !j + !k + !l + !m + !n + !o + !p + !pp + !ps); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_header_stdbool_h=yes else case e in #( e) ac_cv_header_stdbool_h=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdbool_h" >&5 printf "%s\n" "$ac_cv_header_stdbool_h" >&6; } ac_fn_c_find_intX_t "$LINENO" "32" "ac_cv_c_int32_t" case $ac_cv_c_int32_t in #( no|yes) ;; #( *) printf "%s\n" "#define int32_t $ac_cv_c_int32_t" >>confdefs.h ;; esac ac_fn_c_check_type "$LINENO" "off_t" "ac_cv_type_off_t" "$ac_includes_default" if test "x$ac_cv_type_off_t" = xyes then : else case e in #( e) printf "%s\n" "#define off_t long int" >>confdefs.h ;; esac fi ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" if test "x$ac_cv_type_size_t" = xyes then : else case e in #( e) printf "%s\n" "#define size_t unsigned int" >>confdefs.h ;; esac fi ac_fn_c_check_type "$LINENO" "ssize_t" "ac_cv_type_ssize_t" "$ac_includes_default" if test "x$ac_cv_type_ssize_t" = xyes then : else case e in #( e) printf "%s\n" "#define ssize_t int" >>confdefs.h ;; esac fi ac_fn_c_find_uintX_t "$LINENO" "16" "ac_cv_c_uint16_t" case $ac_cv_c_uint16_t in #( no|yes) ;; #( *) printf "%s\n" "#define uint16_t $ac_cv_c_uint16_t" >>confdefs.h ;; esac ac_fn_c_find_uintX_t "$LINENO" "32" "ac_cv_c_uint32_t" case $ac_cv_c_uint32_t in #( no|yes) ;; #( *) printf "%s\n" "#define _UINT32_T 1" >>confdefs.h printf "%s\n" "#define uint32_t $ac_cv_c_uint32_t" >>confdefs.h ;; esac ac_fn_c_find_uintX_t "$LINENO" "64" "ac_cv_c_uint64_t" case $ac_cv_c_uint64_t in #( no|yes) ;; #( *) printf "%s\n" "#define _UINT64_T 1" >>confdefs.h printf "%s\n" "#define uint64_t $ac_cv_c_uint64_t" >>confdefs.h ;; esac # Checks for header files. #AC_CHECK_HEADERS([fcntl.h stdlib.h string.h unistd.h sstream climits cstring]) # Check integer size # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like 'int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of int" >&5 printf %s "checking size of int... " >&6; } if test ${ac_cv_sizeof_int+y} then : printf %s "(cached) " >&6 else case e in #( e) if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (int))" "ac_cv_sizeof_int" "$ac_includes_default" then : else case e in #( e) if test "$ac_cv_type_int" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (int) See 'config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_int=0 fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_int" >&5 printf "%s\n" "$ac_cv_sizeof_int" >&6; } printf "%s\n" "#define SIZEOF_INT $ac_cv_sizeof_int" >>confdefs.h # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like 'int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of long" >&5 printf %s "checking size of long... " >&6; } if test ${ac_cv_sizeof_long+y} then : printf %s "(cached) " >&6 else case e in #( e) if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long))" "ac_cv_sizeof_long" "$ac_includes_default" then : else case e in #( e) if test "$ac_cv_type_long" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (long) See 'config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_long=0 fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long" >&5 printf "%s\n" "$ac_cv_sizeof_long" >&6; } printf "%s\n" "#define SIZEOF_LONG $ac_cv_sizeof_long" >>confdefs.h # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like 'int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of off_t" >&5 printf %s "checking size of off_t... " >&6; } if test ${ac_cv_sizeof_off_t+y} then : printf %s "(cached) " >&6 else case e in #( e) if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (off_t))" "ac_cv_sizeof_off_t" "$ac_includes_default" then : else case e in #( e) if test "$ac_cv_type_off_t" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (off_t) See 'config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_off_t=0 fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_off_t" >&5 printf "%s\n" "$ac_cv_sizeof_off_t" >&6; } printf "%s\n" "#define SIZEOF_OFF_T $ac_cv_sizeof_off_t" >>confdefs.h #AC_CHECK_SIZEOF([size_t] if test "$ac_cv_sizeof_int" -lt 4 then : as_fn_error $? "The size of an integer of type \"int\" must be at least 4 bytes." "$LINENO" 5 fi # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like 'int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of time_t" >&5 printf %s "checking size of time_t... " >&6; } if test ${ac_cv_sizeof_time_t+y} then : printf %s "(cached) " >&6 else case e in #( e) if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (time_t))" "ac_cv_sizeof_time_t" "#include " then : else case e in #( e) if test "$ac_cv_type_time_t" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (time_t) See 'config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_time_t=0 fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_time_t" >&5 printf "%s\n" "$ac_cv_sizeof_time_t" >&6; } printf "%s\n" "#define SIZEOF_TIME_T $ac_cv_sizeof_time_t" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of time_t type" >&5 printf %s "checking size of time_t type... " >&6; } if test "$ac_cv_sizeof_time_t" = "$ac_cv_sizeof_long" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: long" >&5 printf "%s\n" "long" >&6; } printf "%s\n" "#define FFMPEGFS_FORMAT_TIME_T \"ld\"" >>confdefs.h else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: int" >&5 printf "%s\n" "int" >&6; } printf "%s\n" "#define FFMPEGFS_FORMAT_TIME_T \"d\"" >>confdefs.h ;; esac fi # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like 'int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of pthread_t" >&5 printf %s "checking size of pthread_t... " >&6; } if test ${ac_cv_sizeof_pthread_t+y} then : printf %s "(cached) " >&6 else case e in #( e) if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (pthread_t))" "ac_cv_sizeof_pthread_t" "#include " then : else case e in #( e) if test "$ac_cv_type_pthread_t" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (pthread_t) See 'config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_pthread_t=0 fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_pthread_t" >&5 printf "%s\n" "$ac_cv_sizeof_pthread_t" >&6; } printf "%s\n" "#define SIZEOF_PTHREAD_T $ac_cv_sizeof_pthread_t" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of pthread_t type" >&5 printf %s "checking size of pthread_t type... " >&6; } if test "$ac_cv_sizeof_pthread_t" = "$ac_cv_sizeof_long" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: long" >&5 printf "%s\n" "long" >&6; } printf "%s\n" "#define FFMPEGFS_FORMAT_PTHREAD_T \"lx\"" >>confdefs.h else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: int" >&5 printf "%s\n" "int" >&6; } printf "%s\n" "#define FFMPEGFS_FORMAT_PTHREAD_T \"x\"" >>confdefs.h ;; esac fi # Large file support # Check whether --enable-largefile was given. if test ${enable_largefile+y} then : enableval=$enable_largefile; fi if test "$enable_largefile,$enable_year2038" != no,no then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CPPFLAGS option for large files" >&5 printf %s "checking for $CPPFLAGS option for large files... " >&6; } if test ${ac_cv_sys_largefile_opts+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_CPPFLAGS=$CPPFLAGS ac_opt_found=no for ac_opt in "none needed" "-D_FILE_OFFSET_BITS=64" "-D_LARGE_FILES=1"; do if test x"$ac_opt" != x"none needed" then : CPPFLAGS="$ac_save_CPPFLAGS $ac_opt" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifndef FTYPE # define FTYPE off_t #endif /* Check that FTYPE can represent 2**63 - 1 correctly. We can't simply define LARGE_FTYPE to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_FTYPE (((FTYPE) 1 << 31 << 31) - 1 + ((FTYPE) 1 << 31 << 31)) int FTYPE_is_large[(LARGE_FTYPE % 2147483629 == 721 && LARGE_FTYPE % 2147483647 == 1) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : if test x"$ac_opt" = x"none needed" then : # GNU/Linux s390x and alpha need _FILE_OFFSET_BITS=64 for wide ino_t. CPPFLAGS="$CPPFLAGS -DFTYPE=ino_t" if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64" if ac_fn_c_try_compile "$LINENO" then : ac_opt='-D_FILE_OFFSET_BITS=64' fi rm -f core conftest.err conftest.$ac_objext conftest.beam ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam fi ac_cv_sys_largefile_opts=$ac_opt ac_opt_found=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext test $ac_opt_found = no || break done CPPFLAGS=$ac_save_CPPFLAGS test $ac_opt_found = yes || ac_cv_sys_largefile_opts="support not detected" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_largefile_opts" >&5 printf "%s\n" "$ac_cv_sys_largefile_opts" >&6; } ac_have_largefile=yes case $ac_cv_sys_largefile_opts in #( "none needed") : ;; #( "supported through gnulib") : ;; #( "support not detected") : ac_have_largefile=no ;; #( "-D_FILE_OFFSET_BITS=64") : printf "%s\n" "#define _FILE_OFFSET_BITS 64" >>confdefs.h ;; #( "-D_LARGE_FILES=1") : printf "%s\n" "#define _LARGE_FILES 1" >>confdefs.h ;; #( *) : as_fn_error $? "internal error: bad value for \$ac_cv_sys_largefile_opts" "$LINENO" 5 ;; esac if test "$enable_year2038" != no then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CPPFLAGS option for timestamps after 2038" >&5 printf %s "checking for $CPPFLAGS option for timestamps after 2038... " >&6; } if test ${ac_cv_sys_year2038_opts+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_CPPFLAGS="$CPPFLAGS" ac_opt_found=no for ac_opt in "none needed" "-D_TIME_BITS=64" "-D__MINGW_USE_VC2005_COMPAT" "-U_USE_32_BIT_TIME_T -D__MINGW_USE_VC2005_COMPAT"; do if test x"$ac_opt" != x"none needed" then : CPPFLAGS="$ac_save_CPPFLAGS $ac_opt" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include /* Check that time_t can represent 2**32 - 1 correctly. */ #define LARGE_TIME_T \\ ((time_t) (((time_t) 1 << 30) - 1 + 3 * ((time_t) 1 << 30))) int verify_time_t_range[(LARGE_TIME_T / 65537 == 65535 && LARGE_TIME_T % 65537 == 0) ? 1 : -1]; int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_sys_year2038_opts="$ac_opt" ac_opt_found=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext test $ac_opt_found = no || break done CPPFLAGS="$ac_save_CPPFLAGS" test $ac_opt_found = yes || ac_cv_sys_year2038_opts="support not detected" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_year2038_opts" >&5 printf "%s\n" "$ac_cv_sys_year2038_opts" >&6; } ac_have_year2038=yes case $ac_cv_sys_year2038_opts in #( "none needed") : ;; #( "support not detected") : ac_have_year2038=no ;; #( "-D_TIME_BITS=64") : printf "%s\n" "#define _TIME_BITS 64" >>confdefs.h ;; #( "-D__MINGW_USE_VC2005_COMPAT") : printf "%s\n" "#define __MINGW_USE_VC2005_COMPAT 1" >>confdefs.h ;; #( "-U_USE_32_BIT_TIME_T"*) : { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "the 'time_t' type is currently forced to be 32-bit. It will stop working after mid-January 2038. Remove _USE_32BIT_TIME_T from the compiler flags. See 'config.log' for more details" "$LINENO" 5; } ;; #( *) : as_fn_error $? "internal error: bad value for \$ac_cv_sys_year2038_opts" "$LINENO" 5 ;; esac fi fi # Define POSIX standard conformance printf "%s\n" "#define _POSIX_C_SOURCE 200809L" >>confdefs.h # Bug#1037653: Fix build with GCC-13 printf "%s\n" "#define __STDC_CONSTANT_MACROS /**/" >>confdefs.h # This is because there are PKG_CHECK_MODULES calls inside conditionals. if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 printf "%s\n" "$PKG_CONFIG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_ac_pt_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 printf "%s\n" "$ac_pt_PKG_CONFIG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 printf %s "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } PKG_CONFIG="" fi fi # Checks for packages which use pkg-config. pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for chardet >= 1.0.4" >&5 printf %s "checking for chardet >= 1.0.4... " >&6; } if test -n "$chardet_CFLAGS"; then pkg_cv_chardet_CFLAGS="$chardet_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"chardet >= 1.0.4\""; } >&5 ($PKG_CONFIG --exists --print-errors "chardet >= 1.0.4") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_chardet_CFLAGS=`$PKG_CONFIG --cflags "chardet >= 1.0.4" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$chardet_LIBS"; then pkg_cv_chardet_LIBS="$chardet_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"chardet >= 1.0.4\""; } >&5 ($PKG_CONFIG --exists --print-errors "chardet >= 1.0.4") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_chardet_LIBS=`$PKG_CONFIG --libs "chardet >= 1.0.4" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then chardet_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "chardet >= 1.0.4" 2>&1` else chardet_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "chardet >= 1.0.4" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$chardet_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (chardet >= 1.0.4) were not met: $chardet_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables chardet_CFLAGS and chardet_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables chardet_CFLAGS and chardet_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else chardet_CFLAGS=$pkg_cv_chardet_CFLAGS chardet_LIBS=$pkg_cv_chardet_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fuse3 >= 3.4.1" >&5 printf %s "checking for fuse3 >= 3.4.1... " >&6; } if test -n "$fuse3_CFLAGS"; then pkg_cv_fuse3_CFLAGS="$fuse3_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"fuse3 >= 3.4.1\""; } >&5 ($PKG_CONFIG --exists --print-errors "fuse3 >= 3.4.1") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_fuse3_CFLAGS=`$PKG_CONFIG --cflags "fuse3 >= 3.4.1" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$fuse3_LIBS"; then pkg_cv_fuse3_LIBS="$fuse3_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"fuse3 >= 3.4.1\""; } >&5 ($PKG_CONFIG --exists --print-errors "fuse3 >= 3.4.1") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_fuse3_LIBS=`$PKG_CONFIG --libs "fuse3 >= 3.4.1" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then fuse3_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "fuse3 >= 3.4.1" 2>&1` else fuse3_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "fuse3 >= 3.4.1" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$fuse3_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (fuse3 >= 3.4.1) were not met: $fuse3_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables fuse3_CFLAGS and fuse3_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables fuse3_CFLAGS and fuse3_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else fuse3_CFLAGS=$pkg_cv_fuse3_CFLAGS fuse3_LIBS=$pkg_cv_fuse3_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libcue >= 2.1.0" >&5 printf %s "checking for libcue >= 2.1.0... " >&6; } if test -n "$libcue_CFLAGS"; then pkg_cv_libcue_CFLAGS="$libcue_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcue >= 2.1.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libcue >= 2.1.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libcue_CFLAGS=`$PKG_CONFIG --cflags "libcue >= 2.1.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libcue_LIBS"; then pkg_cv_libcue_LIBS="$libcue_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcue >= 2.1.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "libcue >= 2.1.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libcue_LIBS=`$PKG_CONFIG --libs "libcue >= 2.1.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libcue_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libcue >= 2.1.0" 2>&1` else libcue_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libcue >= 2.1.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libcue_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libcue >= 2.1.0) were not met: $libcue_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libcue_CFLAGS and libcue_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libcue_CFLAGS and libcue_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libcue_CFLAGS=$pkg_cv_libcue_CFLAGS libcue_LIBS=$pkg_cv_libcue_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi # Checks for sqlite3 # API misses a lot of functionality if not fairly recent pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for sqlite3 >= 3.7.13" >&5 printf %s "checking for sqlite3 >= 3.7.13... " >&6; } if test -n "$sqlite3_CFLAGS"; then pkg_cv_sqlite3_CFLAGS="$sqlite3_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3 >= 3.7.13\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3 >= 3.7.13") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_sqlite3_CFLAGS=`$PKG_CONFIG --cflags "sqlite3 >= 3.7.13" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$sqlite3_LIBS"; then pkg_cv_sqlite3_LIBS="$sqlite3_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sqlite3 >= 3.7.13\""; } >&5 ($PKG_CONFIG --exists --print-errors "sqlite3 >= 3.7.13") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_sqlite3_LIBS=`$PKG_CONFIG --libs "sqlite3 >= 3.7.13" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then sqlite3_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "sqlite3 >= 3.7.13" 2>&1` else sqlite3_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "sqlite3 >= 3.7.13" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$sqlite3_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (sqlite3 >= 3.7.13) were not met: $sqlite3_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables sqlite3_CFLAGS and sqlite3_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables sqlite3_CFLAGS and sqlite3_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else sqlite3_CFLAGS=$pkg_cv_sqlite3_CFLAGS sqlite3_LIBS=$pkg_cv_sqlite3_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing sqlite3_errstr" >&5 printf %s "checking for library containing sqlite3_errstr... " >&6; } if test ${ac_cv_search_sqlite3_errstr+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char sqlite3_errstr (void); int main (void) { return sqlite3_errstr (); ; return 0; } _ACEOF for ac_lib in '' sqlite3 do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_sqlite3_errstr=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_sqlite3_errstr+y} then : break fi done if test ${ac_cv_search_sqlite3_errstr+y} then : else case e in #( e) ac_cv_search_sqlite3_errstr=no ;; esac fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_sqlite3_errstr" >&5 printf "%s\n" "$ac_cv_search_sqlite3_errstr" >&6; } ac_res=$ac_cv_search_sqlite3_errstr if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" printf "%s\n" "#define HAVE_SQLITE_ERRSTR 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing sqlite3_db_cacheflush" >&5 printf %s "checking for library containing sqlite3_db_cacheflush... " >&6; } if test ${ac_cv_search_sqlite3_db_cacheflush+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char sqlite3_db_cacheflush (void); int main (void) { return sqlite3_db_cacheflush (); ; return 0; } _ACEOF for ac_lib in '' sqlite3 do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_sqlite3_db_cacheflush=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_sqlite3_db_cacheflush+y} then : break fi done if test ${ac_cv_search_sqlite3_db_cacheflush+y} then : else case e in #( e) ac_cv_search_sqlite3_db_cacheflush=no ;; esac fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_sqlite3_db_cacheflush" >&5 printf "%s\n" "$ac_cv_search_sqlite3_db_cacheflush" >&6; } ac_res=$ac_cv_search_sqlite3_db_cacheflush if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" printf "%s\n" "#define HAVE_SQLITE_CACHEFLUSH 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing sqlite3_expanded_sql" >&5 printf %s "checking for library containing sqlite3_expanded_sql... " >&6; } if test ${ac_cv_search_sqlite3_expanded_sql+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char sqlite3_expanded_sql (void); int main (void) { return sqlite3_expanded_sql (); ; return 0; } _ACEOF for ac_lib in '' sqlite3 do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_sqlite3_expanded_sql=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_sqlite3_expanded_sql+y} then : break fi done if test ${ac_cv_search_sqlite3_expanded_sql+y} then : else case e in #( e) ac_cv_search_sqlite3_expanded_sql=no ;; esac fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_sqlite3_expanded_sql" >&5 printf "%s\n" "$ac_cv_search_sqlite3_expanded_sql" >&6; } ac_res=$ac_cv_search_sqlite3_expanded_sql if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" printf "%s\n" "#define HAVE_SQLITE_EXPANDED_SQL 1" >>confdefs.h fi # # Check for programs used when building manpages and help. # # Check for a2x (convert asciidoc to another format) # Extract the first word of "a2x", so it can be a program name with args. set dummy a2x; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_A2X+y} then : printf %s "(cached) " >&6 else case e in #( e) case $A2X in [\\/]* | ?:[\\/]*) ac_cv_path_A2X="$A2X" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_A2X="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi A2X=$ac_cv_path_A2X if test -n "$A2X"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $A2X" >&5 printf "%s\n" "$A2X" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi # Extract the first word of "a2x", so it can be a program name with args. set dummy a2x; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_HAVE_A2X+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$HAVE_A2X"; then ac_cv_prog_HAVE_A2X="$HAVE_A2X" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_HAVE_A2X=""yes"" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_HAVE_A2X" && ac_cv_prog_HAVE_A2X=""no"" fi ;; esac fi HAVE_A2X=$ac_cv_prog_HAVE_A2X if test -n "$HAVE_A2X"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_A2X" >&5 printf "%s\n" "$HAVE_A2X" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "$HAVE_A2X" = "yes" then : else case e in #( e) as_fn_error $? "a2x could not be found. Install asciidoc to fix. Or asciidoc-base instead to save disc space." "$LINENO" 5 ;; esac fi # Check for w3m (html -> text) # Extract the first word of "w3m", so it can be a program name with args. set dummy w3m; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_W3M+y} then : printf %s "(cached) " >&6 else case e in #( e) case $W3M in [\\/]* | ?:[\\/]*) ac_cv_path_W3M="$W3M" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_W3M="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi W3M=$ac_cv_path_W3M if test -n "$W3M"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $W3M" >&5 printf "%s\n" "$W3M" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi # Extract the first word of "w3m", so it can be a program name with args. set dummy w3m; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_HAVE_W3M+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$HAVE_W3M"; then ac_cv_prog_HAVE_W3M="$HAVE_W3M" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_HAVE_W3M=""yes"" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_HAVE_W3M" && ac_cv_prog_HAVE_W3M=""no"" fi ;; esac fi HAVE_W3M=$ac_cv_prog_HAVE_W3M if test -n "$HAVE_W3M"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_W3M" >&5 printf "%s\n" "$HAVE_W3M" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "$HAVE_W3M" = "yes" then : else case e in #( e) as_fn_error $? "w3m could not be found. To fix, kindly install." "$LINENO" 5 ;; esac fi # FFmpeg 4.1.8 "al-Khwarizmi" # # This version is packaged with Debian Buster. This is the lowest version # we support. # # 4.1.8 was released on 2021-10-17. It is the latest stable FFmpeg release # from the 4.1 release branch, which was cut from master on 2018-11-02. # # It includes the following library versions: # # libavutil 56. 22.100 # libavcodec 58. 35.100 # libavformat 58. 20.100 # libavdevice 58. 5.100 (not used) # libavfilter 7. 40.101 # libswscale 5. 3.100 # libswresample 3. 3.100 # libpostproc 55. 3.100 (not used) # # May work with older versions but this is not guaranteed. pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libavutil >= 56.22.100" >&5 printf %s "checking for libavutil >= 56.22.100... " >&6; } if test -n "$libavutil_CFLAGS"; then pkg_cv_libavutil_CFLAGS="$libavutil_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavutil >= 56.22.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavutil >= 56.22.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavutil_CFLAGS=`$PKG_CONFIG --cflags "libavutil >= 56.22.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libavutil_LIBS"; then pkg_cv_libavutil_LIBS="$libavutil_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavutil >= 56.22.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavutil >= 56.22.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavutil_LIBS=`$PKG_CONFIG --libs "libavutil >= 56.22.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libavutil_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libavutil >= 56.22.100" 2>&1` else libavutil_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libavutil >= 56.22.100" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libavutil_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libavutil >= 56.22.100) were not met: $libavutil_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libavutil_CFLAGS and libavutil_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libavutil_CFLAGS and libavutil_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libavutil_CFLAGS=$pkg_cv_libavutil_CFLAGS libavutil_LIBS=$pkg_cv_libavutil_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libavcodec >= 58.35.100" >&5 printf %s "checking for libavcodec >= 58.35.100... " >&6; } if test -n "$libavcodec_CFLAGS"; then pkg_cv_libavcodec_CFLAGS="$libavcodec_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavcodec >= 58.35.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavcodec >= 58.35.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavcodec_CFLAGS=`$PKG_CONFIG --cflags "libavcodec >= 58.35.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libavcodec_LIBS"; then pkg_cv_libavcodec_LIBS="$libavcodec_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavcodec >= 58.35.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavcodec >= 58.35.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavcodec_LIBS=`$PKG_CONFIG --libs "libavcodec >= 58.35.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libavcodec_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libavcodec >= 58.35.100" 2>&1` else libavcodec_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libavcodec >= 58.35.100" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libavcodec_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libavcodec >= 58.35.100) were not met: $libavcodec_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libavcodec_CFLAGS and libavcodec_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libavcodec_CFLAGS and libavcodec_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libavcodec_CFLAGS=$pkg_cv_libavcodec_CFLAGS libavcodec_LIBS=$pkg_cv_libavcodec_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libavformat >= 58.20.100" >&5 printf %s "checking for libavformat >= 58.20.100... " >&6; } if test -n "$libavformat_CFLAGS"; then pkg_cv_libavformat_CFLAGS="$libavformat_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavformat >= 58.20.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavformat >= 58.20.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavformat_CFLAGS=`$PKG_CONFIG --cflags "libavformat >= 58.20.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libavformat_LIBS"; then pkg_cv_libavformat_LIBS="$libavformat_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavformat >= 58.20.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavformat >= 58.20.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavformat_LIBS=`$PKG_CONFIG --libs "libavformat >= 58.20.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libavformat_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libavformat >= 58.20.100" 2>&1` else libavformat_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libavformat >= 58.20.100" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libavformat_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libavformat >= 58.20.100) were not met: $libavformat_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libavformat_CFLAGS and libavformat_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libavformat_CFLAGS and libavformat_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libavformat_CFLAGS=$pkg_cv_libavformat_CFLAGS libavformat_LIBS=$pkg_cv_libavformat_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libavfilter >= 7.40.101" >&5 printf %s "checking for libavfilter >= 7.40.101... " >&6; } if test -n "$libavfilter_CFLAGS"; then pkg_cv_libavfilter_CFLAGS="$libavfilter_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavfilter >= 7.40.101\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavfilter >= 7.40.101") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavfilter_CFLAGS=`$PKG_CONFIG --cflags "libavfilter >= 7.40.101" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libavfilter_LIBS"; then pkg_cv_libavfilter_LIBS="$libavfilter_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libavfilter >= 7.40.101\""; } >&5 ($PKG_CONFIG --exists --print-errors "libavfilter >= 7.40.101") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libavfilter_LIBS=`$PKG_CONFIG --libs "libavfilter >= 7.40.101" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libavfilter_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libavfilter >= 7.40.101" 2>&1` else libavfilter_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libavfilter >= 7.40.101" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libavfilter_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libavfilter >= 7.40.101) were not met: $libavfilter_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libavfilter_CFLAGS and libavfilter_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libavfilter_CFLAGS and libavfilter_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libavfilter_CFLAGS=$pkg_cv_libavfilter_CFLAGS libavfilter_LIBS=$pkg_cv_libavfilter_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libswscale >= 5.3.100" >&5 printf %s "checking for libswscale >= 5.3.100... " >&6; } if test -n "$libswscale_CFLAGS"; then pkg_cv_libswscale_CFLAGS="$libswscale_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libswscale >= 5.3.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libswscale >= 5.3.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libswscale_CFLAGS=`$PKG_CONFIG --cflags "libswscale >= 5.3.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libswscale_LIBS"; then pkg_cv_libswscale_LIBS="$libswscale_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libswscale >= 5.3.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libswscale >= 5.3.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libswscale_LIBS=`$PKG_CONFIG --libs "libswscale >= 5.3.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libswscale_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libswscale >= 5.3.100" 2>&1` else libswscale_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libswscale >= 5.3.100" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libswscale_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libswscale >= 5.3.100) were not met: $libswscale_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libswscale_CFLAGS and libswscale_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libswscale_CFLAGS and libswscale_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libswscale_CFLAGS=$pkg_cv_libswscale_CFLAGS libswscale_LIBS=$pkg_cv_libswscale_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libswresample >= 3.3.100" >&5 printf %s "checking for libswresample >= 3.3.100... " >&6; } if test -n "$libswresample_CFLAGS"; then pkg_cv_libswresample_CFLAGS="$libswresample_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libswresample >= 3.3.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libswresample >= 3.3.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libswresample_CFLAGS=`$PKG_CONFIG --cflags "libswresample >= 3.3.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libswresample_LIBS"; then pkg_cv_libswresample_LIBS="$libswresample_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libswresample >= 3.3.100\""; } >&5 ($PKG_CONFIG --exists --print-errors "libswresample >= 3.3.100") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libswresample_LIBS=`$PKG_CONFIG --libs "libswresample >= 3.3.100" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libswresample_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libswresample >= 3.3.100" 2>&1` else libswresample_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libswresample >= 3.3.100" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libswresample_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libswresample >= 3.3.100) were not met: $libswresample_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libswresample_CFLAGS and libswresample_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libswresample_CFLAGS and libswresample_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libswresample_CFLAGS=$pkg_cv_libswresample_CFLAGS libswresample_LIBS=$pkg_cv_libswresample_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } printf "%s\n" "#define HAVE_FFMPEG 1" >>confdefs.h fi fi fi fi fi fi # Optional libraries # Check whether --with-libdvd was given. if test ${with_libdvd+y} then : withval=$with_libdvd; else case e in #( e) with_libdvd=check ;; esac fi case "$with_libdvd" in #( yes) : pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dvdread >= 5.0.0" >&5 printf %s "checking for dvdread >= 5.0.0... " >&6; } if test -n "$libdvdread_CFLAGS"; then pkg_cv_libdvdread_CFLAGS="$libdvdread_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dvdread >= 5.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "dvdread >= 5.0.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libdvdread_CFLAGS=`$PKG_CONFIG --cflags "dvdread >= 5.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libdvdread_LIBS"; then pkg_cv_libdvdread_LIBS="$libdvdread_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dvdread >= 5.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "dvdread >= 5.0.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libdvdread_LIBS=`$PKG_CONFIG --libs "dvdread >= 5.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libdvdread_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dvdread >= 5.0.0" 2>&1` else libdvdread_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dvdread >= 5.0.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libdvdread_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (dvdread >= 5.0.0) were not met: $libdvdread_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libdvdread_CFLAGS and libdvdread_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libdvdread_CFLAGS and libdvdread_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libdvdread_CFLAGS=$pkg_cv_libdvdread_CFLAGS libdvdread_LIBS=$pkg_cv_libdvdread_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } HAVE_LIBDVDREAD=1 fi ;; #( no) : ;; #( *) : pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dvdread >= 5.0.0" >&5 printf %s "checking for dvdread >= 5.0.0... " >&6; } if test -n "$libdvdread_CFLAGS"; then pkg_cv_libdvdread_CFLAGS="$libdvdread_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dvdread >= 5.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "dvdread >= 5.0.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libdvdread_CFLAGS=`$PKG_CONFIG --cflags "dvdread >= 5.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libdvdread_LIBS"; then pkg_cv_libdvdread_LIBS="$libdvdread_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dvdread >= 5.0.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "dvdread >= 5.0.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libdvdread_LIBS=`$PKG_CONFIG --libs "dvdread >= 5.0.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libdvdread_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dvdread >= 5.0.0" 2>&1` else libdvdread_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dvdread >= 5.0.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libdvdread_PKG_ERRORS" >&5 HAVE_LIBDVDREAD=0 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } HAVE_LIBDVDREAD=0 else libdvdread_CFLAGS=$pkg_cv_libdvdread_CFLAGS libdvdread_LIBS=$pkg_cv_libdvdread_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } HAVE_LIBDVDREAD=1 fi ;; esac if test "$with_libdvd" != "no" -a "0$HAVE_LIBDVDREAD" -eq 1; then USE_LIBDVD_TRUE= USE_LIBDVD_FALSE='#' else USE_LIBDVD_TRUE='#' USE_LIBDVD_FALSE= fi if test "$with_libdvd" != "no" -a "0$HAVE_LIBDVDREAD" -eq 0; then HINT_LIBDVD_TRUE= HINT_LIBDVD_FALSE='#' else HINT_LIBDVD_TRUE='#' HINT_LIBDVD_FALSE= fi # Check whether --with-libbluray was given. if test ${with_libbluray+y} then : withval=$with_libbluray; else case e in #( e) with_libbluray=check ;; esac fi case "$with_libbluray" in #( yes) : pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libbluray >= 0.6.2" >&5 printf %s "checking for libbluray >= 0.6.2... " >&6; } if test -n "$libbluray_CFLAGS"; then pkg_cv_libbluray_CFLAGS="$libbluray_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libbluray >= 0.6.2\""; } >&5 ($PKG_CONFIG --exists --print-errors "libbluray >= 0.6.2") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libbluray_CFLAGS=`$PKG_CONFIG --cflags "libbluray >= 0.6.2" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libbluray_LIBS"; then pkg_cv_libbluray_LIBS="$libbluray_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libbluray >= 0.6.2\""; } >&5 ($PKG_CONFIG --exists --print-errors "libbluray >= 0.6.2") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libbluray_LIBS=`$PKG_CONFIG --libs "libbluray >= 0.6.2" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libbluray_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libbluray >= 0.6.2" 2>&1` else libbluray_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libbluray >= 0.6.2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libbluray_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libbluray >= 0.6.2) were not met: $libbluray_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libbluray_CFLAGS and libbluray_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libbluray_CFLAGS and libbluray_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See 'config.log' for more details" "$LINENO" 5; } else libbluray_CFLAGS=$pkg_cv_libbluray_CFLAGS libbluray_LIBS=$pkg_cv_libbluray_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } HAVE_LIBBLURAY=1 fi ;; #( no) : ;; #( *) : pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libbluray >= 0.6.2" >&5 printf %s "checking for libbluray >= 0.6.2... " >&6; } if test -n "$libbluray_CFLAGS"; then pkg_cv_libbluray_CFLAGS="$libbluray_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libbluray >= 0.6.2\""; } >&5 ($PKG_CONFIG --exists --print-errors "libbluray >= 0.6.2") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libbluray_CFLAGS=`$PKG_CONFIG --cflags "libbluray >= 0.6.2" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$libbluray_LIBS"; then pkg_cv_libbluray_LIBS="$libbluray_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libbluray >= 0.6.2\""; } >&5 ($PKG_CONFIG --exists --print-errors "libbluray >= 0.6.2") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_libbluray_LIBS=`$PKG_CONFIG --libs "libbluray >= 0.6.2" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libbluray_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libbluray >= 0.6.2" 2>&1` else libbluray_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libbluray >= 0.6.2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$libbluray_PKG_ERRORS" >&5 HAVE_LIBBLURAY=0 elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } HAVE_LIBBLURAY=0 else libbluray_CFLAGS=$pkg_cv_libbluray_CFLAGS libbluray_LIBS=$pkg_cv_libbluray_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } HAVE_LIBBLURAY=1 fi ;; esac if test "$with_libbluray" != "no" -a "0$HAVE_LIBBLURAY" -eq 1; then USE_LIBBLURAY_TRUE= USE_LIBBLURAY_FALSE='#' else USE_LIBBLURAY_TRUE='#' USE_LIBBLURAY_FALSE= fi if test "$with_libbluray" != "no" -a "0$HAVE_LIBBLURAY" -eq 0; then HINT_LIBBLURAY_TRUE= HINT_LIBBLURAY_FALSE='#' else HINT_LIBBLURAY_TRUE='#' HINT_LIBBLURAY_FALSE= fi # Optional profiling support # Check whether --enable-perftools was given. if test ${enable_perftools+y} then : enableval=$enable_perftools; case "${enableval}" in yes) perftools=yes ;; no) perftools=no ;; *) as_fn_error $? "bad value ${enableval} for --enable-perftools" "$LINENO" 5 ;; esac else case e in #( e) perftools=no ;; esac fi PERFTOOLS_CFLAGS="" PERFTOOLS_CXXFLAGS="" PERFTOOLS_LIBS="" if test x"$perftools" = xyes then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for HeapProfilerStart in -ltcmalloc_and_profiler" >&5 printf %s "checking for HeapProfilerStart in -ltcmalloc_and_profiler... " >&6; } if test ${ac_cv_lib_tcmalloc_and_profiler_HeapProfilerStart+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ltcmalloc_and_profiler $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char HeapProfilerStart (void); int main (void) { return HeapProfilerStart (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_tcmalloc_and_profiler_HeapProfilerStart=yes else case e in #( e) ac_cv_lib_tcmalloc_and_profiler_HeapProfilerStart=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_tcmalloc_and_profiler_HeapProfilerStart" >&5 printf "%s\n" "$ac_cv_lib_tcmalloc_and_profiler_HeapProfilerStart" >&6; } if test "x$ac_cv_lib_tcmalloc_and_profiler_HeapProfilerStart" = xyes then : printf "%s\n" "#define HAVE_PERFTOOLS 1" >>confdefs.h PERFTOOLS_CFLAGS="-fno-omit-frame-pointer" PERFTOOLS_CXXFLAGS="-fno-omit-frame-pointer" PERFTOOLS_LIBS="-Wl,--push-state,--no-as-needed -ltcmalloc_and_profiler -Wl,--pop-state" else case e in #( e) as_fn_error $? "--enable-perftools requested, but libtcmalloc_and_profiler could not be linked. Install the gperftools development package." "$LINENO" 5 ;; esac fi fi if test x"$perftools" = xyes; then ENABLE_PERFTOOLS_TRUE= ENABLE_PERFTOOLS_FALSE='#' else ENABLE_PERFTOOLS_TRUE='#' ENABLE_PERFTOOLS_FALSE= fi # Check for libvcd # Check whether --with-libvcd was given. if test ${with_libvcd+y} then : withval=$with_libvcd; else case e in #( e) with_libvcd=check ;; esac fi if test "$with_libvcd" != "no"; then USE_LIBVCD_TRUE= USE_LIBVCD_FALSE='#' else USE_LIBVCD_TRUE='#' USE_LIBVCD_FALSE= fi if test -z "$USE_LIBVCD_TRUE"; then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Internal S/VCD support enabled... yes" >&5 printf "%s\n" "Internal S/VCD support enabled... yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Internal S/VCD support enabled... no" >&5 printf "%s\n" "Internal S/VCD support enabled... no" >&6; } fi # Check for doxygen. If not installed, go on, but make doxy won't work. for ac_prog in doxygen do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DOXYGEN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DOXYGEN"; then ac_cv_prog_DOXYGEN="$DOXYGEN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DOXYGEN="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DOXYGEN=$ac_cv_prog_DOXYGEN if test -n "$DOXYGEN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DOXYGEN" >&5 printf "%s\n" "$DOXYGEN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$DOXYGEN" && break done if test -z "$DOXYGEN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Doxygen could not be found; we must continue without it. It is advised to be installed if you intend to use \"make doxy\"." >&5 printf "%s\n" "$as_me: WARNING: Doxygen could not be found; we must continue without it. It is advised to be installed if you intend to use \"make doxy\"." >&2;} fi # Check for curl. If not installed, go on, but make doxy won't work. for ac_prog in curl do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CURL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CURL"; then ac_cv_prog_CURL="$CURL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CURL="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CURL=$ac_cv_prog_CURL if test -n "$CURL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CURL" >&5 printf "%s\n" "$CURL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CURL" && break done if test -z "$CURL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: curl could not be found; we must continue without it. It is advised to be installed if you intend to use \"make doxy\"." >&5 printf "%s\n" "$as_me: WARNING: curl could not be found; we must continue without it. It is advised to be installed if you intend to use \"make doxy\"." >&2;} fi # Check for dot (graphviz). If not installed, go on, but make doxy won't work. for ac_prog in dot do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_GRAPHVIZ+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$GRAPHVIZ"; then ac_cv_prog_GRAPHVIZ="$GRAPHVIZ" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_GRAPHVIZ="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi GRAPHVIZ=$ac_cv_prog_GRAPHVIZ if test -n "$GRAPHVIZ"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $GRAPHVIZ" >&5 printf "%s\n" "$GRAPHVIZ" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$GRAPHVIZ" && break done if test -z "$GRAPHVIZ"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: dot could not be found; we must continue without it. It is advised to be installed if you intend to use \"make doxy\"." >&5 printf "%s\n" "$as_me: WARNING: dot could not be found; we must continue without it. It is advised to be installed if you intend to use \"make doxy\"." >&2;} fi # Check whether --enable-debug was given. if test ${enable_debug+y} then : enableval=$enable_debug; case "${enableval}" in yes) debug=true ;; no) debug=false ;; *) as_fn_error $? "bad value ${enableval} for --enable-debug" "$LINENO" 5 ;; esac else case e in #( e) debug=false ;; esac fi if test x"$debug" = x"true"; then DEBUG_TRUE= DEBUG_FALSE='#' else DEBUG_TRUE='#' DEBUG_FALSE= fi # Check whether --enable-changelog was given. if test ${enable_changelog+y} then : enableval=$enable_changelog; case "${enableval}" in yes) changelog=yes ;; no) changelog=no ;; *) as_fn_error $? "bad value ${enableval} for --enable-changelog" "$LINENO" 5 ;; esac else case e in #( e) changelog=false ;; esac fi if test x$changelog = xno; then NOCHANGELOG_TRUE= NOCHANGELOG_FALSE='#' else NOCHANGELOG_TRUE='#' NOCHANGELOG_FALSE= fi # Outputs resulting files. ac_config_files="$ac_config_files Makefile src/Makefile test/Makefile" # Check whether --with-extra-version was given. if test ${with_extra_version+y} then : withval=$with_extra_version; EXTRA_VERSION="${withval}" else case e in #( e) EXTRA_VERSION="" ;; esac fi if test -n "$EXTRA_VERSION"; then ENABLE_EXTRA_VERSION_TRUE= ENABLE_EXTRA_VERSION_FALSE='#' else ENABLE_EXTRA_VERSION_TRUE='#' ENABLE_EXTRA_VERSION_FALSE= fi cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # 'ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 printf %s "checking that generated files are newer than configure... " >&6; } if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: done" >&5 printf "%s\n" "done" >&6; } case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; esac if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi if test -n "$EXEEXT"; then am__EXEEXT_TRUE= am__EXEEXT_FALSE='#' else am__EXEEXT_TRUE='#' am__EXEEXT_FALSE= fi if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then as_fn_error $? "conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi # Check whether --enable-year2038 was given. if test ${enable_year2038+y} then : enableval=$enable_year2038; fi if test -z "${USE_LIBDVD_TRUE}" && test -z "${USE_LIBDVD_FALSE}"; then as_fn_error $? "conditional \"USE_LIBDVD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HINT_LIBDVD_TRUE}" && test -z "${HINT_LIBDVD_FALSE}"; then as_fn_error $? "conditional \"HINT_LIBDVD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${USE_LIBBLURAY_TRUE}" && test -z "${USE_LIBBLURAY_FALSE}"; then as_fn_error $? "conditional \"USE_LIBBLURAY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${HINT_LIBBLURAY_TRUE}" && test -z "${HINT_LIBBLURAY_FALSE}"; then as_fn_error $? "conditional \"HINT_LIBBLURAY\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ENABLE_PERFTOOLS_TRUE}" && test -z "${ENABLE_PERFTOOLS_FALSE}"; then as_fn_error $? "conditional \"ENABLE_PERFTOOLS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${USE_LIBVCD_TRUE}" && test -z "${USE_LIBVCD_FALSE}"; then as_fn_error $? "conditional \"USE_LIBVCD\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DEBUG_TRUE}" && test -z "${DEBUG_FALSE}"; then as_fn_error $? "conditional \"DEBUG\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${NOCHANGELOG_TRUE}" && test -z "${NOCHANGELOG_FALSE}"; then as_fn_error $? "conditional \"NOCHANGELOG\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${ENABLE_EXTRA_VERSION_TRUE}" && test -z "${ENABLE_EXTRA_VERSION_FALSE}"; then as_fn_error $? "conditional \"ENABLE_EXTRA_VERSION\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by FFMPEGFS $as_me 2.50, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ '$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to the package provider." _ACEOF ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ FFMPEGFS config.status 2.50 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' MKDIR_P='$MKDIR_P' AWK='$AWK' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: '$1' Try '$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: '$1' Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf "%s\n" "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "src/config.h") CONFIG_HEADERS="$CONFIG_HEADERS src/config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac ac_MKDIR_P=$MKDIR_P case $MKDIR_P in [\\/$]* | ?:[\\/]* ) ;; */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t s&@MKDIR_P@&$ac_MKDIR_P&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf "%s\n" "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi # Compute "$ac_file"'s index in $config_headers. _am_arg="$ac_file" _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || $as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$_am_arg" : 'X\(//\)[^/]' \| \ X"$_am_arg" : 'X\(//\)$' \| \ X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$_am_arg" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 printf "%s\n" "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "depfiles":C) test x"$AMDEP_TRUE" != x"" || { # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. case $CONFIG_FILES in #( *\'*) : eval set x "$CONFIG_FILES" ;; #( *) : set x $CONFIG_FILES ;; #( *) : ;; esac shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`printf "%s\n" "$am_mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`$as_dirname -- "$am_mf" || $as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$am_mf" : 'X\(//\)[^/]' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$am_mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` am_filepart=`$as_basename -- "$am_mf" || $as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$am_mf" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` { echo "$as_me:$LINENO: cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles" >&5 (cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } || am_rc=$? done if test $am_rc -ne 0; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE=\"gmake\" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking). See 'config.log' for more details" "$LINENO" 5; } fi { am_dirpart=; unset am_dirpart;} { am_filepart=; unset am_filepart;} { am_mf=; unset am_mf;} { am_rc=; unset am_rc;} rm -f conftest-deps.mk } ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi # Hints on options if test -z "$HINT_LIBDVD_TRUE"; then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: HINT: For DVD support, install the libdvdread development packages. Consult INSTALL.md for more information." >&5 printf "%s\n" "$as_me: HINT: For DVD support, install the libdvdread development packages. Consult INSTALL.md for more information." >&6;} fi if test -z "$HINT_LIBBLURAY_TRUE"; then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: HINT: For Blu-ray support, install the libbluray development package. Consult INSTALL.md for more information." >&5 printf "%s\n" "$as_me: HINT: For Blu-ray support, install the libbluray development package. Consult INSTALL.md for more information." >&6;} fi ffmpegfs-2.50/ffmpegfs.1.txt0000664000175000017500000007260715175426212011436 = FFMPEGFS(1) = :doctype: manpage :man source: ffmpegfs :man version: {revnumber} :man manual: User Commands == NAME == ffmpegfs - mounts and transcodes a multitude of formats to one of the target formats on the fly. == SYNOPSIS == *ffmpegfs* ['OPTION']... 'IN_DIR' 'OUT_DIR' == DESCRIPTION == The ffmpegfs(1) command will mount the directory 'IN_DIR' on 'OUT_DIR'. Thereafter, accessing 'OUT_DIR' will show the contents of 'IN_DIR', with all supported media files transparently renamed and transcoded to one of the supported target formats upon access. Supported output formats: [width="100%"] |=================================================================================== | *Format* | *Description* | *Audio* | *Video* | AIFF | Audio Interchange File Format | | PCM 16 bit BE | ALAC | Apple Lossless Audio Codec | | ALAC | FLAC | Free Lossless Audio | | FLAC | HLS | HTTP Live Streaming | H264 | AAC | MOV | QuickTime File Format | H264 | AAC | MP3 | MPEG-2 Audio Layer III | | MP3 | MP4 | MPEG-4 | H264 | AAC | OGG | | Theora | Vorbis | MKV | Matroska | H264 | AAC | Opus | | Opus | | ProRes | Apple ProRes | ProRes | PCM 16 bit LE | TS | MPEG Transport Stream | H264 | AAC | WAV | Waveform Audio File Format | | PCM 16 bit LE | WebM | | VP9 | Opus | BMP | Video to frameset | | BMP | JPG | Video to frameset | | JPEG | PNG | Video to frameset | | PNG |=================================================================================== == OPTIONS == Usage: ffmpegfs [OPTION]... IN_DIR OUT_DIR Mount IN_DIR on OUT_DIR, converting audio and video files upon access. === Encoding options === *--desttype*=TYPE, *-odesttype*=TYPE:: Select the destination format. 'TYPE' can currently be: + *AIFF*, *ALAC*, *BMP*, *FLAC*, *HLS*, *JPG*, *MOV*, *MP3*, *MP4*, *MKV*, *OGG*, *Opus*, *PNG*, *ProRes*, *TS*, *WAV*, *WebM*. + To stream videos, *MP4*, *TS*, *HLS*, *OGG*, *WEBM*, *MKV*, or *MOV*/*PRORES* must be selected. + To use HTTP Live Streaming, set *HLS*. + When a destination *JPG*, *PNG*, or *BMP* is chosen, all frames of a video source file will be presented in a virtual directory named after the source file. Audio will not be available. + To use the smart transcoding feature, specify a video and audio file type, separated by a "+" sign. For example, --desttype=mov+aiff will convert video files to Apple Quicktime MOV and audio-only files to AIFF. + Defaults to: *mp4* *--audiocodec*=TYPE, *-oaudiocodec*=TYPE:: Select an audio codec. 'TYPE' depends on the destination format and can currently be: + [width="100%"] |=================================================================================== | *Formats* | *Audio Codecs* | MP4 | **AAC**, MP3 | WebM | **OPUS**, VORBIS | MOV | **AAC**, AC3, MP3 | MKV | **AAC**, AC3, MP3 | TS, HLS | **AAC**, AC3, MP3 |=================================================================================== + Other destination formats do not support other codecs than the default. + Defaults to: The destination format's default setting, as indicated by the first codec name in the list. *--videocodec*=TYPE, *-ovideocodec*=TYPE:: Select a video codec. 'TYPE' depends on the destination format and can currently be: + [width="100%"] |=================================================================================== | *Formats* | *Video Codecs* | MP4 | **H264**, H265, MPEG1, MPEG2 | WebM | **VP9**, VP8, AV1 | MOV | **H264**, H265, MPEG1, MPEG2 | MKV | **H264**, H265, MPEG1, MPEG2 | TS, HLS | **H264**, H265, MPEG1, MPEG2 |=================================================================================== + Other destination formats do not support other codecs than the default. + Defaults to: The destination format's default setting, as indicated by the first codec name in the list. *--autocopy*=OPTION, *-oautocopy*=OPTION:: Select the auto copy option. 'OPTION' can be: + [width="100%"] |=================================================================================== |*OFF* |Never copy streams, transcode always. |*MATCH* |Copy stream if target supports codec. |*MATCHLIMIT* |Same as MATCH, only copy if target not larger, transcode otherwise. |*STRICT* |Copy stream if codec matches desired target, transcode otherwise. |*STRICTLIMIT* |Same as STRICT, only copy if target not larger, transcode otherwise. |=================================================================================== + This can speed up transcoding significantly as copying streams uses much less computing power as compared to transcoding. + *MATCH* copies a stream if the target supports it, e.g., an AAC audio stream will be copied to MPEG, although FFmpeg's target format is MP3 for this container. H264 would be copied to ProRes, although the result would be a regular MOV or MP4, not a ProRes file. + *STRICT* would convert AAC to MP3 for MPEG or H264 to ProRes for Prores files to strictly adhere to the output format setting. This will create homogenous results which might prevent problems with picky playback software. + Note: When the *--audiocodec* or *--videocodec* option is specified, the STRICT option should be used to ensure that the chosen output codec is used in any scenario. MATCH would enable copy if the output format supports the input codec. Defaults to: *OFF* *--recodesame*=OPTION, *-orecodesame*=OPTION:: Select recode to the same format option, 'OPTION' can be: + [width="100%"] |=================================================================================== |*NO* |Never recode to the same format. |*YES* |Always recode to the same format. |=================================================================================== + Defaults to: *NO* *--profile*=NAME, *-oprofile*=NAME:: Set profile for target audience, 'NAME' can be: + [width="100%"] |======================================================= |*NONE* |no profile |*FF* |optimise for Firefox |*EDGE* |optimise for MS Edge and Internet Explorer > 11 |*IE* |optimise for MS Edge and Internet Explorer <= 11 |*CHROME* |Google Chrome |*SAFARI* |Apple Safari |*OPERA* |Opera |*MAXTHON* |Maxthon |======================================================= + *Note:* applies to the MP4 output format only, and is ignored for all other formats. + Defaults to: *NONE* --*level*=NAME, -o *level*=NAME:: Set level for output if available. 'NAME' can be: + [width="100%"] |=========================== |*PROXY* |Proxy – apco |*LT* |LT – apcs |*STANDARD* |standard – apcn |*HQ* |HQ - apch |=========================== + *Note:* applies to the MP4 output format only, and is ignored for all other formats. + Defaults to: *HQ* *--include_extensions*=LIST, *-oinclude_extensions*=LIST:: Set the list of file extensions to be encoded. 'LIST' can have one or more entries that are separated by commas. These are the only file extensions that will be transcoded. Can be specified numerous times and will be merged, which is required when specifying them in the fstab because commas cannot be used to separate the extensions. The entries support shell wildcard patterns. + Example: --include_extensions=mp4,wmv to encode MPEG-4 and Windows Media files only. + Defaults to: Encode all supported files. *--hide_extensions*=LIST, *-ohide_extensions*=LIST:: Set a list of file extensions to exclude from the output. 'LIST' can have one or more entries that are separated by commas. Can be specified numerous times and will be merged, which is required when specifying them in the fstab because commas cannot be used to separate the extensions. The entries support shell wildcard patterns. + Example: --hide_extensions=jpg,png,cue to stop covers and cue sheets from showing up. + Defaults to: Show all files. === Audio Options === *--audiobitrate*=BITRATE, *-o audiobitrate*=BITRATE:: Select the audio encoding bitrate. + Defaults to: *128 kbit* + *Acceptable values for 'BITRATE':* + *mp4:* 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 288, 320, 352, 384, 416, and 448 kbps. + *mp3:* For sampling frequencies of 32, 44.1, and 48 kHz, 'BITRATE' can be among 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, and 320 kbps. + For sampling frequencies of 16, 22.05, and 24 kHz, 'BITRATE' can be among 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, and 160 kbps. + When in doubt, it is recommended to choose a bitrate among 96, 112, 128, 160, 192, 224, 256, and 320 kbps. + -- *BITRATE*:: can be defined as... * n bit/s: # or #bps * n kbit/s: #K or #Kbps * n Mbit/s: #M or #Mbps -- *--audiosamplerate*=SAMPLERATE, *-o audiosamplerate*=SAMPLERATE:: This limits the output sample rate to 'SAMPLERATE'. If the source file sample rate is higher, it will be downsampled automatically. + Typical values are 8000, 11025, 22050, 44100, 48000, 96000, and 192000. + If the target codec does not support the selected sample rate, the next matching rate will be chosen (e.g. if 24K is selected but only 22.05 or 44.1 KHz is supported, 22.05 KHz will be set). + Set to 0 to keep the source rate. + Defaults to: *44.1 kHz* + -- *SAMPLERATE*:: can be defined as... * In Hz: # or #Hz * In kHz: #K or #KHz -- *--audiochannels*=CHANNELS, *-o audiochannels*=CHANNELS:: This limits the number of output channels to 'CHANNELS'. If the source has more channels, the number will be reduced to this limit. + Typical values are 1, 2 or 6 (e.g., 5.1) channels. + If the target codec does not support the selected number of channels, transcoding may fail. + Set to 0 to keep the number of channels. + Defaults to: *2 channels (stereo)* *--audiosamplefmt*=SAMPLEFMT, *-o audiosamplefmt*=SAMPLEFMT:: This sets a sample format. 'SAMPLEFMT' can be: + 0 to use the predefined setting; 8, 16, 32, 64 for integer format, F16, F32, F64 for floating point. + Not all formats are supported by all destination types. Selecting an invalid format will be reported as a command line error and a list of values printed. + [width="100%"] |=========================== |*Container Format* | *Sample Format* |*AIFF* |0, 16, 32 |*ALAC* |0, 16, 24 |*WAV* |0, 8, 16, 32, 64, F16, F32, F64 |*FLAC* |0, 16, 24 |=========================== + Defaults to: 0 (Use the same as the source or the predefined format of the destination if the source format is not possible.) === Video Options === *--videobitrate*=BITRATE, *-o videobitrate*=BITRATE:: This sets the video encoding bit rate. Setting this too high or too low may cause transcoding to fail. + Defaults to: *2 Mbit* + *mp4:* May be specified as 500 to 25,000 kbps. + -- *BITRATE*:: can be defined as... * n bit/s: # or #bps * n kbit/s: #K or #Kbps * n Mbit/s: #M or #Mbps -- *--videoheight*=HEIGHT, -o *videoheight*=HEIGHT:: This sets the height of the transcoded video. + When the video is rescaled, the aspect ratio is preserved if --width is not set at the same time. + Defaults to: *keep source video height* *--videowidth*=WIDTH, -o *videowidth*=WIDTH:: This sets the width of the transcoded video. + When the video is rescaled, the aspect ratio is preserved if --height is not set at the same time. + Defaults to: *keep source video width* *--deinterlace*, -o *deinterlace*:: Deinterlace video if necessary while transcoding. + This may need a higher bit rate, but this will increase picture quality when streaming via HTML5. + Defaults to: "no deinterlace" === HLS Options === *--segment_duration*, -o *segment_duration*:: Set the duration of one video segment of the HLS stream. This argument is a floating point value, e.g., it can be set to 2.5 for 2500 milliseconds. + Should normally be left as the default. + *Note:* This applies to the HLS output format only, and is ignored for all other formats. + Defaults to: *10 seconds* *--min_seek_time_diff*, -o *min_seek_time_diff*:: If the requested HLS segment is less than min_seek_time seconds away, discard the seek request. The segment will be available very soon anyway, and that makes a re-transcode necessary. Set to 0 to disable. + Should normally be left as the default. + *Note:* This applies to the HLS output format only, and is ignored for all other formats. + Defaults to: *30 seconds* === Hardware Acceleration Options === *--hwaccel_enc*=API, *-o hwaccel_enc*=API:: Select the hardware acceleration API for encoding. + Defaults to: *NONE* (no acceleration). + -- *API*:: can be defined as... * *NONE*: use software encoder * *VAAPI*: Video Acceleration API (VA-API) * *OMX*: OpenMAX (Open Media Acceleration) -- *--hwaccel_dec_blocked*=CODEC[:PROFILE[:PROFILE]], *-o hwaccel_dec_blocked*=CODEC:[:PROFILE[:PROFILE]]:: Block a codec and, optionally, a profile for hardware decoding. The option can be repeated to block several codecs. + Defaults to: no codecs blocked. + -- *CODEC*:: can be defined as... * *H263*: H.263 * *H264*: H.264 * *HEVC*: H.265 / HEVC * *MPEG2*: MPEG-2 video * *MPEG4*: MPEG-4 video * *VC1*: SMPTE VC-1 * *VP8*: Google VP9 * *VP9*: Google VP9 * *WMV3*: Windows Media Video 9 -- + *PROFILE*:: can optionally be added to block a certain profile from the codec only. + Example: VP9:0 blocks Google VP profile 0. + Example: H264:1:33 blocks H.264 profile 1 and 33. *--hwaccel_enc_device*=DEVICE, -o *hwaccel_enc_device*=DEVICE:: Select the hardware acceleration device. May be required for VAAPI, especially if more than one device is available. + *Note:* This only applies to VAAPI hardware acceleration; all other types are ignored. + Defaults to: *empty* (use default device). + Example: */dev/dri/renderD128* *--hwaccel_dec*=API, *-o hwaccel_dec*=API:: Select the hardware acceleration API for decoding. + Defaults to: *NONE* (no acceleration) + -- *API*:: can be defined as... * *NONE*: use software decoder * *VAAPI*: Video Acceleration API (VA-API) * *MMAL*: Multimedia Abstraction Layer by Broadcom -- *--hwaccel_dec_device*=DEVICE, -o *hwaccel_dec_device*=DEVICE:: Select the hardware acceleration device. May be required for VAAPI, especially if more than one device is available. + *Note:* This only applies to VAAPI hardware acceleration; all other types are ignored. + Defaults to: *empty* (use default device) + Example: */dev/dri/renderD128* === Album Arts === --*noalbumarts*, -o *noalbumarts*:: Do not copy album art into the output file. + This will reduce the file size and may be useful when streaming via HTML5 when album art is not used anyway. + Defaults to: *add album arts* === Virtual Script === --*enablescript*, -o *enablescript*:: Add a virtual index.php to every directory. It reads scripts/videotag.php from the FFmpegfs binary directory. + This can be very handy for testing video playback. Of course, feel free to replace videotag.php with your own script. + Defaults to: *Do not generate script file* --*scriptfile*, -o *scriptfile*:: Set the name of the virtual script created in each directory. + Defaults to: *index.php* --*scriptsource*, -o *scriptsource*:: Use a different source file. + Defaults to: *scripts/videotag.php* === Cache Options === *--expiry_time*=TIME, *-o expiry_time*=TIME:: Cache entries expire after 'TIME' and will be deleted to save disc space. + Defaults to: *1 week* *--max_inactive_suspend*=TIME, *-o max_inactive_suspend*=TIME:: While being accessed, the file is transcoded to the target format in the background. When the client quits, transcoding will continue until this time out. Transcoding is suspended until it is accessed again, then transcoding will continue. + Defaults to: *15 seconds* *--max_inactive_abort*=TIME, *-o max_inactive_abort*=TIME:: While being accessed, the file is transcoded in the background to the target format. When the client quits, transcoding will continue until this time out, then the transcoder thread quits. + Defaults to: *30 seconds* *--prebuffer_time*=TIME, *-o prebuffer_time*=TIME:: Files will be decoded until the buffer contains the specified playing time, allowing playback to start smoothly without lags. Both options must be met if prebuffer time and prebuffer size are specified. + Set to 0 to disable pre-buffering. + Defaults to: *no prebuffer time* *--prebuffer_size*=SIZE, *-o prebuffer_size*=SIZE:: Files will be decoded until the specified number of bytes is present in the buffer, allowing playback to start smoothly without lags. Both options must be met if prebuffer size and prebuffer time are specified. + Set to 0 to disable pre-buffering. + Defaults to: *100 KB* *--max_cache_size*=SIZE, *-o max_cache_size*=SIZE:: Set the maximum diskspace used by the cache. If the cache grows beyond this limit when a file is transcoded, old entries will be deleted to keep the cache within the size limit. + Defaults to: *unlimited* *--min_diskspace*=SIZE, *-o min_diskspace*=SIZE:: Set the required diskspace on the cachepath mount. If the remaining space falls below 'SIZE' when a file is transcoded, old entries will be deleted to keep the diskspace within the limit. + Defaults to: *0 (no minimum space)* *--cachepath*=DIR, *-o cachepath*=DIR:: Sets the disc cache directory to 'DIR'. If it does not already exist, it will be created. The user running FFmpegfs must have write access to the location. + Defaults to: *$\{XDG_CACHE_HOME:-\~/.cache}/ffmpegfs* (as specified in the XDG Base Directory Specification). Falls back to $\{HOME:-~/.cache}/ffmpegfs if not defined. If executed with root privileges, "/var/cache/ffmpegfs" will be used. *--disable_cache*, -o *disable_cache*:: Disable the cache functionality completely. + Defaults to: *enabled* *--cache_maintenance*=TIME, *-o cache_maintenance*=TIME:: Starts cache maintenance in 'TIME' intervals. This will enforce the expery_time, max_cache_size and min_diskspace settings. Do not set it too low as this can slow down transcoding. + Only one FFmpegfs process will do the maintenance by becoming the master. If that process exits, another will take over, so that one will always do the maintenance. + Defaults to: *1 hour* *--prune_cache*:: Prune the cache immediately according to the above settings at application start up. + Defaults to: *Do not prune cache* *--clear_cache*, *-o clear_cache*:: On startup, clear the cache. All previously transcoded files will be deleted. + -- *TIME*:: can be defined as... * Seconds: # * Minutes: #m * Hours: #h * Days: #d * Weeks: #w -- + -- *SIZE*:: can be defined as... * In bytes: # or #B * In KBytes: #K or #KB * In MBytes: #M or #MB * In GBytes: #G or #GB * In TBytes: #T or #TB -- === Other === *--max_threads*=COUNT, *-o max_threads*=COUNT:: Limit concurrent transcoder threads. Set to 0 for unlimited threads. Recommended values are up to 16 times the number of CPU cores. Should be left as the default. + Defaults to: *16 times number of detected cpu cores* *--decoding_errors*, *-o decoding_errors*:: Decoding errors are normally ignored, leaving bloopers and hiccups in encoded audio or video but still creating a valid file. When this option is set, transcoding will stop with an error. + Defaults to: *Ignore errors* *--min_dvd_chapter_duration*=SECONDS, *-o min_dvd_chapter_duration*=SECONDS:: This ignores DVD chapters shorter than SECONDS. To disable, set to 0. This avoids transcoding errors for DVD chapters too short to detect its streams. + Defaults to: *1 second* *--win_smb_fix*, *-o win_smb_fix*:: Windows seems to access the files on Samba drives starting at the last 64K segment when the file is opened. Setting --win_smb_fix=1 will ignore these attempts (not decode the file up to this point). + Defaults to: *on* === Logging === *--log_maxlevel*=LEVEL, *-o log_maxlevel*=LEVEL:: Maximum level of messages to log, either ERROR, WARNING, INFO, DEBUG or TRACE. Defaults to INFO and is always set to DEBUG in debug mode. + Note that the other log flags must also be set to enable logging. *--log_stderr*, *-o log_stderr*:: Enable outputting logging messages to stderr. Automatically enabled in debug mode. *--log_syslog*, *-o log_syslog*:: Enable outputting logging messages to syslog. *--logfile*=FILE, *-o logfile*=FILE:: File to output log messages to. By default, no file will be written. === General/FUSE options === *-d*, *-o debug*:: Enable debug output. This will result in a large quantity of diagnostic information being printed to stderr as the programme runs. It implies *-f*. *-f*:: Run in the foreground instead of detaching from the terminal. *-h*, *--help*:: Print usage information. *-V*, *--version*:: Output version information. *-c*, *--capabilities*:: Output FFmpeg capabilities: a list of the system's available codecs. *-s*:: Force single-threaded operation. == Usage == Mount your file system as follows: ffmpegfs [--audiobitrate bitrate] [--videobitrate bitrate] musicdir mountpoint [-o fuse_options] To use FFmpegfs as a daemon and encode to MPEG-4, for instance: ffmpegfs --audiobitrate=256K --videobitrate=1.5M /mnt/music /mnt/ffmpegfs -o allow_other,ro,desttype=mp4 This will run FFmpegfs in the foreground and print the log output to the screen: ffmpegfs -f --log_stderr --audiobitrate=256K --videobitrate=1.5M --audiobitrate=256K --videobitrate=1.5M /mnt/music /mnt/ffmpegfs -o allow_other,ro,desttype=mp4 With the following entry in "/etc/fstab," the same result can be obtained with more recent versions of FUSE: ffmpegfs#/mnt/music /mnt/ffmpegfs fuse allow_other,ro,audiobitrate=256K,videobitrate=2000000,desttype=mp4 0 0 Another (more current) way to express this command: /mnt/music /mnt/ffmpegfs fuse.ffmpegfs allow_other,ro,audiobitrate=256K,videobitrate=2000000,desttype=mp4 0 0 At this point, files like `/mnt/music/**.flac` and `/mnt/music/**.ogg` will show up as `/mnt/ffmpegfs/**.mp4`. Audio bitrates will be reduced to 256 KBit, video to 1.5 MBit. The source bitrate will not be scaled up if it is lower; it will remain at the lower value. Keep in mind that only root can, by default, utilise the "allow other" option. Either use the "user allow other" key in /etc/fuse.conf or run FFmpegfs as root. Any user must have "allow other" enabled in order to access the mount. By default, only the user who initiated FFmpegfs has access to this. Examples: ffmpegfs -f $HOME/test/in $HOME/test/out --log_stderr --log_maxlevel=DEBUG -o allow_other,ro,desttype=mp4,cachepath=$HOME/test/cache Transcode files using FFmpegfs from test/in to test/out while logging to stderr at a noisy TRACE level. The cache resides in test/cache. All directories are under the current user's home directory. ffmpegfs -f $HOME/test/in $HOME/test/out --log_stderr --log_maxlevel=DEBUG -o allow_other,ro,desttype=mp4,cachepath=$HOME/test/cache,videowidth=640 Similar to the previous, but with a 640-pixel maximum video width. The aspect ratio will be maintained when scaling down larger videos. Videos that are smaller won't be scaled up. ffmpegfs -f $HOME/test/in $HOME/test/out --log_stderr --log_maxlevel=DEBUG -o allow_other,ro,desttype=mp4,cachepath=$HOME/test/cache,deinterlace Deinterlacing can be enabled for better image quality. == HOW IT WORKS == The decoder and encoder are initialised when a file is opened, and the file's metadata is also read. At this point, a rough estimate of the total file size can be made. Because the actual size greatly depends on the material encoded, this technique works fair-to-good for MP4 or WebM output files but works well for MP3, AIFF, or WAV output files. The file is transcoded as it is being read and stored in a private per-file buffer. This buffer keeps expanding as the file is read until the entire file has been transcoded. After being decoded, the file is stored in a disc buffer and is readily accessible. Other processes will share the same transcoded data if they access the same file because transcoding is done in a single additional thread, which saves CPU time. Transcoding will continue for a while if all processes close the file before it is finished. Transcoding will resume if the file is viewed once more before the timer expires. If not, it will halt and delete the current chunk to free up storage space. A file will be transcoded up to the seek point when you seek within it (if not already done). Since the majority of programmes will read a file from beginning to end, this is typically not a problem. Future upgrades might offer actual random seeking (but if this is feasible, it is not yet clear due to restrictions to positioning inside compressed streams). When HLS streaming is chosen, this already functions. The requested segment is immediately skipped to by FFmpegfs. *MP3:* The source file's comments are used to generate ID3 version 2.4 and 1.1 tags. They are correspondingly at the beginning and the end of the file. *MP4:* The same is true for meta atoms contained in MP4 containers. *WAV*: The estimated size of the WAV file will be included in a pro forma WAV header. When the file is complete, this header will be changed. Though most current gamers apparently disregard this information and continue to play the file, it does not seem required. Only for MP3 targets: A particular optimization has been done so that programmes that look for id3v1 tags don't have to wait for the entire file to be transcoded before reading the tag. This accelerates these apps *dramatically*. == ABOUT OUTPUT FORMATS == A few remarks regarding the output formats that are supported: Since these are plain vanilla constant bitrate (CBR) MP3 files, there isn't much to say about the MP3 output. Any modern player should be able to play them well. However, MP4 files are unique because standard MP4s aren't really ideal for live broadcasting. The start block of an MP4 has a field with the size of the compressed data section, which is the cause. It suffices to say that until the size is known, compression must be finished, a file seek must be performed to the beginning, and the size atom updated. That size is unknown for a live stream that is ongoing. To obtain that value for our transcoded files, one would need to wait for the entire file to be recoded. As if that weren't enough, the file's final section contains some crucial details, such as meta tags for the artist, album, etc. Additionally, the fact that there is just one enormous data block makes it difficult to do random searches among the contents without access to the entire data section. Many programmes will then read the crucial information from the end of an MP4 before returning to the file's head and beginning playback. This will destroy FFmpegfs' entire transcode-on-demand concept. Several extensions have been created to work around the restriction, including "faststart," which moves the aforementioned meta data from the end to the beginning of the MP4 file. Additionally, it is possible to omit the size field (0). An further plugin is isml (smooth live streaming). Older versions of FFmpeg do not support several new MP4 features that are required for direct-to-stream transcoding, like ISMV, faststart, separate moof/empty moov, to mention a few (or if available, not working properly). Faststart files are produced by default with an empty size field so that the file can be started to be written out at once rather than having to be encoded as a complete first. It would take some time before playback could begin if it were fully encoded. The data part is divided into chunks of about 1 second each, all with their own header, so it is possible to fill in the size fields early enough. One disadvantage is that not all players agree with the format, or they play it with odd side effects. VLC only refreshes the time display every several seconds while playing the file. There may not always be a complete duration displayed while streaming using HTML5 video tags, but that is fine as long as the content plays. Playback can only move backwards from the current playback position. However, that is the cost of commencing playback quickly. == DEVELOPMENT == Git is the revision control system used by FFmpegfs. The complete repository is available here: `git clone https://github.com/nschlia/ffmpegfs.git` or the mirror: `git clone https://salsa.debian.org/nschlia/ffmpegfs.git` FFmpegfs is composed primarily of C++17 with a small amount of C. The following libraries are utilised: * http://fuse.sourceforge.net/[FUSE] FFmpeg home pages: * https://www.ffmpeg.org/[FFmpeg] == FILES == */usr/local/bin/ffmpegfs*, */etc/fstab* == AUTHORS == This fork with FFmpeg support has been maintained by mailto:nschlia@oblivion-software.de[Norbert Schlia] since 2017 to date. Based on work by K. Henriksson (from 2008 to 2017) and the original author, David Collett (from 2006 to 2008). Much thanks to them for the original work and giving me a good head start! == LICENSE == This program can be distributed under the terms of the GNU GPL version 3 or later. It can be found http://www.gnu.org/licenses/gpl-3.0.html[online] or in the COPYING file. This file and other documentation files can be distributed under the terms of the GNU Free Documentation License 1.3 or later. It can be found http://www.gnu.org/licenses/fdl-1.3.html[online] or in the COPYING.DOC file. == FFMPEG LICENSE == FFmpeg is licensed under the GNU Lesser General Public License (LGPL) version 2.1 or later. However, FFmpeg incorporates several optional parts and optimizations that are covered by the GNU General Public License (GPL) version 2 or later. If those parts get used the GPL applies to all of FFmpeg. See https://www.ffmpeg.org/legal.html for details. == COPYRIGHT == This fork with FFmpeg support copyright \(C) 2017-2026 mailto:nschlia@oblivion-software.de[Norbert Schlia]. Based on work copyright \(C) 2006-2008 David Collett, 2008-2013 K. Henriksson. Much thanks to them for the original work! This is free software: you are free to change and redistribute it under the terms of the GNU General Public License (GPL) version 3 or later. This manual is copyright \(C) 2010-2011 K. Henriksson and \(C) 2017-2026 by N. Schlia and may be distributed under the GNU Free Documentation License (GFDL) 1.3 or later with no invariant sections, or alternatively under the GNU General Public License (GPL) version 3 or later. ffmpegfs-2.50/config/0000775000175000017500000000000015215723160010247 5ffmpegfs-2.50/config/depcomp0000755000175000017500000005602014553310161011542 #! /bin/sh # depcomp - compile a program generating dependencies as side-effects scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2021 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Alexandre Oliva . case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: depcomp [--help] [--version] PROGRAM [ARGS] Run PROGRAMS ARGS to compile a file, generating dependencies as side-effects. Environment variables: depmode Dependency tracking mode. source Source file read by 'PROGRAMS ARGS'. object Object file output by 'PROGRAMS ARGS'. DEPDIR directory where to store dependencies. depfile Dependency file to output. tmpdepfile Temporary file to use when outputting dependencies. libtool Whether libtool is used (yes/no). Report bugs to . EOF exit $? ;; -v | --v*) echo "depcomp $scriptversion" exit $? ;; esac # Get the directory component of the given path, and save it in the # global variables '$dir'. Note that this directory component will # be either empty or ending with a '/' character. This is deliberate. set_dir_from () { case $1 in */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; *) dir=;; esac } # Get the suffix-stripped basename of the given path, and save it the # global variable '$base'. set_base_from () { base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` } # If no dependency file was actually created by the compiler invocation, # we still have to create a dummy depfile, to avoid errors with the # Makefile "include basename.Plo" scheme. make_dummy_depfile () { echo "#dummy" > "$depfile" } # Factor out some common post-processing of the generated depfile. # Requires the auxiliary global variable '$tmpdepfile' to be set. aix_post_process_depfile () { # If the compiler actually managed to produce a dependency file, # post-process it. if test -f "$tmpdepfile"; then # Each line is of the form 'foo.o: dependency.h'. # Do two passes, one to just change these to # $object: dependency.h # and one to simply output # dependency.h: # which is needed to avoid the deleted-header problem. { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" } > "$depfile" rm -f "$tmpdepfile" else make_dummy_depfile fi } # A tabulation character. tab=' ' # A newline character. nl=' ' # Character ranges might be problematic outside the C locale. # These definitions help. upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ lower=abcdefghijklmnopqrstuvwxyz digits=0123456789 alpha=${upper}${lower} if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 fi # Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. depfile=${depfile-`echo "$object" | sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" # Avoid interferences from the environment. gccflag= dashmflag= # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case # here, because this file can only contain one case statement. if test "$depmode" = hp; then # HP compiler uses -M and no extra arg. gccflag=-M depmode=gcc fi if test "$depmode" = dashXmstdout; then # This is just like dashmstdout with a different argument. dashmflag=-xM depmode=dashmstdout fi cygpath_u="cygpath -u -f -" if test "$depmode" = msvcmsys; then # This is just like msvisualcpp but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvisualcpp fi if test "$depmode" = msvc7msys; then # This is just like msvc7 but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 cygpath_u='sed s,\\\\,/,g' depmode=msvc7 fi if test "$depmode" = xlc; then # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. gccflag=-qmakedep=gcc,-MF depmode=gcc fi case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what ## we want. Yay! Note: for some reason libtool 1.4 doesn't like ## it if -MD -MP comes after the -MF stuff. Hmm. ## Unfortunately, FreeBSD c89 acceptance of flags depends upon ## the command line argument order; so add the flags where they ## appear in depend2.am. Note that the slowdown incurred here ## affects only configure: in makefiles, %FASTDEP% shortcuts this. for arg do case $arg in -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; *) set fnord "$@" "$arg" ;; esac shift # fnord shift # $arg done "$@" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi mv "$tmpdepfile" "$depfile" ;; gcc) ## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. ## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. ## (see the conditional assignment to $gccflag above). ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like ## -MM, not -M (despite what the docs say). Also, it might not be ## supported by the other compilers which use the 'gcc' depmode. ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then gccflag=-MD, fi "$@" -Wp,"$gccflag$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The second -e expression handles DOS-style file names with drive # letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" ## This next piece of magic avoids the "deleted header file" problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. ## Some versions of gcc put a space before the ':'. On the theory ## that the space means something, we add a space to the output as ## well. hp depmode also adds that space, but also prefixes the VPATH ## to the object. Take care to not repeat it in the output. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; sgi) if test "$libtool" = yes; then "$@" "-Wp,-MDupdate,$tmpdepfile" else "$@" -MDupdate "$tmpdepfile" fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files echo "$object : \\" > "$depfile" # Clip off the initial element (the dependent). Don't try to be # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; # the IRIX cc adds comments like '#:fec' to the end of the # dependency line. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ | tr "$nl" ' ' >> "$depfile" echo >> "$depfile" # The second pass generates a dummy entry for each header file. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" ;; xlc) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. In older versions, this file always lives in the # current directory. Also, the AIX compiler puts '$object:' at the # start of each line; $object doesn't have directory information. # Version 6 uses the directory in both cases. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.u tmpdepfile2=$base.u tmpdepfile3=$dir.libs/$base.u "$@" -Wc,-M else tmpdepfile1=$dir$base.u tmpdepfile2=$dir$base.u tmpdepfile3=$dir$base.u "$@" -M fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done aix_post_process_depfile ;; tcc) # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 # FIXME: That version still under development at the moment of writing. # Make that this statement remains true also for stable, released # versions. # It will wrap lines (doesn't matter whether long or short) with a # trailing '\', as in: # # foo.o : \ # foo.c \ # foo.h \ # # It will put a trailing '\' even on the last line, and will use leading # spaces rather than leading tabs (at least since its commit 0394caf7 # "Emit spaces for -MD"). "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. # We have to change lines of the first kind to '$object: \'. sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" # And for each line of the second kind, we have to emit a 'dep.h:' # dummy dependency, to avoid the deleted-header problem. sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; ## The order of this option in the case statement is important, since the ## shell code in configure will try each of these formats in the order ## listed in this file. A plain '-MD' option would be understood by many ## compilers, so we must ensure this comes after the gcc and icc options. pgcc) # Portland's C compiler understands '-MD'. # Will always output deps to 'file.d' where file is the root name of the # source file under compilation, even if file resides in a subdirectory. # The object file name does not affect the name of the '.d' file. # pgcc 10.2 will output # foo.o: sub/foo.c sub/foo.h # and will wrap long lines using '\' : # foo.o: sub/foo.c ... \ # sub/foo.h ... \ # ... set_dir_from "$object" # Use the source, not the object, to determine the base name, since # that's sadly what pgcc will do too. set_base_from "$source" tmpdepfile=$base.d # For projects that build the same source file twice into different object # files, the pgcc approach of using the *source* file root name can cause # problems in parallel builds. Use a locking strategy to avoid stomping on # the same $tmpdepfile. lockdir=$base.d-lock trap " echo '$0: caught signal, cleaning up...' >&2 rmdir '$lockdir' exit 1 " 1 2 13 15 numtries=100 i=$numtries while test $i -gt 0; do # mkdir is a portable test-and-set. if mkdir "$lockdir" 2>/dev/null; then # This process acquired the lock. "$@" -MD stat=$? # Release the lock. rmdir "$lockdir" break else # If the lock is being held by a different process, wait # until the winning process is done or we timeout. while test -d "$lockdir" && test $i -gt 0; do sleep 1 i=`expr $i - 1` done fi i=`expr $i - 1` done trap - 1 2 13 15 if test $i -le 0; then echo "$0: failed to acquire lock after $numtries attempts" >&2 echo "$0: check lockdir '$lockdir'" >&2 exit 1 fi if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each line is of the form `foo.o: dependent.h', # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this invocation # correctly. Breaking it into two sed invocations is a workaround. sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp2) # The "hp" stanza above does not work with aCC (C++) and HP's ia64 # compilers, which have integrated preprocessors. The correct option # to use with these is +Maked; it writes dependencies to a file named # 'foo.d', which lands next to the object file, wherever that # happens to be. # Much of this is similar to the tru64 case; see comments there. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then tmpdepfile1=$dir$base.d tmpdepfile2=$dir.libs/$base.d "$@" -Wc,+Maked else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d "$@" +Maked fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" do test -f "$tmpdepfile" && break done if test -f "$tmpdepfile"; then sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" # Add 'dependent.h:' lines. sed -ne '2,${ s/^ *// s/ \\*$// s/$/:/ p }' "$tmpdepfile" >> "$depfile" else make_dummy_depfile fi rm -f "$tmpdepfile" "$tmpdepfile2" ;; tru64) # The Tru64 compiler uses -MD to generate dependencies as a side # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put # dependencies in 'foo.d' instead, so we check for that too. # Subdirectories are respected. set_dir_from "$object" set_base_from "$object" if test "$libtool" = yes; then # Libtool generates 2 separate objects for the 2 libraries. These # two compilations output dependencies in $dir.libs/$base.o.d and # in $dir$base.o.d. We have to check for both files, because # one of the two compilations can be disabled. We should prefer # $dir$base.o.d over $dir.libs/$base.o.d because the latter is # automatically cleaned when .libs/ is deleted, while ignoring # the former would cause a distcleancheck panic. tmpdepfile1=$dir$base.o.d # libtool 1.5 tmpdepfile2=$dir.libs/$base.o.d # Likewise. tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 "$@" -Wc,-MD else tmpdepfile1=$dir$base.d tmpdepfile2=$dir$base.d tmpdepfile3=$dir$base.d "$@" -MD fi stat=$? if test $stat -ne 0; then rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" exit $stat fi for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" do test -f "$tmpdepfile" && break done # Same post-processing that is required for AIX mode. aix_post_process_depfile ;; msvc7) if test "$libtool" = yes; then showIncludes=-Wc,-showIncludes else showIncludes=-showIncludes fi "$@" $showIncludes > "$tmpdepfile" stat=$? grep -v '^Note: including file: ' "$tmpdepfile" if test $stat -ne 0; then rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" # The first sed program below extracts the file names and escapes # backslashes for cygpath. The second sed program outputs the file # name when reading, but also accumulates all include files in the # hold buffer in order to output them again at the end. This only # works with sed implementations that can handle large buffers. sed < "$tmpdepfile" -n ' /^Note: including file: *\(.*\)/ { s//\1/ s/\\/\\\\/g p }' | $cygpath_u | sort -u | sed -n ' s/ /\\ /g s/\(.*\)/'"$tab"'\1 \\/p s/.\(.*\) \\/\1:/ H $ { s/.*/'"$tab"'/ G p }' >> "$depfile" echo >> "$depfile" # make sure the fragment doesn't end with a backslash rm -f "$tmpdepfile" ;; msvc7msys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. dashmstdout) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout, regardless of -o. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done test -z "$dashmflag" && dashmflag=-M # Require at least two characters before searching for ':' # in the target name. This is to cope with DOS-style filenames: # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. "$@" $dashmflag | sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process this sed invocation # correctly. Breaking it into two sed invocations is a workaround. tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; dashXmstdout) # This case only exists to satisfy depend.m4. It is never actually # run, as this mode is specially recognized in the preamble. exit 1 ;; makedepend) "$@" || exit $? # Remove any Libtool call if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # X makedepend shift cleared=no eat=no for arg do case $cleared in no) set ""; shift cleared=yes ;; esac if test $eat = yes; then eat=no continue fi case "$arg" in -D*|-I*) set fnord "$@" "$arg"; shift ;; # Strip any option that makedepend may not understand. Remove # the object too, otherwise makedepend will parse it as a source file. -arch) eat=yes ;; -*|$object) ;; *) set fnord "$@" "$arg"; shift ;; esac done obj_suffix=`echo "$object" | sed 's/^.*\././'` touch "$tmpdepfile" ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" rm -f "$depfile" # makedepend may prepend the VPATH from the source file name to the object. # No need to regex-escape $object, excess matching of '.' is harmless. sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" # Some versions of the HPUX 10.20 sed can't process the last invocation # correctly. Breaking it into two sed invocations is a workaround. sed '1,2d' "$tmpdepfile" \ | tr ' ' "$nl" \ | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" "$tmpdepfile".bak ;; cpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi # Remove '-o $object'. IFS=" " for arg do case $arg in -o) shift ;; $object) shift ;; *) set fnord "$@" "$arg" shift # fnord shift # $arg ;; esac done "$@" -E \ | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ | sed '$ s: \\$::' > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" cat < "$tmpdepfile" >> "$depfile" sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; msvisualcpp) # Important note: in order to support this mode, a compiler *must* # always write the preprocessed file to stdout. "$@" || exit $? # Remove the call to Libtool. if test "$libtool" = yes; then while test "X$1" != 'X--mode=compile'; do shift done shift fi IFS=" " for arg do case "$arg" in -o) shift ;; $object) shift ;; "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") set fnord "$@" shift shift ;; *) set fnord "$@" "$arg" shift shift ;; esac done "$@" -E 2>/dev/null | sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" echo "$tab" >> "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; msvcmsys) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; none) exec "$@" ;; *) echo "Unknown depmode $depmode" 1>&2 exit 1 ;; esac exit 0 # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ffmpegfs-2.50/config/install-sh0000755000175000017500000003577614553310161012210 #!/bin/sh # install - install a program, script, or datafile scriptversion=2020-11-14.01; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. tab=' ' nl=' ' IFS=" $tab$nl" # Set DOITPROG to "echo" to test this script. doit=${DOITPROG-} doit_exec=${doit:-exec} # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_mkdir= # Desired mode of installed file. mode=0755 # Create dirs (including intermediate dirs) using mode 755. # This is like GNU 'install' as of coreutils 8.32 (2020). mkdir_umask=22 backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false is_target_a_directory=possibly usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -p pass -p to $cpprog. -s $stripprog installed files. -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG By default, rm is invoked with -f; when overridden with RMPROG, it's up to you to specify -f if you want it. If -S is not specified, no backups are attempted. Email bug reports to bug-automake@gnu.org. Automake home page: https://www.gnu.org/software/automake/ " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -p) cpprog="$cpprog -p";; -s) stripcmd=$stripprog;; -S) backupsuffix="$2" shift;; -t) is_target_a_directory=always dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) is_target_a_directory=never;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done # We allow the use of options -d and -T together, by making -d # take the precedence; this is for compatibility with GNU install. if test -n "$dir_arg"; then if test -n "$dst_arg"; then echo "$0: target directory not allowed when installing a directory." >&2 exit 1 fi fi if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then if test $# -gt 1 || test "$is_target_a_directory" = always; then if test ! -d "$dst_arg"; then echo "$0: $dst_arg: Is not a directory." >&2 exit 1 fi fi fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? # Don't chown directories that already exist. if test $dstdir_status = 0; then chowncmd="" fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dstbase=`basename "$src"` case $dst in */) dst=$dst$dstbase;; *) dst=$dst/$dstbase;; esac dstdir_status=0 else dstdir=`dirname "$dst"` test -d "$dstdir" dstdir_status=$? fi fi case $dstdir in */) dstdirslash=$dstdir;; *) dstdirslash=$dstdir/;; esac obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false # The $RANDOM variable is not portable (e.g., dash). Use it # here however when possible just to lower collision chance. tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap ' ret=$? rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null exit $ret ' 0 # Because "mkdir -p" follows existing symlinks and we likely work # directly in world-writeable /tmp, make sure that the '$tmpdir' # directory is successfully created first before we actually test # 'mkdir -p'. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. test_tmpdir="$tmpdir/a" ls_ld_tmpdir=`ls -ld "$test_tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null fi trap '' 0;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac oIFS=$IFS IFS=/ set -f set fnord $dstdir shift set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=${dstdirslash}_inst.$$_ rmtmp=${dstdirslash}_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && { test -z "$stripcmd" || { # Create $dsttmp read-write so that cp doesn't create it read-only, # which would cause strip to fail. if test -z "$doit"; then : >"$dsttmp" # No need to fork-exec 'touch'. else $doit touch "$dsttmp" fi } } && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # If $backupsuffix is set, and the file being installed # already exists, attempt a backup. Don't worry if it fails, # e.g., if mv doesn't support -f. if test -n "$backupsuffix" && test -f "$dst"; then $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null fi # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ffmpegfs-2.50/config/config.guess0000755000175000017500000014051214553310161012505 #! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2022 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale timestamp='2022-01-09' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.guess # # Please send patches to . # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2022 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi # Just in case it came from the environment. GUESS= # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. tmp= # shellcheck disable=SC2172 trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 set_cc_for_build() { # prevent multiple calls if $tmp is already set test "$tmp" && return 0 : "${TMPDIR=/tmp}" # shellcheck disable=SC2039,SC3028 { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } dummy=$tmp/dummy case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in ,,) echo "int x;" > "$dummy.c" for driver in cc gcc c89 c99 ; do if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then CC_FOR_BUILD=$driver break fi done if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac } # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if test -f /.attbin/uname ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case $UNAME_SYSTEM in Linux|GNU|GNU/*) LIBC=unknown set_cc_for_build cat <<-EOF > "$dummy.c" #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #elif defined(__GLIBC__) LIBC=gnu #else #include /* First heuristic to detect musl libc. */ #ifdef __DEFINED_va_list LIBC=musl #endif #endif EOF cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` eval "$cc_set_libc" # Second heuristic to detect musl libc. if [ "$LIBC" = unknown ] && command -v ldd >/dev/null && ldd --version 2>&1 | grep -q ^musl; then LIBC=musl fi # If the system lacks a compiler, then just pick glibc. # We could probably try harder. if [ "$LIBC" = unknown ]; then LIBC=gnu fi ;; esac # Note: order is significant - the case branches are not exclusive. case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ echo unknown)` case $UNAME_MACHINE_ARCH in aarch64eb) machine=aarch64_be-unknown ;; armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; earmv*) arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` machine=${arch}${endian}-unknown ;; *) machine=$UNAME_MACHINE_ARCH-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. case $UNAME_MACHINE_ARCH in earm*) os=netbsdelf ;; arm*|i386|m68k|ns32k|sh3*|sparc|vax) set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # Determine ABI tags. case $UNAME_MACHINE_ARCH in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case $UNAME_VERSION in Debian*) release='-gnu' ;; *) release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. GUESS=$machine-${os}${release}${abi-} ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE ;; *:SecBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE ;; *:MidnightBSD:*:*) GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE ;; *:ekkoBSD:*:*) GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE ;; *:SolidBSD:*:*) GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE ;; *:OS108:*:*) GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE ;; macppc:MirBSD:*:*) GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE ;; *:MirBSD:*:*) GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE ;; *:Sortix:*:*) GUESS=$UNAME_MACHINE-unknown-sortix ;; *:Twizzler:*:*) GUESS=$UNAME_MACHINE-unknown-twizzler ;; *:Redox:*:*) GUESS=$UNAME_MACHINE-unknown-redox ;; mips:OSF1:*.*) GUESS=mips-dec-osf1 ;; alpha:OSF1:*:*) # Reset EXIT trap before exiting to avoid spurious non-zero exit code. trap '' 0 case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case $ALPHA_CPU_TYPE in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") UNAME_MACHINE=alpha ;; "LCA4 (21066/21068)") UNAME_MACHINE=alpha ;; "EV5 (21164)") UNAME_MACHINE=alphaev5 ;; "EV5.6 (21164A)") UNAME_MACHINE=alphaev56 ;; "EV5.6 (21164PC)") UNAME_MACHINE=alphapca56 ;; "EV5.7 (21164PC)") UNAME_MACHINE=alphapca57 ;; "EV6 (21264)") UNAME_MACHINE=alphaev6 ;; "EV6.7 (21264A)") UNAME_MACHINE=alphaev67 ;; "EV6.8CB (21264C)") UNAME_MACHINE=alphaev68 ;; "EV6.8AL (21264B)") UNAME_MACHINE=alphaev68 ;; "EV6.8CX (21264D)") UNAME_MACHINE=alphaev68 ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE=alphaev69 ;; "EV7 (21364)") UNAME_MACHINE=alphaev7 ;; "EV7.9 (21364A)") UNAME_MACHINE=alphaev79 ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` GUESS=$UNAME_MACHINE-dec-osf$OSF_REL ;; Amiga*:UNIX_System_V:4.0:*) GUESS=m68k-unknown-sysv4 ;; *:[Aa]miga[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-amigaos ;; *:[Mm]orph[Oo][Ss]:*:*) GUESS=$UNAME_MACHINE-unknown-morphos ;; *:OS/390:*:*) GUESS=i370-ibm-openedition ;; *:z/VM:*:*) GUESS=s390-ibm-zvmoe ;; *:OS400:*:*) GUESS=powerpc-ibm-os400 ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) GUESS=arm-acorn-riscix$UNAME_RELEASE ;; arm*:riscos:*:*|arm*:RISCOS:*:*) GUESS=arm-unknown-riscos ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) GUESS=hppa1.1-hitachi-hiuxmpp ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. case `(/bin/universe) 2>/dev/null` in att) GUESS=pyramid-pyramid-sysv3 ;; *) GUESS=pyramid-pyramid-bsd ;; esac ;; NILE*:*:*:dcosx) GUESS=pyramid-pyramid-svr4 ;; DRS?6000:unix:4.0:6*) GUESS=sparc-icl-nx6 ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) GUESS=sparc-icl-nx7 ;; esac ;; s390x:SunOS:*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL ;; sun4H:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-hal-solaris2$SUN_REL ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris2$SUN_REL ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) GUESS=i386-pc-auroraux$UNAME_RELEASE ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) set_cc_for_build SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=$SUN_ARCH-pc-solaris2$SUN_REL ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=sparc-sun-solaris3$SUN_REL ;; sun4*:SunOS:*:*) case `/usr/bin/arch -k` in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` GUESS=sparc-sun-sunos$SUN_REL ;; sun3*:SunOS:*:*) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 case `/bin/arch` in sun3) GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun4) GUESS=sparc-sun-sunos$UNAME_RELEASE ;; esac ;; aushp:SunOS:*:*) GUESS=sparc-auspex-sunos$UNAME_RELEASE ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) GUESS=m68k-atari-mint$UNAME_RELEASE ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) GUESS=m68k-milan-mint$UNAME_RELEASE ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) GUESS=m68k-hades-mint$UNAME_RELEASE ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) GUESS=m68k-unknown-mint$UNAME_RELEASE ;; m68k:machten:*:*) GUESS=m68k-apple-machten$UNAME_RELEASE ;; powerpc:machten:*:*) GUESS=powerpc-apple-machten$UNAME_RELEASE ;; RISC*:Mach:*:*) GUESS=mips-dec-mach_bsd4.3 ;; RISC*:ULTRIX:*:*) GUESS=mips-dec-ultrix$UNAME_RELEASE ;; VAX*:ULTRIX*:*:*) GUESS=vax-dec-ultrix$UNAME_RELEASE ;; 2020:CLIX:*:* | 2430:CLIX:*:*) GUESS=clipper-intergraph-clix$UNAME_RELEASE ;; mips:*:*:UMIPS | mips:*:*:RISCos) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`"$dummy" "$dummyarg"` && { echo "$SYSTEM_NAME"; exit; } GUESS=mips-mips-riscos$UNAME_RELEASE ;; Motorola:PowerMAX_OS:*:*) GUESS=powerpc-motorola-powermax ;; Motorola:*:4.3:PL8-*) GUESS=powerpc-harris-powermax ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) GUESS=powerpc-harris-powermax ;; Night_Hawk:Power_UNIX:*:*) GUESS=powerpc-harris-powerunix ;; m88k:CX/UX:7*:*) GUESS=m88k-harris-cxux7 ;; m88k:*:4*:R4*) GUESS=m88k-motorola-sysv4 ;; m88k:*:3*:R3*) GUESS=m88k-motorola-sysv3 ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 then if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ test "$TARGET_BINARY_INTERFACE"x = x then GUESS=m88k-dg-dgux$UNAME_RELEASE else GUESS=m88k-dg-dguxbcs$UNAME_RELEASE fi else GUESS=i586-dg-dgux$UNAME_RELEASE fi ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) GUESS=m88k-dolphin-sysv3 ;; M88*:*:R3*:*) # Delta 88k system running SVR3 GUESS=m88k-motorola-sysv3 ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) GUESS=m88k-tektronix-sysv3 ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) GUESS=m68k-tektronix-bsd ;; *:IRIX*:*:*) IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` GUESS=mips-sgi-irix$IRIX_REL ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) GUESS=i386-ibm-aix ;; ia64:AIX:*:*) if test -x /usr/bin/oslevel ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` then GUESS=$SYSTEM_NAME else GUESS=rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then GUESS=rs6000-ibm-aix3.2.4 else GUESS=rs6000-ibm-aix3.2 fi ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if test -x /usr/bin/lslpp ; then IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` else IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi GUESS=$IBM_ARCH-ibm-aix$IBM_REV ;; *:AIX:*:*) GUESS=rs6000-ibm-aix ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) GUESS=romp-ibm-bsd4.4 ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) GUESS=rs6000-bull-bosx ;; DPX/2?00:B.O.S.:*:*) GUESS=m68k-bull-sysv3 ;; 9000/[34]??:4.3bsd:1.*:*) GUESS=m68k-hp-bsd ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) GUESS=m68k-hp-bsd4.4 ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` case $UNAME_MACHINE in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if test -x /usr/bin/getconf; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case $sc_cpu_version in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case $sc_kernel_bits in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi if test "$HP_ARCH" = ""; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if test "$HP_ARCH" = hppa2.0w then set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH=hppa2.0w else HP_ARCH=hppa64 fi fi GUESS=$HP_ARCH-hp-hpux$HPUX_REV ;; ia64:HP-UX:*:*) HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` GUESS=ia64-hp-hpux$HPUX_REV ;; 3050*:HI-UX:*:*) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } GUESS=unknown-hitachi-hiuxwe2 ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) GUESS=hppa1.1-hp-bsd ;; 9000/8??:4.3bsd:*:*) GUESS=hppa1.0-hp-bsd ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) GUESS=hppa1.0-hp-mpeix ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) GUESS=hppa1.1-hp-osf ;; hp8??:OSF1:*:*) GUESS=hppa1.0-hp-osf ;; i*86:OSF1:*:*) if test -x /usr/sbin/sysversion ; then GUESS=$UNAME_MACHINE-unknown-osf1mk else GUESS=$UNAME_MACHINE-unknown-osf1 fi ;; parisc*:Lites*:*:*) GUESS=hppa1.1-hp-lites ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) GUESS=c1-convex-bsd ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) GUESS=c34-convex-bsd ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) GUESS=c38-convex-bsd ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) GUESS=c4-convex-bsd ;; CRAY*Y-MP:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=ymp-cray-unicos$CRAY_REL ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=t90-cray-unicos$CRAY_REL ;; CRAY*T3E:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=alphaev5-cray-unicosmk$CRAY_REL ;; CRAY*SV1:*:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=sv1-cray-unicos$CRAY_REL ;; *:UNICOS/mp:*:*) CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` GUESS=craynv-cray-unicosmp$CRAY_REL ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE ;; sparc*:BSD/OS:*:*) GUESS=sparc-unknown-bsdi$UNAME_RELEASE ;; *:BSD/OS:*:*) GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE ;; arm:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` set_cc_for_build if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi else FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf fi ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`/usr/bin/uname -p` case $UNAME_PROCESSOR in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL ;; i*:CYGWIN*:*) GUESS=$UNAME_MACHINE-pc-cygwin ;; *:MINGW64*:*) GUESS=$UNAME_MACHINE-pc-mingw64 ;; *:MINGW*:*) GUESS=$UNAME_MACHINE-pc-mingw32 ;; *:MSYS*:*) GUESS=$UNAME_MACHINE-pc-msys ;; i*:PW*:*) GUESS=$UNAME_MACHINE-pc-pw32 ;; *:SerenityOS:*:*) GUESS=$UNAME_MACHINE-pc-serenity ;; *:Interix*:*) case $UNAME_MACHINE in x86) GUESS=i586-pc-interix$UNAME_RELEASE ;; authenticamd | genuineintel | EM64T) GUESS=x86_64-unknown-interix$UNAME_RELEASE ;; IA64) GUESS=ia64-unknown-interix$UNAME_RELEASE ;; esac ;; i*:UWIN*:*) GUESS=$UNAME_MACHINE-pc-uwin ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) GUESS=x86_64-pc-cygwin ;; prep*:SunOS:5.*:*) SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` GUESS=powerpcle-unknown-solaris2$SUN_REL ;; *:GNU:*:*) # the GNU system GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL ;; *:GNU/*:*:*) # other systems with GNU libc and userland GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC ;; *:Minix:*:*) GUESS=$UNAME_MACHINE-unknown-minix ;; aarch64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then GUESS=$UNAME_MACHINE-unknown-linux-$LIBC else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi else GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf fi fi ;; avr32*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; cris:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; crisv32:Linux:*:*) GUESS=$UNAME_MACHINE-axis-linux-$LIBC ;; e2k:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; frv:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; hexagon:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:Linux:*:*) GUESS=$UNAME_MACHINE-pc-linux-$LIBC ;; ia64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; k1om:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m32r*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; m68*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; mips:Linux:*:* | mips64:Linux:*:*) set_cc_for_build IS_GLIBC=0 test x"${LIBC}" = xgnu && IS_GLIBC=1 sed 's/^ //' << EOF > "$dummy.c" #undef CPU #undef mips #undef mipsel #undef mips64 #undef mips64el #if ${IS_GLIBC} && defined(_ABI64) LIBCABI=gnuabi64 #else #if ${IS_GLIBC} && defined(_ABIN32) LIBCABI=gnuabin32 #else LIBCABI=${LIBC} #endif #endif #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa64r6 #else #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa32r6 #else #if defined(__mips64) CPU=mips64 #else CPU=mips #endif #endif #endif #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) MIPS_ENDIAN=el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) MIPS_ENDIAN= #else MIPS_ENDIAN= #endif #endif EOF cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` eval "$cc_set_vars" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; openrisc*:Linux:*:*) GUESS=or1k-unknown-linux-$LIBC ;; or32:Linux:*:* | or1k*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; padre:Linux:*:*) GUESS=sparc-unknown-linux-$LIBC ;; parisc64:Linux:*:* | hppa64:Linux:*:*) GUESS=hppa64-unknown-linux-$LIBC ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; *) GUESS=hppa-unknown-linux-$LIBC ;; esac ;; ppc64:Linux:*:*) GUESS=powerpc64-unknown-linux-$LIBC ;; ppc:Linux:*:*) GUESS=powerpc-unknown-linux-$LIBC ;; ppc64le:Linux:*:*) GUESS=powerpc64le-unknown-linux-$LIBC ;; ppcle:Linux:*:*) GUESS=powerpcle-unknown-linux-$LIBC ;; riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; s390:Linux:*:* | s390x:Linux:*:*) GUESS=$UNAME_MACHINE-ibm-linux-$LIBC ;; sh64*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sh*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; sparc:Linux:*:* | sparc64:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; tile*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; vax:Linux:*:*) GUESS=$UNAME_MACHINE-dec-linux-$LIBC ;; x86_64:Linux:*:*) set_cc_for_build LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_X32 >/dev/null then LIBCABI=${LIBC}x32 fi fi GUESS=$UNAME_MACHINE-pc-linux-$LIBCABI ;; xtensa*:Linux:*:*) GUESS=$UNAME_MACHINE-unknown-linux-$LIBC ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. GUESS=i386-sequent-sysv4 ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. GUESS=$UNAME_MACHINE-pc-os2-emx ;; i*86:XTS-300:*:STOP) GUESS=$UNAME_MACHINE-unknown-stop ;; i*86:atheos:*:*) GUESS=$UNAME_MACHINE-unknown-atheos ;; i*86:syllable:*:*) GUESS=$UNAME_MACHINE-pc-syllable ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) GUESS=i386-unknown-lynxos$UNAME_RELEASE ;; i*86:*DOS:*:*) GUESS=$UNAME_MACHINE-pc-msdosdjgpp ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL fi ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL else GUESS=$UNAME_MACHINE-pc-sysv32 fi ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. GUESS=i586-pc-msdosdjgpp ;; Intel:Mach:3*:*) GUESS=i386-pc-mach3 ;; paragon:*:*:*) GUESS=i860-intel-osf1 ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 fi ;; mini*:CTIX:SYS*5:*) # "miniframe" GUESS=m68010-convergent-sysv ;; mc68k:UNIX:SYSTEM5:3.51m) GUESS=m68k-convergent-sysv ;; M680?0:D-NIX:5.3:*) GUESS=m68k-diab-dnix ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) GUESS=m68k-unknown-lynxos$UNAME_RELEASE ;; mc68030:UNIX_System_V:4.*:*) GUESS=m68k-atari-sysv4 ;; TSUNAMI:LynxOS:2.*:*) GUESS=sparc-unknown-lynxos$UNAME_RELEASE ;; rs6000:LynxOS:2.*:*) GUESS=rs6000-unknown-lynxos$UNAME_RELEASE ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) GUESS=powerpc-unknown-lynxos$UNAME_RELEASE ;; SM[BE]S:UNIX_SV:*:*) GUESS=mips-dde-sysv$UNAME_RELEASE ;; RM*:ReliantUNIX-*:*:*) GUESS=mips-sni-sysv4 ;; RM*:SINIX-*:*:*) GUESS=mips-sni-sysv4 ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` GUESS=$UNAME_MACHINE-sni-sysv4 else GUESS=ns32k-sni-sysv fi ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says GUESS=i586-unisys-sysv4 ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm GUESS=hppa1.1-stratus-sysv4 ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. GUESS=i860-stratus-sysv4 ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. GUESS=$UNAME_MACHINE-stratus-vos ;; *:VOS:*:*) # From Paul.Green@stratus.com. GUESS=hppa1.1-stratus-vos ;; mc68*:A/UX:*:*) GUESS=m68k-apple-aux$UNAME_RELEASE ;; news*:NEWS-OS:6*:*) GUESS=mips-sony-newsos6 ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if test -d /usr/nec; then GUESS=mips-nec-sysv$UNAME_RELEASE else GUESS=mips-unknown-sysv$UNAME_RELEASE fi ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. GUESS=powerpc-be-beos ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. GUESS=powerpc-apple-beos ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. GUESS=i586-pc-beos ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. GUESS=i586-pc-haiku ;; x86_64:Haiku:*:*) GUESS=x86_64-unknown-haiku ;; SX-4:SUPER-UX:*:*) GUESS=sx4-nec-superux$UNAME_RELEASE ;; SX-5:SUPER-UX:*:*) GUESS=sx5-nec-superux$UNAME_RELEASE ;; SX-6:SUPER-UX:*:*) GUESS=sx6-nec-superux$UNAME_RELEASE ;; SX-7:SUPER-UX:*:*) GUESS=sx7-nec-superux$UNAME_RELEASE ;; SX-8:SUPER-UX:*:*) GUESS=sx8-nec-superux$UNAME_RELEASE ;; SX-8R:SUPER-UX:*:*) GUESS=sx8r-nec-superux$UNAME_RELEASE ;; SX-ACE:SUPER-UX:*:*) GUESS=sxace-nec-superux$UNAME_RELEASE ;; Power*:Rhapsody:*:*) GUESS=powerpc-apple-rhapsody$UNAME_RELEASE ;; *:Rhapsody:*:*) GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE ;; arm64:Darwin:*:*) GUESS=aarch64-apple-darwin$UNAME_RELEASE ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac if command -v xcode-select > /dev/null 2> /dev/null && \ ! xcode-select --print-path > /dev/null 2> /dev/null ; then # Avoid executing cc if there is no toolchain installed as # cc will be a stub that puts up a graphical alert # prompting the user to install developer tools. CC_FOR_BUILD=no_compiler_found else set_cc_for_build fi if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_PPC >/dev/null then UNAME_PROCESSOR=powerpc fi elif test "$UNAME_PROCESSOR" = i386 ; then # uname -m returns i386 or x86_64 UNAME_PROCESSOR=$UNAME_MACHINE fi GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE ;; *:QNX:*:4*) GUESS=i386-pc-qnx ;; NEO-*:NONSTOP_KERNEL:*:*) GUESS=neo-tandem-nsk$UNAME_RELEASE ;; NSE-*:NONSTOP_KERNEL:*:*) GUESS=nse-tandem-nsk$UNAME_RELEASE ;; NSR-*:NONSTOP_KERNEL:*:*) GUESS=nsr-tandem-nsk$UNAME_RELEASE ;; NSV-*:NONSTOP_KERNEL:*:*) GUESS=nsv-tandem-nsk$UNAME_RELEASE ;; NSX-*:NONSTOP_KERNEL:*:*) GUESS=nsx-tandem-nsk$UNAME_RELEASE ;; *:NonStop-UX:*:*) GUESS=mips-compaq-nonstopux ;; BS2000:POSIX*:*:*) GUESS=bs2000-siemens-sysv ;; DS/*:UNIX_System_V:*:*) GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "${cputype-}" = 386; then UNAME_MACHINE=i386 elif test "x${cputype-}" != x; then UNAME_MACHINE=$cputype fi GUESS=$UNAME_MACHINE-unknown-plan9 ;; *:TOPS-10:*:*) GUESS=pdp10-unknown-tops10 ;; *:TENEX:*:*) GUESS=pdp10-unknown-tenex ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) GUESS=pdp10-dec-tops20 ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) GUESS=pdp10-xkl-tops20 ;; *:TOPS-20:*:*) GUESS=pdp10-unknown-tops20 ;; *:ITS:*:*) GUESS=pdp10-unknown-its ;; SEI:*:*:SEIUX) GUESS=mips-sei-seiux$UNAME_RELEASE ;; *:DragonFly:*:*) DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case $UNAME_MACHINE in A*) GUESS=alpha-dec-vms ;; I*) GUESS=ia64-dec-vms ;; V*) GUESS=vax-dec-vms ;; esac ;; *:XENIX:*:SysV) GUESS=i386-pc-xenix ;; i*86:skyos:*:*) SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL ;; i*86:rdos:*:*) GUESS=$UNAME_MACHINE-pc-rdos ;; i*86:Fiwix:*:*) GUESS=$UNAME_MACHINE-pc-fiwix ;; *:AROS:*:*) GUESS=$UNAME_MACHINE-unknown-aros ;; x86_64:VMkernel:*:*) GUESS=$UNAME_MACHINE-unknown-esx ;; amd64:Isilon\ OneFS:*:*) GUESS=x86_64-unknown-onefs ;; *:Unleashed:*:*) GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE ;; esac # Do we have a guess based on uname results? if test "x$GUESS" != x; then echo "$GUESS" exit fi # No uname command or uname output not recognized. set_cc_for_build cat > "$dummy.c" < #include #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #include #if defined(_SIZE_T_) || defined(SIGLOST) #include #endif #endif #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) #if !defined (ultrix) #include #if defined (BSD) #if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); #else #if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); #else printf ("vax-dec-bsd\n"); exit (0); #endif #endif #else printf ("vax-dec-bsd\n"); exit (0); #endif #else #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname un; uname (&un); printf ("vax-dec-ultrix%s\n", un.release); exit (0); #else printf ("vax-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname *un; uname (&un); printf ("mips-dec-ultrix%s\n", un.release); exit (0); #else printf ("mips-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } echo "$0: unable to guess system type" >&2 case $UNAME_MACHINE:$UNAME_SYSTEM in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 <&2 </dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = "$UNAME_MACHINE" UNAME_RELEASE = "$UNAME_RELEASE" UNAME_SYSTEM = "$UNAME_SYSTEM" UNAME_VERSION = "$UNAME_VERSION" EOF fi exit 1 # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: ffmpegfs-2.50/config/config.sub0000755000175000017500000010511614553310161012151 #! /bin/sh # Configuration validation subroutine script. # Copyright 1992-2022 Free Software Foundation, Inc. # shellcheck disable=SC2006,SC2268 # see below for rationale timestamp='2022-01-03' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # Please send patches to . # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.sub # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. # The "shellcheck disable" line above the timestamp inhibits complaints # about features and limitations of the classic Bourne shell that were # superseded or lifted in POSIX. However, this script identifies a wide # variety of pre-POSIX systems that do not have POSIX shells at all, and # even some reasonably current systems (Solaris 10 as case-in-point) still # have a pre-POSIX /bin/sh. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS Canonicalize a configuration name. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright 1992-2022 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; *local*) # First pass through any local machine types. echo "$1" exit ;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Split fields of configuration type # shellcheck disable=SC2162 saved_IFS=$IFS IFS="-" read field1 field2 field3 field4 <&2 exit 1 ;; *-*-*-*) basic_machine=$field1-$field2 basic_os=$field3-$field4 ;; *-*-*) # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two # parts maybe_os=$field2-$field3 case $maybe_os in nto-qnx* | linux-* | uclinux-uclibc* \ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ | storm-chaos* | os2-emx* | rtmk-nova*) basic_machine=$field1 basic_os=$maybe_os ;; android-linux) basic_machine=$field1-unknown basic_os=linux-android ;; *) basic_machine=$field1-$field2 basic_os=$field3 ;; esac ;; *-*) # A lone config we happen to match not fitting any pattern case $field1-$field2 in decstation-3100) basic_machine=mips-dec basic_os= ;; *-*) # Second component is usually, but not always the OS case $field2 in # Prevent following clause from handling this valid os sun*os*) basic_machine=$field1 basic_os=$field2 ;; zephyr*) basic_machine=$field1-unknown basic_os=$field2 ;; # Manufacturers dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \ | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \ | unicom* | ibm* | next | hp | isi* | apollo | altos* \ | convergent* | ncr* | news | 32* | 3600* | 3100* \ | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \ | ultra | tti* | harris | dolphin | highlevel | gould \ | cbm | ns | masscomp | apple | axis | knuth | cray \ | microblaze* | sim | cisco \ | oki | wec | wrs | winbond) basic_machine=$field1-$field2 basic_os= ;; *) basic_machine=$field1 basic_os=$field2 ;; esac ;; esac ;; *) # Convert single-component short-hands not valid as part of # multi-component configurations. case $field1 in 386bsd) basic_machine=i386-pc basic_os=bsd ;; a29khif) basic_machine=a29k-amd basic_os=udi ;; adobe68k) basic_machine=m68010-adobe basic_os=scout ;; alliant) basic_machine=fx80-alliant basic_os= ;; altos | altos3068) basic_machine=m68k-altos basic_os= ;; am29k) basic_machine=a29k-none basic_os=bsd ;; amdahl) basic_machine=580-amdahl basic_os=sysv ;; amiga) basic_machine=m68k-unknown basic_os= ;; amigaos | amigados) basic_machine=m68k-unknown basic_os=amigaos ;; amigaunix | amix) basic_machine=m68k-unknown basic_os=sysv4 ;; apollo68) basic_machine=m68k-apollo basic_os=sysv ;; apollo68bsd) basic_machine=m68k-apollo basic_os=bsd ;; aros) basic_machine=i386-pc basic_os=aros ;; aux) basic_machine=m68k-apple basic_os=aux ;; balance) basic_machine=ns32k-sequent basic_os=dynix ;; blackfin) basic_machine=bfin-unknown basic_os=linux ;; cegcc) basic_machine=arm-unknown basic_os=cegcc ;; convex-c1) basic_machine=c1-convex basic_os=bsd ;; convex-c2) basic_machine=c2-convex basic_os=bsd ;; convex-c32) basic_machine=c32-convex basic_os=bsd ;; convex-c34) basic_machine=c34-convex basic_os=bsd ;; convex-c38) basic_machine=c38-convex basic_os=bsd ;; cray) basic_machine=j90-cray basic_os=unicos ;; crds | unos) basic_machine=m68k-crds basic_os= ;; da30) basic_machine=m68k-da30 basic_os= ;; decstation | pmax | pmin | dec3100 | decstatn) basic_machine=mips-dec basic_os= ;; delta88) basic_machine=m88k-motorola basic_os=sysv3 ;; dicos) basic_machine=i686-pc basic_os=dicos ;; djgpp) basic_machine=i586-pc basic_os=msdosdjgpp ;; ebmon29k) basic_machine=a29k-amd basic_os=ebmon ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson basic_os=ose ;; gmicro) basic_machine=tron-gmicro basic_os=sysv ;; go32) basic_machine=i386-pc basic_os=go32 ;; h8300hms) basic_machine=h8300-hitachi basic_os=hms ;; h8300xray) basic_machine=h8300-hitachi basic_os=xray ;; h8500hms) basic_machine=h8500-hitachi basic_os=hms ;; harris) basic_machine=m88k-harris basic_os=sysv3 ;; hp300 | hp300hpux) basic_machine=m68k-hp basic_os=hpux ;; hp300bsd) basic_machine=m68k-hp basic_os=bsd ;; hppaosf) basic_machine=hppa1.1-hp basic_os=osf ;; hppro) basic_machine=hppa1.1-hp basic_os=proelf ;; i386mach) basic_machine=i386-mach basic_os=mach ;; isi68 | isi) basic_machine=m68k-isi basic_os=sysv ;; m68knommu) basic_machine=m68k-unknown basic_os=linux ;; magnum | m3230) basic_machine=mips-mips basic_os=sysv ;; merlin) basic_machine=ns32k-utek basic_os=sysv ;; mingw64) basic_machine=x86_64-pc basic_os=mingw64 ;; mingw32) basic_machine=i686-pc basic_os=mingw32 ;; mingw32ce) basic_machine=arm-unknown basic_os=mingw32ce ;; monitor) basic_machine=m68k-rom68k basic_os=coff ;; morphos) basic_machine=powerpc-unknown basic_os=morphos ;; moxiebox) basic_machine=moxie-unknown basic_os=moxiebox ;; msdos) basic_machine=i386-pc basic_os=msdos ;; msys) basic_machine=i686-pc basic_os=msys ;; mvs) basic_machine=i370-ibm basic_os=mvs ;; nacl) basic_machine=le32-unknown basic_os=nacl ;; ncr3000) basic_machine=i486-ncr basic_os=sysv4 ;; netbsd386) basic_machine=i386-pc basic_os=netbsd ;; netwinder) basic_machine=armv4l-rebel basic_os=linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony basic_os=newsos ;; news1000) basic_machine=m68030-sony basic_os=newsos ;; necv70) basic_machine=v70-nec basic_os=sysv ;; nh3000) basic_machine=m68k-harris basic_os=cxux ;; nh[45]000) basic_machine=m88k-harris basic_os=cxux ;; nindy960) basic_machine=i960-intel basic_os=nindy ;; mon960) basic_machine=i960-intel basic_os=mon960 ;; nonstopux) basic_machine=mips-compaq basic_os=nonstopux ;; os400) basic_machine=powerpc-ibm basic_os=os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson basic_os=ose ;; os68k) basic_machine=m68k-none basic_os=os68k ;; paragon) basic_machine=i860-intel basic_os=osf ;; parisc) basic_machine=hppa-unknown basic_os=linux ;; psp) basic_machine=mipsallegrexel-sony basic_os=psp ;; pw32) basic_machine=i586-unknown basic_os=pw32 ;; rdos | rdos64) basic_machine=x86_64-pc basic_os=rdos ;; rdos32) basic_machine=i386-pc basic_os=rdos ;; rom68k) basic_machine=m68k-rom68k basic_os=coff ;; sa29200) basic_machine=a29k-amd basic_os=udi ;; sei) basic_machine=mips-sei basic_os=seiux ;; sequent) basic_machine=i386-sequent basic_os= ;; sps7) basic_machine=m68k-bull basic_os=sysv2 ;; st2000) basic_machine=m68k-tandem basic_os= ;; stratus) basic_machine=i860-stratus basic_os=sysv4 ;; sun2) basic_machine=m68000-sun basic_os= ;; sun2os3) basic_machine=m68000-sun basic_os=sunos3 ;; sun2os4) basic_machine=m68000-sun basic_os=sunos4 ;; sun3) basic_machine=m68k-sun basic_os= ;; sun3os3) basic_machine=m68k-sun basic_os=sunos3 ;; sun3os4) basic_machine=m68k-sun basic_os=sunos4 ;; sun4) basic_machine=sparc-sun basic_os= ;; sun4os3) basic_machine=sparc-sun basic_os=sunos3 ;; sun4os4) basic_machine=sparc-sun basic_os=sunos4 ;; sun4sol2) basic_machine=sparc-sun basic_os=solaris2 ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun basic_os= ;; sv1) basic_machine=sv1-cray basic_os=unicos ;; symmetry) basic_machine=i386-sequent basic_os=dynix ;; t3e) basic_machine=alphaev5-cray basic_os=unicos ;; t90) basic_machine=t90-cray basic_os=unicos ;; toad1) basic_machine=pdp10-xkl basic_os=tops20 ;; tpf) basic_machine=s390x-ibm basic_os=tpf ;; udi29k) basic_machine=a29k-amd basic_os=udi ;; ultra3) basic_machine=a29k-nyu basic_os=sym1 ;; v810 | necv810) basic_machine=v810-nec basic_os=none ;; vaxv) basic_machine=vax-dec basic_os=sysv ;; vms) basic_machine=vax-dec basic_os=vms ;; vsta) basic_machine=i386-pc basic_os=vsta ;; vxworks960) basic_machine=i960-wrs basic_os=vxworks ;; vxworks68) basic_machine=m68k-wrs basic_os=vxworks ;; vxworks29k) basic_machine=a29k-wrs basic_os=vxworks ;; xbox) basic_machine=i686-pc basic_os=mingw32 ;; ymp) basic_machine=ymp-cray basic_os=unicos ;; *) basic_machine=$1 basic_os= ;; esac ;; esac # Decode 1-component or ad-hoc basic machines case $basic_machine in # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) cpu=hppa1.1 vendor=winbond ;; op50n) cpu=hppa1.1 vendor=oki ;; op60c) cpu=hppa1.1 vendor=oki ;; ibm*) cpu=i370 vendor=ibm ;; orion105) cpu=clipper vendor=highlevel ;; mac | mpw | mac-mpw) cpu=m68k vendor=apple ;; pmac | pmac-mpw) cpu=powerpc vendor=apple ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) cpu=m68000 vendor=att ;; 3b*) cpu=we32k vendor=att ;; bluegene*) cpu=powerpc vendor=ibm basic_os=cnk ;; decsystem10* | dec10*) cpu=pdp10 vendor=dec basic_os=tops10 ;; decsystem20* | dec20*) cpu=pdp10 vendor=dec basic_os=tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) cpu=m68k vendor=motorola ;; dpx2*) cpu=m68k vendor=bull basic_os=sysv3 ;; encore | umax | mmax) cpu=ns32k vendor=encore ;; elxsi) cpu=elxsi vendor=elxsi basic_os=${basic_os:-bsd} ;; fx2800) cpu=i860 vendor=alliant ;; genix) cpu=ns32k vendor=ns ;; h3050r* | hiux*) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) cpu=m68000 vendor=hp ;; hp9k3[2-9][0-9]) cpu=m68k vendor=hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) cpu=hppa1.1 vendor=hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; i*86v32) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv32 ;; i*86v4*) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv4 ;; i*86v) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=sysv ;; i*86sol2) cpu=`echo "$1" | sed -e 's/86.*/86/'` vendor=pc basic_os=solaris2 ;; j90 | j90-cray) cpu=j90 vendor=cray basic_os=${basic_os:-unicos} ;; iris | iris4d) cpu=mips vendor=sgi case $basic_os in irix*) ;; *) basic_os=irix4 ;; esac ;; miniframe) cpu=m68000 vendor=convergent ;; *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) cpu=m68k vendor=atari basic_os=mint ;; news-3600 | risc-news) cpu=mips vendor=sony basic_os=newsos ;; next | m*-next) cpu=m68k vendor=next case $basic_os in openstep*) ;; nextstep*) ;; ns2*) basic_os=nextstep2 ;; *) basic_os=nextstep3 ;; esac ;; np1) cpu=np1 vendor=gould ;; op50n-* | op60c-*) cpu=hppa1.1 vendor=oki basic_os=proelf ;; pa-hitachi) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; pbd) cpu=sparc vendor=tti ;; pbb) cpu=m68k vendor=tti ;; pc532) cpu=ns32k vendor=pc532 ;; pn) cpu=pn vendor=gould ;; power) cpu=power vendor=ibm ;; ps2) cpu=i386 vendor=ibm ;; rm[46]00) cpu=mips vendor=siemens ;; rtpc | rtpc-*) cpu=romp vendor=ibm ;; sde) cpu=mipsisa32 vendor=sde basic_os=${basic_os:-elf} ;; simso-wrs) cpu=sparclite vendor=wrs basic_os=vxworks ;; tower | tower-32) cpu=m68k vendor=ncr ;; vpp*|vx|vx-*) cpu=f301 vendor=fujitsu ;; w65) cpu=w65 vendor=wdc ;; w89k-*) cpu=hppa1.1 vendor=winbond basic_os=proelf ;; none) cpu=none vendor=none ;; leon|leon[3-9]) cpu=sparc vendor=$basic_machine ;; leon-*|leon[3-9]-*) cpu=sparc vendor=`echo "$basic_machine" | sed 's/-.*//'` ;; *-*) # shellcheck disable=SC2162 saved_IFS=$IFS IFS="-" read cpu vendor <&2 exit 1 ;; esac ;; esac # Here we canonicalize certain aliases for manufacturers. case $vendor in digital*) vendor=dec ;; commodore*) vendor=cbm ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if test x$basic_os != x then # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just # set os. case $basic_os in gnu/linux*) kernel=linux os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'` ;; os2-emx) kernel=os2 os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'` ;; nto-qnx*) kernel=nto os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` ;; *-*) # shellcheck disable=SC2162 saved_IFS=$IFS IFS="-" read kernel os <&2 exit 1 ;; esac # As a final step for OS-related things, validate the OS-kernel combination # (given a valid OS), if there is a kernel. case $kernel-$os in linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ | linux-musl* | linux-relibc* | linux-uclibc* ) ;; uclinux-uclibc* ) ;; -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) # These are just libc implementations, not actual OSes, and thus # require a kernel. echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 exit 1 ;; kfreebsd*-gnu* | kopensolaris*-gnu*) ;; vxworks-simlinux | vxworks-simwindows | vxworks-spe) ;; nto-qnx*) ;; os2-emx) ;; *-eabi* | *-gnueabi*) ;; -*) # Blank kernel with real OS is always fine. ;; *-*) echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 exit 1 ;; esac # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. case $vendor in unknown) case $cpu-$os in *-riscix*) vendor=acorn ;; *-sunos*) vendor=sun ;; *-cnk* | *-aix*) vendor=ibm ;; *-beos*) vendor=be ;; *-hpux*) vendor=hp ;; *-mpeix*) vendor=hp ;; *-hiux*) vendor=hitachi ;; *-unos*) vendor=crds ;; *-dgux*) vendor=dg ;; *-luna*) vendor=omron ;; *-genix*) vendor=ns ;; *-clix*) vendor=intergraph ;; *-mvs* | *-opened*) vendor=ibm ;; *-os400*) vendor=ibm ;; s390-* | s390x-*) vendor=ibm ;; *-ptx*) vendor=sequent ;; *-tpf*) vendor=ibm ;; *-vxsim* | *-vxworks* | *-windiss*) vendor=wrs ;; *-aux*) vendor=apple ;; *-hms*) vendor=hitachi ;; *-mpw* | *-macos*) vendor=apple ;; *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) vendor=atari ;; *-vos*) vendor=stratus ;; esac ;; esac echo "$cpu-$vendor-${kernel:+$kernel-}$os" exit # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: ffmpegfs-2.50/config/test-driver0000755000175000017500000001141714553310161012364 #! /bin/sh # test-driver - basic testsuite driver script. scriptversion=2018-03-07.03; # UTC # Copyright (C) 2011-2021 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . # Make unconditional expansion of undefined variables an error. This # helps a lot in preventing typo-related bugs. set -u usage_error () { echo "$0: $*" >&2 print_usage >&2 exit 2 } print_usage () { cat <"$log_file" "$@" >>"$log_file" 2>&1 estatus=$? if test $enable_hard_errors = no && test $estatus -eq 99; then tweaked_estatus=1 else tweaked_estatus=$estatus fi case $tweaked_estatus:$expect_failure in 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; 0:*) col=$grn res=PASS recheck=no gcopy=no;; 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; *:*) col=$red res=FAIL recheck=yes gcopy=yes;; esac # Report the test outcome and exit status in the logs, so that one can # know whether the test passed or failed simply by looking at the '.log' # file, without the need of also peaking into the corresponding '.trs' # file (automake bug#11814). echo "$res $test_name (exit status: $estatus)" >>"$log_file" # Report outcome to console. echo "${col}${res}${std}: $test_name" # Register the test result, and other relevant metadata. echo ":test-result: $res" > $trs_file echo ":global-test-result: $res" >> $trs_file echo ":recheck: $recheck" >> $trs_file echo ":copy-in-global-log: $gcopy" >> $trs_file # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ffmpegfs-2.50/config/compile0000755000175000017500000001635014553310161011545 #! /bin/sh # Wrapper for compilers which do not understand '-c -o'. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2021 Free Software Foundation, Inc. # Written by Tom Tromey . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . nl=' ' # We need space, tab and new line, in precisely that order. Quoting is # there to prevent tools from complaining about whitespace usage. IFS=" "" $nl" file_conv= # func_file_conv build_file lazy # Convert a $build file to $host form and store it in $file # Currently only supports Windows hosts. If the determined conversion # type is listed in (the comma separated) LAZY, no conversion will # take place. func_file_conv () { file=$1 case $file in / | /[!/]*) # absolute file, and not a UNC file if test -z "$file_conv"; then # lazily determine how to convert abs files case `uname -s` in MINGW*) file_conv=mingw ;; CYGWIN* | MSYS*) file_conv=cygwin ;; *) file_conv=wine ;; esac fi case $file_conv/,$2, in *,$file_conv,*) ;; mingw/*) file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` ;; cygwin/* | msys/*) file=`cygpath -m "$file" || echo "$file"` ;; wine/*) file=`winepath -w "$file" || echo "$file"` ;; esac ;; esac } # func_cl_dashL linkdir # Make cl look for libraries in LINKDIR func_cl_dashL () { func_file_conv "$1" if test -z "$lib_path"; then lib_path=$file else lib_path="$lib_path;$file" fi linker_opts="$linker_opts -LIBPATH:$file" } # func_cl_dashl library # Do a library search-path lookup for cl func_cl_dashl () { lib=$1 found=no save_IFS=$IFS IFS=';' for dir in $lib_path $LIB do IFS=$save_IFS if $shared && test -f "$dir/$lib.dll.lib"; then found=yes lib=$dir/$lib.dll.lib break fi if test -f "$dir/$lib.lib"; then found=yes lib=$dir/$lib.lib break fi if test -f "$dir/lib$lib.a"; then found=yes lib=$dir/lib$lib.a break fi done IFS=$save_IFS if test "$found" != yes; then lib=$lib.lib fi } # func_cl_wrapper cl arg... # Adjust compile command to suit cl func_cl_wrapper () { # Assume a capable shell lib_path= shared=: linker_opts= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. eat=1 case $2 in *.o | *.[oO][bB][jJ]) func_file_conv "$2" set x "$@" -Fo"$file" shift ;; *) func_file_conv "$2" set x "$@" -Fe"$file" shift ;; esac ;; -I) eat=1 func_file_conv "$2" mingw set x "$@" -I"$file" shift ;; -I*) func_file_conv "${1#-I}" mingw set x "$@" -I"$file" shift ;; -l) eat=1 func_cl_dashl "$2" set x "$@" "$lib" shift ;; -l*) func_cl_dashl "${1#-l}" set x "$@" "$lib" shift ;; -L) eat=1 func_cl_dashL "$2" ;; -L*) func_cl_dashL "${1#-L}" ;; -static) shared=false ;; -Wl,*) arg=${1#-Wl,} save_ifs="$IFS"; IFS=',' for flag in $arg; do IFS="$save_ifs" linker_opts="$linker_opts $flag" done IFS="$save_ifs" ;; -Xlinker) eat=1 linker_opts="$linker_opts $2" ;; -*) set x "$@" "$1" shift ;; *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) func_file_conv "$1" set x "$@" -Tp"$file" shift ;; *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) func_file_conv "$1" mingw set x "$@" "$file" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -n "$linker_opts"; then linker_opts="-link$linker_opts" fi exec "$@" $linker_opts exit 1 } eat= case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: compile [--help] [--version] PROGRAM [ARGS] Wrapper for compilers which do not understand '-c -o'. Remove '-o dest.o' from ARGS, run PROGRAM with the remaining arguments, and rename the output as expected. If you are trying to build a whole package this is not the right script to run: please start by reading the file 'INSTALL'. Report bugs to . EOF exit $? ;; -v | --v*) echo "compile $scriptversion" exit $? ;; cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) func_cl_wrapper "$@" # Doesn't return... ;; esac ofile= cfile= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. # So we strip '-o arg' only if arg is an object. eat=1 case $2 in *.o | *.obj) ofile=$2 ;; *) set x "$@" -o "$2" shift ;; esac ;; *.c) cfile=$1 set x "$@" "$1" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -z "$ofile" || test -z "$cfile"; then # If no '-o' option was seen then we might have been invoked from a # pattern rule where we don't need one. That is ok -- this is a # normal compilation that the losing compiler can handle. If no # '.c' file was seen then we are probably linking. That is also # ok. exec "$@" fi # Name of file we expect compiler to create. cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` # Create the lock directory. # Note: use '[/\\:.-]' here to ensure that we don't use the same name # that we are using for the .o file. Also, base the name on the expected # object file name, since that is what matters with a parallel build. lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d while true; do if mkdir "$lockdir" >/dev/null 2>&1; then break fi sleep 1 done # FIXME: race condition here if user kills between mkdir and trap. trap "rmdir '$lockdir'; exit 1" 1 2 15 # Run the compile. "$@" ret=$? if test -f "$cofile"; then test "$cofile" = "$ofile" || mv "$cofile" "$ofile" elif test -f "${cofile}bj"; then test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" fi rmdir "$lockdir" exit $ret # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ffmpegfs-2.50/config/missing0000755000175000017500000001533614553310161011571 #! /bin/sh # Common wrapper for a few potentially missing GNU programs. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1996-2021 Free Software Foundation, Inc. # Originally written by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try '$0 --help' for more information" exit 1 fi case $1 in --is-lightweight) # Used by our autoconf macros to check whether the available missing # script is modern enough. exit 0 ;; --run) # Back-compat with the calling convention used by older automake. shift ;; -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due to PROGRAM being missing or too old. Options: -h, --help display this help and exit -v, --version output version information and exit Supported PROGRAM values: aclocal autoconf autoheader autom4te automake makeinfo bison yacc flex lex help2man Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and 'g' are ignored when checking the name. Send bug reports to ." exit $? ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing $scriptversion (GNU Automake)" exit $? ;; -*) echo 1>&2 "$0: unknown '$1' option" echo 1>&2 "Try '$0 --help' for more information" exit 1 ;; esac # Run the given program, remember its exit status. "$@"; st=$? # If it succeeded, we are done. test $st -eq 0 && exit 0 # Also exit now if we it failed (or wasn't found), and '--version' was # passed; such an option is passed most likely to detect whether the # program is present and works. case $2 in --version|--help) exit $st;; esac # Exit code 63 means version mismatch. This often happens when the user # tries to use an ancient version of a tool on a file that requires a # minimum version. if test $st -eq 63; then msg="probably too old" elif test $st -eq 127; then # Program was missing. msg="missing on your system" else # Program was found and executed, but failed. Give up. exit $st fi perl_URL=https://www.perl.org/ flex_URL=https://github.com/westes/flex gnu_software_URL=https://www.gnu.org/software program_details () { case $1 in aclocal|automake) echo "The '$1' program is part of the GNU Automake package:" echo "<$gnu_software_URL/automake>" echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/autoconf>" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; autoconf|autom4te|autoheader) echo "The '$1' program is part of the GNU Autoconf package:" echo "<$gnu_software_URL/autoconf/>" echo "It also requires GNU m4 and Perl in order to run:" echo "<$gnu_software_URL/m4/>" echo "<$perl_URL>" ;; esac } give_advice () { # Normalize program name to check for. normalized_program=`echo "$1" | sed ' s/^gnu-//; t s/^gnu//; t s/^g//; t'` printf '%s\n' "'$1' is $msg." configure_deps="'configure.ac' or m4 files included by 'configure.ac'" case $normalized_program in autoconf*) echo "You should only need it if you modified 'configure.ac'," echo "or m4 files included by it." program_details 'autoconf' ;; autoheader*) echo "You should only need it if you modified 'acconfig.h' or" echo "$configure_deps." program_details 'autoheader' ;; automake*) echo "You should only need it if you modified 'Makefile.am' or" echo "$configure_deps." program_details 'automake' ;; aclocal*) echo "You should only need it if you modified 'acinclude.m4' or" echo "$configure_deps." program_details 'aclocal' ;; autom4te*) echo "You might have modified some maintainer files that require" echo "the 'autom4te' program to be rebuilt." program_details 'autom4te' ;; bison*|yacc*) echo "You should only need it if you modified a '.y' file." echo "You may want to install the GNU Bison package:" echo "<$gnu_software_URL/bison/>" ;; lex*|flex*) echo "You should only need it if you modified a '.l' file." echo "You may want to install the Fast Lexical Analyzer package:" echo "<$flex_URL>" ;; help2man*) echo "You should only need it if you modified a dependency" \ "of a man page." echo "You may want to install the GNU Help2man package:" echo "<$gnu_software_URL/help2man/>" ;; makeinfo*) echo "You should only need it if you modified a '.texi' file, or" echo "any other file indirectly affecting the aspect of the manual." echo "You might want to install the Texinfo package:" echo "<$gnu_software_URL/texinfo/>" echo "The spurious makeinfo call might also be the consequence of" echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" echo "want to install GNU make:" echo "<$gnu_software_URL/make/>" ;; *) echo "You might have modified some files without having the proper" echo "tools for further handling them. Check the 'README' file, it" echo "often tells you about the needed prerequisites for installing" echo "this package. You may also peek at any GNU archive site, in" echo "case some other package contains this missing '$1' program." ;; esac } give_advice "$1" | sed -e '1s/^/WARNING: /' \ -e '2,$s/^/ /' >&2 # Propagate the correct exit status (expected to be 127 for a program # not found, 63 for a program that failed due to version mismatch). exit $st # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: ffmpegfs-2.50/COPYING0000644000175000017500000010451515052412650007757 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ffmpegfs-2.50/ffmpegfs.10000664000175000017500000011076015215723160010606 '\" t .\" Title: ffmpegfs .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets vsnapshot .\" Date: Juni 2026 .\" Manual: User Commands .\" Source: ffmpegfs 2.50 .\" Language: English .\" .TH "FFMPEGFS" "1" "Juni 2026" "ffmpegfs 2\&.50" "User Commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" ffmpegfs \- mounts and transcodes a multitude of formats to one of the target formats on the fly\&. .SH "SYNOPSIS" .sp \fBffmpegfs\fR [\fIOPTION\fR]\&... \fIIN_DIR\fR \fIOUT_DIR\fR .SH "DESCRIPTION" .sp The ffmpegfs(1) command will mount the directory \fIIN_DIR\fR on \fIOUT_DIR\fR\&. Thereafter, accessing \fIOUT_DIR\fR will show the contents of \fIIN_DIR\fR, with all supported media files transparently renamed and transcoded to one of the supported target formats upon access\&. .sp Supported output formats: .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt. T{ .sp \fBFormat\fR T}:T{ .sp \fBDescription\fR T}:T{ .sp \fBAudio\fR T}:T{ .sp \fBVideo\fR T} T{ .sp AIFF T}:T{ .sp Audio Interchange File Format T}:T{ .sp T}:T{ .sp PCM 16 bit BE T} T{ .sp ALAC T}:T{ .sp Apple Lossless Audio Codec T}:T{ .sp T}:T{ .sp ALAC T} T{ .sp FLAC T}:T{ .sp Free Lossless Audio T}:T{ .sp T}:T{ .sp FLAC T} T{ .sp HLS T}:T{ .sp HTTP Live Streaming T}:T{ .sp H264 T}:T{ .sp AAC T} T{ .sp MOV T}:T{ .sp QuickTime File Format T}:T{ .sp H264 T}:T{ .sp AAC T} T{ .sp MP3 T}:T{ .sp MPEG\-2 Audio Layer III T}:T{ .sp T}:T{ .sp MP3 T} T{ .sp MP4 T}:T{ .sp MPEG\-4 T}:T{ .sp H264 T}:T{ .sp AAC T} T{ .sp OGG T}:T{ .sp T}:T{ .sp Theora T}:T{ .sp Vorbis T} T{ .sp MKV T}:T{ .sp Matroska T}:T{ .sp H264 T}:T{ .sp AAC T} T{ .sp Opus T}:T{ .sp T}:T{ .sp Opus T}:T{ .sp T} T{ .sp ProRes T}:T{ .sp Apple ProRes T}:T{ .sp ProRes T}:T{ .sp PCM 16 bit LE T} T{ .sp TS T}:T{ .sp MPEG Transport Stream T}:T{ .sp H264 T}:T{ .sp AAC T} T{ .sp WAV T}:T{ .sp Waveform Audio File Format T}:T{ .sp T}:T{ .sp PCM 16 bit LE T} T{ .sp WebM T}:T{ .sp T}:T{ .sp VP9 T}:T{ .sp Opus T} T{ .sp BMP T}:T{ .sp Video to frameset T}:T{ .sp T}:T{ .sp BMP T} T{ .sp JPG T}:T{ .sp Video to frameset T}:T{ .sp T}:T{ .sp JPEG T} T{ .sp PNG T}:T{ .sp Video to frameset T}:T{ .sp T}:T{ .sp PNG T} .TE .sp 1 .SH "OPTIONS" .sp Usage: ffmpegfs [OPTION]\&... IN_DIR OUT_DIR .sp Mount IN_DIR on OUT_DIR, converting audio and video files upon access\&. .SS "Encoding options" .PP \fB\-\-desttype\fR=TYPE, \fB\-odesttype\fR=TYPE .RS 4 Select the destination format\&. \fITYPE\fR can currently be: .sp \fBAIFF\fR, \fBALAC\fR, \fBBMP\fR, \fBFLAC\fR, \fBHLS\fR, \fBJPG\fR, \fBMOV\fR, \fBMP3\fR, \fBMP4\fR, \fBMKV\fR, \fBOGG\fR, \fBOpus\fR, \fBPNG\fR, \fBProRes\fR, \fBTS\fR, \fBWAV\fR, \fBWebM\fR\&. .sp To stream videos, \fBMP4\fR, \fBTS\fR, \fBHLS\fR, \fBOGG\fR, \fBWEBM\fR, \fBMKV\fR, or \fBMOV\fR/\fBPRORES\fR must be selected\&. .sp To use HTTP Live Streaming, set \fBHLS\fR\&. .sp When a destination \fBJPG\fR, \fBPNG\fR, or \fBBMP\fR is chosen, all frames of a video source file will be presented in a virtual directory named after the source file\&. Audio will not be available\&. .sp To use the smart transcoding feature, specify a video and audio file type, separated by a "+" sign\&. For example, \-\-desttype=mov+aiff will convert video files to Apple Quicktime MOV and audio\-only files to AIFF\&. .sp Defaults to: \fBmp4\fR .RE .PP \fB\-\-audiocodec\fR=TYPE, \fB\-oaudiocodec\fR=TYPE .RS 4 Select an audio codec\&. \fITYPE\fR depends on the destination format and can currently be: .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt. T{ \fBFormats\fR T}:T{ \fBAudio Codecs\fR T} T{ MP4 T}:T{ \fBAAC\fR, MP3 T} T{ WebM T}:T{ \fBOPUS\fR, VORBIS T} T{ MOV T}:T{ \fBAAC\fR, AC3, MP3 T} T{ MKV T}:T{ \fBAAC\fR, AC3, MP3 T} T{ TS, HLS T}:T{ \fBAAC\fR, AC3, MP3 T} .TE .sp 1 Other destination formats do not support other codecs than the default\&. .sp Defaults to: The destination format\(cqs default setting, as indicated by the first codec name in the list\&. .RE .PP \fB\-\-videocodec\fR=TYPE, \fB\-ovideocodec\fR=TYPE .RS 4 Select a video codec\&. \fITYPE\fR depends on the destination format and can currently be: .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt. T{ \fBFormats\fR T}:T{ \fBVideo Codecs\fR T} T{ MP4 T}:T{ \fBH264\fR, H265, MPEG1, MPEG2 T} T{ WebM T}:T{ \fBVP9\fR, VP8, AV1 T} T{ MOV T}:T{ \fBH264\fR, H265, MPEG1, MPEG2 T} T{ MKV T}:T{ \fBH264\fR, H265, MPEG1, MPEG2 T} T{ TS, HLS T}:T{ \fBH264\fR, H265, MPEG1, MPEG2 T} .TE .sp 1 Other destination formats do not support other codecs than the default\&. .sp Defaults to: The destination format\(cqs default setting, as indicated by the first codec name in the list\&. .RE .PP \fB\-\-autocopy\fR=OPTION, \fB\-oautocopy\fR=OPTION .RS 4 Select the auto copy option\&. \fIOPTION\fR can be: .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt. T{ \fBOFF\fR T}:T{ Never copy streams, transcode always\&. T} T{ \fBMATCH\fR T}:T{ Copy stream if target supports codec\&. T} T{ \fBMATCHLIMIT\fR T}:T{ Same as MATCH, only copy if target not larger, transcode otherwise\&. T} T{ \fBSTRICT\fR T}:T{ Copy stream if codec matches desired target, transcode otherwise\&. T} T{ \fBSTRICTLIMIT\fR T}:T{ Same as STRICT, only copy if target not larger, transcode otherwise\&. T} .TE .sp 1 This can speed up transcoding significantly as copying streams uses much less computing power as compared to transcoding\&. .sp \fBMATCH\fR copies a stream if the target supports it, e\&.g\&., an AAC audio stream will be copied to MPEG, although FFmpeg\(cqs target format is MP3 for this container\&. H264 would be copied to ProRes, although the result would be a regular MOV or MP4, not a ProRes file\&. .sp \fBSTRICT\fR would convert AAC to MP3 for MPEG or H264 to ProRes for Prores files to strictly adhere to the output format setting\&. This will create homogenous results which might prevent problems with picky playback software\&. .sp Note: When the \fB\-\-audiocodec\fR or \fB\-\-videocodec\fR option is specified, the STRICT option should be used to ensure that the chosen output codec is used in any scenario\&. MATCH would enable copy if the output format supports the input codec\&. .RE .sp Defaults to: \fBOFF\fR .PP \fB\-\-recodesame\fR=OPTION, \fB\-orecodesame\fR=OPTION .RS 4 Select recode to the same format option, \fIOPTION\fR can be: .TS allbox tab(:); lt lt lt lt. T{ \fBNO\fR T}:T{ Never recode to the same format\&. T} T{ \fBYES\fR T}:T{ Always recode to the same format\&. T} .TE .sp 1 Defaults to: \fBNO\fR .RE .PP \fB\-\-profile\fR=NAME, \fB\-oprofile\fR=NAME .RS 4 Set profile for target audience, \fINAME\fR can be: .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt. T{ \fBNONE\fR T}:T{ no profile T} T{ \fBFF\fR T}:T{ optimise for Firefox T} T{ \fBEDGE\fR T}:T{ optimise for MS Edge and Internet Explorer > 11 T} T{ \fBIE\fR T}:T{ optimise for MS Edge and Internet Explorer ⇐ 11 T} T{ \fBCHROME\fR T}:T{ Google Chrome T} T{ \fBSAFARI\fR T}:T{ Apple Safari T} T{ \fBOPERA\fR T}:T{ Opera T} T{ \fBMAXTHON\fR T}:T{ Maxthon T} .TE .sp 1 \fBNote:\fR applies to the MP4 output format only, and is ignored for all other formats\&. .sp Defaults to: \fBNONE\fR .RE .PP \-\-\fBlevel\fR=NAME, \-o \fBlevel\fR=NAME .RS 4 Set level for output if available\&. \fINAME\fR can be: .TS allbox tab(:); lt lt lt lt lt lt lt lt. T{ \fBPROXY\fR T}:T{ Proxy \(en apco T} T{ \fBLT\fR T}:T{ LT \(en apcs T} T{ \fBSTANDARD\fR T}:T{ standard \(en apcn T} T{ \fBHQ\fR T}:T{ HQ \- apch T} .TE .sp 1 \fBNote:\fR applies to the MP4 output format only, and is ignored for all other formats\&. .sp Defaults to: \fBHQ\fR .RE .PP \fB\-\-include_extensions\fR=LIST, \fB\-oinclude_extensions\fR=LIST .RS 4 Set the list of file extensions to be encoded\&. \fILIST\fR can have one or more entries that are separated by commas\&. These are the only file extensions that will be transcoded\&. Can be specified numerous times and will be merged, which is required when specifying them in the fstab because commas cannot be used to separate the extensions\&. The entries support shell wildcard patterns\&. .sp Example: \-\-include_extensions=mp4,wmv to encode MPEG\-4 and Windows Media files only\&. .sp Defaults to: Encode all supported files\&. .RE .PP \fB\-\-hide_extensions\fR=LIST, \fB\-ohide_extensions\fR=LIST .RS 4 Set a list of file extensions to exclude from the output\&. \fILIST\fR can have one or more entries that are separated by commas\&. Can be specified numerous times and will be merged, which is required when specifying them in the fstab because commas cannot be used to separate the extensions\&. The entries support shell wildcard patterns\&. .sp Example: \-\-hide_extensions=jpg,png,cue to stop covers and cue sheets from showing up\&. .sp Defaults to: Show all files\&. .RE .SS "Audio Options" .PP \fB\-\-audiobitrate\fR=BITRATE, \fB\-o audiobitrate\fR=BITRATE .RS 4 Select the audio encoding bitrate\&. .sp Defaults to: \fB128 kbit\fR .sp \fBAcceptable values for \fR\fB\fIBITRATE\fR\fR\fB:\fR .sp \fBmp4:\fR 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 288, 320, 352, 384, 416, and 448 kbps\&. .sp \fBmp3:\fR For sampling frequencies of 32, 44\&.1, and 48 kHz, \fIBITRATE\fR can be among 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, and 320 kbps\&. .sp For sampling frequencies of 16, 22\&.05, and 24 kHz, \fIBITRATE\fR can be among 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, and 160 kbps\&. .sp When in doubt, it is recommended to choose a bitrate among 96, 112, 128, 160, 192, 224, 256, and 320 kbps\&. .PP \fBBITRATE\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} n bit/s: # or #bps .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} n kbit/s: #K or #Kbps .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} n Mbit/s: #M or #Mbps .RE .RE .RE .PP \fB\-\-audiosamplerate\fR=SAMPLERATE, \fB\-o audiosamplerate\fR=SAMPLERATE .RS 4 This limits the output sample rate to \fISAMPLERATE\fR\&. If the source file sample rate is higher, it will be downsampled automatically\&. .sp Typical values are 8000, 11025, 22050, 44100, 48000, 96000, and 192000\&. .sp If the target codec does not support the selected sample rate, the next matching rate will be chosen (e\&.g\&. if 24K is selected but only 22\&.05 or 44\&.1 KHz is supported, 22\&.05 KHz will be set)\&. .sp Set to 0 to keep the source rate\&. .sp Defaults to: \fB44\&.1 kHz\fR .PP \fBSAMPLERATE\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In Hz: # or #Hz .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In kHz: #K or #KHz .RE .RE .RE .PP \fB\-\-audiochannels\fR=CHANNELS, \fB\-o audiochannels\fR=CHANNELS .RS 4 This limits the number of output channels to \fICHANNELS\fR\&. If the source has more channels, the number will be reduced to this limit\&. .sp Typical values are 1, 2 or 6 (e\&.g\&., 5\&.1) channels\&. .sp If the target codec does not support the selected number of channels, transcoding may fail\&. .sp Set to 0 to keep the number of channels\&. .sp Defaults to: \fB2 channels (stereo)\fR .RE .PP \fB\-\-audiosamplefmt\fR=SAMPLEFMT, \fB\-o audiosamplefmt\fR=SAMPLEFMT .RS 4 This sets a sample format\&. \fISAMPLEFMT\fR can be: .sp 0 to use the predefined setting; 8, 16, 32, 64 for integer format, F16, F32, F64 for floating point\&. .sp Not all formats are supported by all destination types\&. Selecting an invalid format will be reported as a command line error and a list of values printed\&. .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt. T{ \fBContainer Format\fR T}:T{ \fBSample Format\fR T} T{ \fBAIFF\fR T}:T{ 0, 16, 32 T} T{ \fBALAC\fR T}:T{ 0, 16, 24 T} T{ \fBWAV\fR T}:T{ 0, 8, 16, 32, 64, F16, F32, F64 T} T{ \fBFLAC\fR T}:T{ 0, 16, 24 T} .TE .sp 1 Defaults to: 0 (Use the same as the source or the predefined format of the destination if the source format is not possible\&.) .RE .SS "Video Options" .PP \fB\-\-videobitrate\fR=BITRATE, \fB\-o videobitrate\fR=BITRATE .RS 4 This sets the video encoding bit rate\&. Setting this too high or too low may cause transcoding to fail\&. .sp Defaults to: \fB2 Mbit\fR .sp \fBmp4:\fR May be specified as 500 to 25,000 kbps\&. .PP \fBBITRATE\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} n bit/s: # or #bps .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} n kbit/s: #K or #Kbps .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} n Mbit/s: #M or #Mbps .RE .RE .RE .PP \fB\-\-videoheight\fR=HEIGHT, \-o \fBvideoheight\fR=HEIGHT .RS 4 This sets the height of the transcoded video\&. .sp When the video is rescaled, the aspect ratio is preserved if \-\-width is not set at the same time\&. .sp Defaults to: \fBkeep source video height\fR .RE .PP \fB\-\-videowidth\fR=WIDTH, \-o \fBvideowidth\fR=WIDTH .RS 4 This sets the width of the transcoded video\&. .sp When the video is rescaled, the aspect ratio is preserved if \-\-height is not set at the same time\&. .sp Defaults to: \fBkeep source video width\fR .RE .PP \fB\-\-deinterlace\fR, \-o \fBdeinterlace\fR .RS 4 Deinterlace video if necessary while transcoding\&. .sp This may need a higher bit rate, but this will increase picture quality when streaming via HTML5\&. .sp Defaults to: "no deinterlace" .RE .SS "HLS Options" .PP \fB\-\-segment_duration\fR, \-o \fBsegment_duration\fR .RS 4 Set the duration of one video segment of the HLS stream\&. This argument is a floating point value, e\&.g\&., it can be set to 2\&.5 for 2500 milliseconds\&. .sp Should normally be left as the default\&. .sp \fBNote:\fR This applies to the HLS output format only, and is ignored for all other formats\&. .sp Defaults to: \fB10 seconds\fR .RE .PP \fB\-\-min_seek_time_diff\fR, \-o \fBmin_seek_time_diff\fR .RS 4 If the requested HLS segment is less than min_seek_time seconds away, discard the seek request\&. The segment will be available very soon anyway, and that makes a re\-transcode necessary\&. Set to 0 to disable\&. .sp Should normally be left as the default\&. .sp \fBNote:\fR This applies to the HLS output format only, and is ignored for all other formats\&. .sp Defaults to: \fB30 seconds\fR .RE .SS "Hardware Acceleration Options" .PP \fB\-\-hwaccel_enc\fR=API, \fB\-o hwaccel_enc\fR=API .RS 4 Select the hardware acceleration API for encoding\&. .sp Defaults to: \fBNONE\fR (no acceleration)\&. .PP \fBAPI\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBNONE\fR: use software encoder .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBVAAPI\fR: Video Acceleration API (VA\-API) .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBOMX\fR: OpenMAX (Open Media Acceleration) .RE .RE .RE .PP \fB\-\-hwaccel_dec_blocked\fR=CODEC[:PROFILE[:PROFILE]], \fB\-o hwaccel_dec_blocked\fR=CODEC:[:PROFILE[:PROFILE]] .RS 4 Block a codec and, optionally, a profile for hardware decoding\&. The option can be repeated to block several codecs\&. .sp Defaults to: no codecs blocked\&. .PP \fBCODEC\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBH263\fR: H\&.263 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBH264\fR: H\&.264 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBHEVC\fR: H\&.265 / HEVC .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBMPEG2\fR: MPEG\-2 video .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBMPEG4\fR: MPEG\-4 video .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBVC1\fR: SMPTE VC\-1 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBVP8\fR: Google VP9 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBVP9\fR: Google VP9 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBWMV3\fR: Windows Media Video 9 .RE .RE .RE .PP \fBPROFILE\fR .RS 4 can optionally be added to block a certain profile from the codec only\&. .sp Example: VP9:0 blocks Google VP profile 0\&. .sp Example: H264:1:33 blocks H\&.264 profile 1 and 33\&. .RE .PP \fB\-\-hwaccel_enc_device\fR=DEVICE, \-o \fBhwaccel_enc_device\fR=DEVICE .RS 4 Select the hardware acceleration device\&. May be required for VAAPI, especially if more than one device is available\&. .sp \fBNote:\fR This only applies to VAAPI hardware acceleration; all other types are ignored\&. .sp Defaults to: \fBempty\fR (use default device)\&. .sp Example: \fB/dev/dri/renderD128\fR .RE .PP \fB\-\-hwaccel_dec\fR=API, \fB\-o hwaccel_dec\fR=API .RS 4 Select the hardware acceleration API for decoding\&. .sp Defaults to: \fBNONE\fR (no acceleration) .PP \fBAPI\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBNONE\fR: use software decoder .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBVAAPI\fR: Video Acceleration API (VA\-API) .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBMMAL\fR: Multimedia Abstraction Layer by Broadcom .RE .RE .RE .PP \fB\-\-hwaccel_dec_device\fR=DEVICE, \-o \fBhwaccel_dec_device\fR=DEVICE .RS 4 Select the hardware acceleration device\&. May be required for VAAPI, especially if more than one device is available\&. .sp \fBNote:\fR This only applies to VAAPI hardware acceleration; all other types are ignored\&. .sp Defaults to: \fBempty\fR (use default device) .sp Example: \fB/dev/dri/renderD128\fR .RE .SS "Album Arts" .PP \-\-\fBnoalbumarts\fR, \-o \fBnoalbumarts\fR .RS 4 Do not copy album art into the output file\&. .sp This will reduce the file size and may be useful when streaming via HTML5 when album art is not used anyway\&. .sp Defaults to: \fBadd album arts\fR .RE .SS "Virtual Script" .PP \-\-\fBenablescript\fR, \-o \fBenablescript\fR .RS 4 Add a virtual index\&.php to every directory\&. It reads scripts/videotag\&.php from the FFmpegfs binary directory\&. .sp This can be very handy for testing video playback\&. Of course, feel free to replace videotag\&.php with your own script\&. .sp Defaults to: \fBDo not generate script file\fR .RE .PP \-\-\fBscriptfile\fR, \-o \fBscriptfile\fR .RS 4 Set the name of the virtual script created in each directory\&. .sp Defaults to: \fBindex\&.php\fR .RE .PP \-\-\fBscriptsource\fR, \-o \fBscriptsource\fR .RS 4 Use a different source file\&. .sp Defaults to: \fBscripts/videotag\&.php\fR .RE .SS "Cache Options" .PP \fB\-\-expiry_time\fR=TIME, \fB\-o expiry_time\fR=TIME .RS 4 Cache entries expire after \fITIME\fR and will be deleted to save disc space\&. .sp Defaults to: \fB1 week\fR .RE .PP \fB\-\-max_inactive_suspend\fR=TIME, \fB\-o max_inactive_suspend\fR=TIME .RS 4 While being accessed, the file is transcoded to the target format in the background\&. When the client quits, transcoding will continue until this time out\&. Transcoding is suspended until it is accessed again, then transcoding will continue\&. .sp Defaults to: \fB15 seconds\fR .RE .PP \fB\-\-max_inactive_abort\fR=TIME, \fB\-o max_inactive_abort\fR=TIME .RS 4 While being accessed, the file is transcoded in the background to the target format\&. When the client quits, transcoding will continue until this time out, then the transcoder thread quits\&. .sp Defaults to: \fB30 seconds\fR .RE .PP \fB\-\-prebuffer_time\fR=TIME, \fB\-o prebuffer_time\fR=TIME .RS 4 Files will be decoded until the buffer contains the specified playing time, allowing playback to start smoothly without lags\&. Both options must be met if prebuffer time and prebuffer size are specified\&. .sp Set to 0 to disable pre\-buffering\&. .sp Defaults to: \fBno prebuffer time\fR .RE .PP \fB\-\-prebuffer_size\fR=SIZE, \fB\-o prebuffer_size\fR=SIZE .RS 4 Files will be decoded until the specified number of bytes is present in the buffer, allowing playback to start smoothly without lags\&. Both options must be met if prebuffer size and prebuffer time are specified\&. .sp Set to 0 to disable pre\-buffering\&. .sp Defaults to: \fB100 KB\fR .RE .PP \fB\-\-max_cache_size\fR=SIZE, \fB\-o max_cache_size\fR=SIZE .RS 4 Set the maximum diskspace used by the cache\&. If the cache grows beyond this limit when a file is transcoded, old entries will be deleted to keep the cache within the size limit\&. .sp Defaults to: \fBunlimited\fR .RE .PP \fB\-\-min_diskspace\fR=SIZE, \fB\-o min_diskspace\fR=SIZE .RS 4 Set the required diskspace on the cachepath mount\&. If the remaining space falls below \fISIZE\fR when a file is transcoded, old entries will be deleted to keep the diskspace within the limit\&. .sp Defaults to: \fB0 (no minimum space)\fR .RE .PP \fB\-\-cachepath\fR=DIR, \fB\-o cachepath\fR=DIR .RS 4 Sets the disc cache directory to \fIDIR\fR\&. If it does not already exist, it will be created\&. The user running FFmpegfs must have write access to the location\&. .sp Defaults to: \fB${XDG_CACHE_HOME:\-~/\&.cache}/ffmpegfs\fR (as specified in the XDG Base Directory Specification)\&. Falls back to ${HOME:\-~/\&.cache}/ffmpegfs if not defined\&. If executed with root privileges, "/var/cache/ffmpegfs" will be used\&. .RE .PP \fB\-\-disable_cache\fR, \-o \fBdisable_cache\fR .RS 4 Disable the cache functionality completely\&. .sp Defaults to: \fBenabled\fR .RE .PP \fB\-\-cache_maintenance\fR=TIME, \fB\-o cache_maintenance\fR=TIME .RS 4 Starts cache maintenance in \fITIME\fR intervals\&. This will enforce the expery_time, max_cache_size and min_diskspace settings\&. Do not set it too low as this can slow down transcoding\&. .sp Only one FFmpegfs process will do the maintenance by becoming the master\&. If that process exits, another will take over, so that one will always do the maintenance\&. .sp Defaults to: \fB1 hour\fR .RE .PP \fB\-\-prune_cache\fR .RS 4 Prune the cache immediately according to the above settings at application start up\&. .sp Defaults to: \fBDo not prune cache\fR .RE .PP \fB\-\-clear_cache\fR, \fB\-o clear_cache\fR .RS 4 On startup, clear the cache\&. All previously transcoded files will be deleted\&. .PP \fBTIME\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Seconds: # .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Minutes: #m .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Hours: #h .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Days: #d .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Weeks: #w .RE .RE .PP \fBSIZE\fR .RS 4 can be defined as\&... .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In bytes: # or #B .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In KBytes: #K or #KB .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In MBytes: #M or #MB .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In GBytes: #G or #GB .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In TBytes: #T or #TB .RE .RE .RE .SS "Other" .PP \fB\-\-max_threads\fR=COUNT, \fB\-o max_threads\fR=COUNT .RS 4 Limit concurrent transcoder threads\&. Set to 0 for unlimited threads\&. Recommended values are up to 16 times the number of CPU cores\&. Should be left as the default\&. .sp Defaults to: \fB16 times number of detected cpu cores\fR .RE .PP \fB\-\-decoding_errors\fR, \fB\-o decoding_errors\fR .RS 4 Decoding errors are normally ignored, leaving bloopers and hiccups in encoded audio or video but still creating a valid file\&. When this option is set, transcoding will stop with an error\&. .sp Defaults to: \fBIgnore errors\fR .RE .PP \fB\-\-min_dvd_chapter_duration\fR=SECONDS, \fB\-o min_dvd_chapter_duration\fR=SECONDS .RS 4 This ignores DVD chapters shorter than SECONDS\&. To disable, set to 0\&. This avoids transcoding errors for DVD chapters too short to detect its streams\&. .sp Defaults to: \fB1 second\fR .RE .PP \fB\-\-win_smb_fix\fR, \fB\-o win_smb_fix\fR .RS 4 Windows seems to access the files on Samba drives starting at the last 64K segment when the file is opened\&. Setting \-\-win_smb_fix=1 will ignore these attempts (not decode the file up to this point)\&. .sp Defaults to: \fBon\fR .RE .SS "Logging" .PP \fB\-\-log_maxlevel\fR=LEVEL, \fB\-o log_maxlevel\fR=LEVEL .RS 4 Maximum level of messages to log, either ERROR, WARNING, INFO, DEBUG or TRACE\&. Defaults to INFO and is always set to DEBUG in debug mode\&. .sp Note that the other log flags must also be set to enable logging\&. .RE .PP \fB\-\-log_stderr\fR, \fB\-o log_stderr\fR .RS 4 Enable outputting logging messages to stderr\&. Automatically enabled in debug mode\&. .RE .PP \fB\-\-log_syslog\fR, \fB\-o log_syslog\fR .RS 4 Enable outputting logging messages to syslog\&. .RE .PP \fB\-\-logfile\fR=FILE, \fB\-o logfile\fR=FILE .RS 4 File to output log messages to\&. By default, no file will be written\&. .RE .SS "General/FUSE options" .PP \fB\-d\fR, \fB\-o debug\fR .RS 4 Enable debug output\&. This will result in a large quantity of diagnostic information being printed to stderr as the programme runs\&. It implies \fB\-f\fR\&. .RE .PP \fB\-f\fR .RS 4 Run in the foreground instead of detaching from the terminal\&. .RE .PP \fB\-h\fR, \fB\-\-help\fR .RS 4 Print usage information\&. .RE .PP \fB\-V\fR, \fB\-\-version\fR .RS 4 Output version information\&. .RE .PP \fB\-c\fR, \fB\-\-capabilities\fR .RS 4 Output FFmpeg capabilities: a list of the system\(cqs available codecs\&. .RE .PP \fB\-s\fR .RS 4 Force single\-threaded operation\&. .RE .SH "USAGE" .sp Mount your file system as follows: .sp .if n \{\ .RS 4 .\} .nf ffmpegfs [\-\-audiobitrate bitrate] [\-\-videobitrate bitrate] musicdir mountpoint [\-o fuse_options] .fi .if n \{\ .RE .\} .sp To use FFmpegfs as a daemon and encode to MPEG\-4, for instance: .sp .if n \{\ .RS 4 .\} .nf ffmpegfs \-\-audiobitrate=256K \-\-videobitrate=1\&.5M /mnt/music /mnt/ffmpegfs \-o allow_other,ro,desttype=mp4 .fi .if n \{\ .RE .\} .sp This will run FFmpegfs in the foreground and print the log output to the screen: .sp .if n \{\ .RS 4 .\} .nf ffmpegfs \-f \-\-log_stderr \-\-audiobitrate=256K \-\-videobitrate=1\&.5M \-\-audiobitrate=256K \-\-videobitrate=1\&.5M /mnt/music /mnt/ffmpegfs \-o allow_other,ro,desttype=mp4 .fi .if n \{\ .RE .\} .sp With the following entry in "/etc/fstab," the same result can be obtained with more recent versions of FUSE: .sp .if n \{\ .RS 4 .\} .nf ffmpegfs#/mnt/music /mnt/ffmpegfs fuse allow_other,ro,audiobitrate=256K,videobitrate=2000000,desttype=mp4 0 0 .fi .if n \{\ .RE .\} .sp Another (more current) way to express this command: .sp .if n \{\ .RS 4 .\} .nf /mnt/music /mnt/ffmpegfs fuse\&.ffmpegfs allow_other,ro,audiobitrate=256K,videobitrate=2000000,desttype=mp4 0 0 .fi .if n \{\ .RE .\} .sp At this point, files like /mnt/music/**\&.flac and /mnt/music/**\&.ogg will show up as /mnt/ffmpegfs/**\&.mp4\&. .sp Audio bitrates will be reduced to 256 KBit, video to 1\&.5 MBit\&. The source bitrate will not be scaled up if it is lower; it will remain at the lower value\&. .sp Keep in mind that only root can, by default, utilise the "allow other" option\&. Either use the "user allow other" key in /etc/fuse\&.conf or run FFmpegfs as root\&. .sp Any user must have "allow other" enabled in order to access the mount\&. By default, only the user who initiated FFmpegfs has access to this\&. .sp Examples: .sp .if n \{\ .RS 4 .\} .nf ffmpegfs \-f $HOME/test/in $HOME/test/out \-\-log_stderr \-\-log_maxlevel=DEBUG \-o allow_other,ro,desttype=mp4,cachepath=$HOME/test/cache .fi .if n \{\ .RE .\} .sp Transcode files using FFmpegfs from test/in to test/out while logging to stderr at a noisy TRACE level\&. The cache resides in test/cache\&. All directories are under the current user\(cqs home directory\&. .sp .if n \{\ .RS 4 .\} .nf ffmpegfs \-f $HOME/test/in $HOME/test/out \-\-log_stderr \-\-log_maxlevel=DEBUG \-o allow_other,ro,desttype=mp4,cachepath=$HOME/test/cache,videowidth=640 .fi .if n \{\ .RE .\} .sp Similar to the previous, but with a 640\-pixel maximum video width\&. The aspect ratio will be maintained when scaling down larger videos\&. Videos that are smaller won\(cqt be scaled up\&. .sp .if n \{\ .RS 4 .\} .nf ffmpegfs \-f $HOME/test/in $HOME/test/out \-\-log_stderr \-\-log_maxlevel=DEBUG \-o allow_other,ro,desttype=mp4,cachepath=$HOME/test/cache,deinterlace .fi .if n \{\ .RE .\} .sp Deinterlacing can be enabled for better image quality\&. .SH "HOW IT WORKS" .sp The decoder and encoder are initialised when a file is opened, and the file\(cqs metadata is also read\&. At this point, a rough estimate of the total file size can be made\&. Because the actual size greatly depends on the material encoded, this technique works fair\-to\-good for MP4 or WebM output files but works well for MP3, AIFF, or WAV output files\&. .sp The file is transcoded as it is being read and stored in a private per\-file buffer\&. This buffer keeps expanding as the file is read until the entire file has been transcoded\&. After being decoded, the file is stored in a disc buffer and is readily accessible\&. .sp Other processes will share the same transcoded data if they access the same file because transcoding is done in a single additional thread, which saves CPU time\&. Transcoding will continue for a while if all processes close the file before it is finished\&. Transcoding will resume if the file is viewed once more before the timer expires\&. If not, it will halt and delete the current chunk to free up storage space\&. .sp A file will be transcoded up to the seek point when you seek within it (if not already done)\&. Since the majority of programmes will read a file from beginning to end, this is typically not a problem\&. Future upgrades might offer actual random seeking (but if this is feasible, it is not yet clear due to restrictions to positioning inside compressed streams)\&. When HLS streaming is chosen, this already functions\&. The requested segment is immediately skipped to by FFmpegfs\&. .sp \fBMP3:\fR The source file\(cqs comments are used to generate ID3 version 2\&.4 and 1\&.1 tags\&. They are correspondingly at the beginning and the end of the file\&. .sp \fBMP4:\fR The same is true for meta atoms contained in MP4 containers\&. .sp \fBWAV\fR: The estimated size of the WAV file will be included in a pro forma WAV header\&. When the file is complete, this header will be changed\&. Though most current gamers apparently disregard this information and continue to play the file, it does not seem required\&. .sp Only for MP3 targets: A particular optimization has been done so that programmes that look for id3v1 tags don\(cqt have to wait for the entire file to be transcoded before reading the tag\&. This accelerates these apps \fBdramatically\fR\&. .SH "ABOUT OUTPUT FORMATS" .sp A few remarks regarding the output formats that are supported: .sp Since these are plain vanilla constant bitrate (CBR) MP3 files, there isn\(cqt much to say about the MP3 output\&. Any modern player should be able to play them well\&. .sp However, MP4 files are unique because standard MP4s aren\(cqt really ideal for live broadcasting\&. The start block of an MP4 has a field with the size of the compressed data section, which is the cause\&. It suffices to say that until the size is known, compression must be finished, a file seek must be performed to the beginning, and the size atom updated\&. .sp That size is unknown for a live stream that is ongoing\&. To obtain that value for our transcoded files, one would need to wait for the entire file to be recoded\&. As if that weren\(cqt enough, the file\(cqs final section contains some crucial details, such as meta tags for the artist, album, etc\&. Additionally, the fact that there is just one enormous data block makes it difficult to do random searches among the contents without access to the entire data section\&. .sp Many programmes will then read the crucial information from the end of an MP4 before returning to the file\(cqs head and beginning playback\&. This will destroy FFmpegfs\*(Aq entire transcode\-on\-demand concept\&. .sp Several extensions have been created to work around the restriction, including "faststart," which moves the aforementioned meta data from the end to the beginning of the MP4 file\&. Additionally, it is possible to omit the size field (0)\&. An further plugin is isml (smooth live streaming)\&. .sp Older versions of FFmpeg do not support several new MP4 features that are required for direct\-to\-stream transcoding, like ISMV, faststart, separate moof/empty moov, to mention a few (or if available, not working properly)\&. .sp Faststart files are produced by default with an empty size field so that the file can be started to be written out at once rather than having to be encoded as a complete first\&. It would take some time before playback could begin if it were fully encoded\&. The data part is divided into chunks of about 1 second each, all with their own header, so it is possible to fill in the size fields early enough\&. .sp One disadvantage is that not all players agree with the format, or they play it with odd side effects\&. VLC only refreshes the time display every several seconds while playing the file\&. There may not always be a complete duration displayed while streaming using HTML5 video tags, but that is fine as long as the content plays\&. Playback can only move backwards from the current playback position\&. .sp However, that is the cost of commencing playback quickly\&. .SH "DEVELOPMENT" .sp Git is the revision control system used by FFmpegfs\&. The complete repository is available here: .sp git clone https://github\&.com/nschlia/ffmpegfs\&.git .sp or the mirror: .sp git clone https://salsa\&.debian\&.org/nschlia/ffmpegfs\&.git .sp FFmpegfs is composed primarily of C++17 with a small amount of C\&. The following libraries are utilised: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} FUSE .RE .sp FFmpeg home pages: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} FFmpeg .RE .SH "FILES" .sp \fB/usr/local/bin/ffmpegfs\fR, \fB/etc/fstab\fR .SH "AUTHORS" .sp This fork with FFmpeg support has been maintained by Norbert Schlia since 2017 to date\&. .sp Based on work by K\&. Henriksson (from 2008 to 2017) and the original author, David Collett (from 2006 to 2008)\&. .sp Much thanks to them for the original work and giving me a good head start! .SH "LICENSE" .sp This program can be distributed under the terms of the GNU GPL version 3 or later\&. It can be found online or in the COPYING file\&. .sp This file and other documentation files can be distributed under the terms of the GNU Free Documentation License 1\&.3 or later\&. It can be found online or in the COPYING\&.DOC file\&. .SH "FFMPEG LICENSE" .sp FFmpeg is licensed under the GNU Lesser General Public License (LGPL) version 2\&.1 or later\&. However, FFmpeg incorporates several optional parts and optimizations that are covered by the GNU General Public License (GPL) version 2 or later\&. If those parts get used the GPL applies to all of FFmpeg\&. .sp See https://www\&.ffmpeg\&.org/legal\&.html for details\&. .SH "COPYRIGHT" .sp This fork with FFmpeg support copyright (C) 2017\-2026 Norbert Schlia\&. .sp Based on work copyright (C) 2006\-2008 David Collett, 2008\-2013 K\&. Henriksson\&. .sp Much thanks to them for the original work! .sp This is free software: you are free to change and redistribute it under the terms of the GNU General Public License (GPL) version 3 or later\&. .sp This manual is copyright (C) 2010\-2011 K\&. Henriksson and (C) 2017\-2026 by N\&. Schlia and may be distributed under the GNU Free Documentation License (GFDL) 1\&.3 or later with no invariant sections, or alternatively under the GNU General Public License (GPL) version 3 or later\&. ffmpegfs-2.50/test/0000775000175000017500000000000015215723161007762 5ffmpegfs-2.50/test/test_tags_mp40000755000175000017500000000013615052412650012400 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_tags" mp4 1 ffmpegfs-2.50/test/test_filenames_prores0000755000175000017500000000015015052412650014213 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filenames" prores mov ffmpegfs-2.50/test/test_filenames_webm0000755000175000017500000000014315052412650013635 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filenames" webm ffmpegfs-2.50/test/test_filesize_opus0000755000175000017500000000014115052412650013536 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filesize" opus ffmpegfs-2.50/test/test_filesize_ts0000755000175000017500000000014015052412650013175 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filesize" ts ffmpegfs-2.50/test/Makefile.am0000664000175000017500000000367415202053425011743 TESTS=\ test_audio_aiff \ test_audio_alac \ test_audio_flac \ test_audio_mov \ test_audio_mp3 \ test_audio_mp4 \ test_audio_ogg \ test_audio_opus \ test_audio_prores \ test_audio_ts \ test_audio_wav \ test_audio_webm \ test_cache_hls \ test_cache_bmp \ test_cache_jpg \ test_cache_png \ test_cuesheet_file \ test_cuesheet_embedded \ test_filecount_hls \ test_filenames_aiff \ test_filenames_alac \ test_filenames_flac \ test_filenames_hls \ test_filenames_mov \ test_filenames_mp3 \ test_filenames_mp4 \ test_filenames_ogg \ test_filenames_opus \ test_filenames_prores \ test_filenames_ts \ test_filenames_wav \ test_filenames_webm \ test_filesize_aiff \ test_filesize_alac \ test_filesize_flac \ test_filesize_hls \ test_filesize_mov \ test_filesize_mp3 \ test_filesize_mp4 \ test_filesize_ogg \ test_filesize_opus \ test_filesize_prores \ test_filesize_ts \ test_filesize_video_mov \ test_filesize_video_mp4 \ test_filesize_video_prores \ test_filesize_video_ts \ test_filesize_video_webm \ test_filesize_wav \ test_filesize_webm \ test_frameset_bmp \ test_frameset_jpg \ test_frameset_png \ test_tags_aiff \ test_tags_alac \ test_tags_flac \ test_tags_mov \ test_tags_mp3 \ test_tags_mp4 \ test_tags_ogg \ test_tags_opus \ test_tags_prores \ test_tags_ts \ test_tags_wav \ test_tags_webm \ test_video_hw_dec_mmal \ test_video_hw_dec_vaapi \ test_video_hw_enc_omx \ test_video_hw_enc_vaapi EXTRA_DIST = $(TESTS) funcs.sh srcdir test_cache test_filenames test_tags test_audio test_filesize test_frameset test_filesize_video test_video_hw test_cuesheet EXTRA_DIST += $(wildcard tags/*) # NOT IN RELEASE 1.0! Add later: test_picture CLEANFILES = $(patsubst %,%_builtin.log,$(TESTS)) AM_CPPFLAGS=-Ofast $(libswresample_CFLAGS) check_PROGRAMS = fpcompare metadata fpcompare_SOURCES = fpcompare.c fpcompare_LDADD = -lchromaprint -lavcodec -lavformat -lavutil $(libswresample_LIBS) metadata_SOURCES = metadata.c metadata_LDADD = -lavcodec -lavformat -lavutil $(libswresample_LIBS) ffmpegfs-2.50/test/funcs.sh0000775000175000017500000001165415202120020011343 PATH=$PWD/../src:$PATH export LC_ALL=C TMP_BASE="${TMPDIR:-/tmp}" TMP_BASE="$(cd "${TMP_BASE}" && pwd -P)" DIRNAME="" CACHEPATH="" TMPPATH="" TEST_TMPDIRS=() EXITCODE="${2:-99}" register_test_tmpdir() { local path="${1:-}" [ -n "${path}" ] || return 0 TEST_TMPDIRS+=("${path}") } make_test_tmpdir() { local varname="${1:?missing temporary directory variable name}" local kind="${2:?missing temporary directory kind}" local path case "${varname}" in ''|*[!A-Za-z0-9_]*) echo "ERROR: invalid temporary directory variable name '${varname}'" >&2 return 1 ;; esac case "${kind}" in mnt|cache|tmp) ;; *) echo "ERROR: invalid temporary directory kind '${kind}'" >&2 return 1 ;; esac path="$(mktemp -d "${TMP_BASE}/ffmpegfs-${kind}.${0##*/}.XXXXXXXXXX")" register_test_tmpdir "${path}" printf -v "${varname}" '%s' "${path}" } remove_own_tmpdir() { local path="${1:-}" [ -n "${path}" ] || return 0 path="${path%/}" [ -d "${path}" ] || return 0 case "${path}" in "${TMP_BASE}"/ffmpegfs-mnt.*|"${TMP_BASE}"/ffmpegfs-cache.*|"${TMP_BASE}"/ffmpegfs-tmp.*) rm -rf --one-file-system -- "${path}" ;; *) echo "WARNING: refusing to remove unexpected temp path: ${path}" >&2 ;; esac } remove_registered_tmpdirs() { local path for path in "${TEST_TMPDIRS[@]}" do remove_own_tmpdir "${path}" done } unmount_own_mountpoint() { local path="${1:-}" [ -n "${path}" ] || return 0 path="${path%/}" [ -d "${path}" ] || return 0 case "${path}" in "${TMP_BASE}"/ffmpegfs-mnt.*) ;; *) echo "WARNING: refusing to unmount unexpected path: ${path}" >&2 return 0 ;; esac if mountpoint -q -- "${path}" then if hash fusermount 2>/dev/null then fusermount -u "${path}" 2>/dev/null || fusermount -uz "${path}" 2>/dev/null || true else umount "${path}" 2>/dev/null || umount -l "${path}" 2>/dev/null || true fi fi } cleanup () { EXIT=$? echo "Return code: ${EXIT}" # Errors are no longer fatal set +e # Unmount ffmpegfs first. If the mount is still busy, use a lazy # unmount so the temporary mount directory can still be removed from # this test process' namespace. unmount_own_mountpoint "${DIRNAME:-}" # Remove only the temporary directories created by this test run. # This also removes directories whose variable was reassigned later. remove_registered_tmpdirs # Arrividerci exit ${EXIT} } ffmpegfserr () { echo "***TEST FAILED***" echo "Return code: ${EXITCODE}" exit ${EXITCODE} } check_filesize() { MIN="${2:?missing minimum file size}" MAX="${3:-}" SHORT="${5:-}" if [ "${FILEEXT}" != "hls" ] then FILE="$1.${FILEEXT}" else FILE="$1" fi if [ -z "${MAX}" ] then MAX=${MIN} fi if [ -z "${4:-}" ] then SIZE=$(stat -c %s "${DIRNAME}/${FILE}") else SIZE=$(stat -c %s "${4:-}/${FILE}") fi if [ ${SIZE} -ge ${MIN} -a ${SIZE} -le ${MAX} ] then RESULT="Pass" RETURN=0 else RESULT="FAIL" RETURN=1 fi if [ -z "${SHORT}" ] then if [ ${MIN} -eq ${MAX} ] then printf "> %s -> File: %-20s Size: %8i (expected %8i)\n" ${RESULT} ${FILE} ${SIZE} ${MIN} else printf "> %s -> File: %-20s Size: %8i (expected %8i...%8i)\n" ${RESULT} ${FILE} ${SIZE} ${MIN} ${MAX} fi else if [ ${MIN} -eq ${MAX} ] then printf "%s -> Size: %8i (expected %8i)\n" ${RESULT} ${SIZE} ${MIN} else printf "%s -> Size: %8i (expected %8i...%8i)\n" ${RESULT} ${SIZE} ${MIN} ${MAX} fi fi if [ ${RETURN} != 0 ]; then exit ${RETURN} fi } set -euo pipefail trap cleanup EXIT trap ffmpegfserr USR1 DESTTYPE="${1:?missing destination type}" EXTRANAME="${3:-}" # Map filenames if [ "${DESTTYPE}" == "prores" ]; then # ProRes uses the MOV container FILEEXT="mov" elif [ "${DESTTYPE}" == "alac" ]; then # ALAC (Apple Lossless Coding) uses the mp4 container FILEEXT="m4a" else FILEEXT=${DESTTYPE} fi if [ -z "${EXTRANAME}" ]; then EXTRANAME=$DESTTYPE fi if [ "${EXTRANAME}" == "hls" ] && [[ "${0##*/}" == *_hls ]]; then EXTRANAME= fi if [ -n "${EXTRANAME}" ]; then EXTRANAME=_$EXTRANAME fi SRCDIR="$( cd "${BASH_SOURCE%/*}/srcdir" && pwd )" make_test_tmpdir DIRNAME mnt make_test_tmpdir CACHEPATH cache make_test_tmpdir TMPPATH tmp #--disable_cache ( ffmpegfs -f "${SRCDIR}" "${DIRNAME}" --logfile=${0##*/}${EXTRANAME}_builtin.log --log_maxlevel=TRACE --cachepath="${CACHEPATH}" --desttype=${DESTTYPE} ${ADDOPT:-} > /dev/null || kill -USR1 $$ ) & while ! mount | grep -q "${DIRNAME}" ; do sleep 0.1 done ffmpegfs-2.50/test/test_frameset_jpg0000755000175000017500000000014015052412650013323 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_frameset" jpg ffmpegfs-2.50/test/test_filesize_video_webm0000755000175000017500000000014715052412650014676 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filesize"_video webm ffmpegfs-2.50/test/test_filesize_video_mp40000755000175000017500000000014615052412650014443 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filesize"_video mp4 ffmpegfs-2.50/test/test_filesize_alac0000755000175000017500000000014115052412650013450 #!/bin/bash __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" "${__dir}/test_filesize" alac ffmpegfs-2.50/test/srcdir/0000755000175000017500000000000015052412650011243 5ffmpegfs-2.50/test/srcdir/copyright0000644000175000017500000000101615052412650013114 To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . On Debian systems, the full text of the CC0 Public Domain Dedication version version 1.0 can be found in the file `/usr/share/common-licenses/CC0-1.0,'. ffmpegfs-2.50/test/srcdir/frame_test_pal.mp40000644000175000017500000357063315052412650014613 ftypisomisomiso2avc1mp41free*mdatEH, #x264 - core 148 r2748 97eaef2 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=24 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=24 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00te+,ڻęë/w C;O%fA`U٘Aѯ$nL>2@8?L/s0Qbi\ n1Ɔ:[c2[фP*8iO(ӱ9ܯgf1&٦wGҺז"fVpk b|g@~ݟ =e|t!O üq⇉X`䗡o cyN+J 4!%&>ЧQl]6!ҍ=q"9jPZJDąC&j\W/(䢫?ȉMI:bw@?]9,/'b[fm ӨE:;G#<*YͼayŚk/Zljgalto۝v ^}^l%b]aƫY9"VJ\KQ~m7wm-}WF HKF%,-X^}Je|Oζ_{Ψ֖KG3<*.-YCD밺sm0ĸ𜉟YGsRI /q>iudPstu¦'@ÁHlǮ 4茾h1@xwdKsYi߈ն`M$' 8VV:pzrݺjf2Bp+%%6'f0%Y==ї0Lo4a5:iH=EENS k*D|TM,E.2Suzӷ]Fmш>FHzm(2 S0h8,_([.[bF? [ # E|Q$(wps@:)b$T$-Ghe;?'lj̫?n?!Χ-2B+m@$`5E`0]9$1]qDZ{_ N3Z!VtƕeV[@]3 r ~>-<3$$1F]ء.}mN&|y8)(]kVAJWFS|X޵dXוmt5^KtŰjGȠ`/m)yUkqva:OUHX09$4-k}L`]?W6SO#$\TNu8ɑDBi2 A!lB0`Ev63](W[DlI?ZZוwNMm=Ԡ-]IL G.Zގr#%he:JbJFDDsKx v:qv N$ Pț>|vbJ&2 V?/ISI;n֖{[@p79Q-$q} F5PO6T#?۴1g;OPI{g,[#"BC4Fy:h- ɰtu.+t~z 07`i|)cDIS($ve9G/,hmF χl~J]\IYu~g-ڠ[q#]UI":ufpy`2n9׮XA¾ӷ.9`^maK,)eLaX8 `0MSړjHPA@IP: J/9QłX>0fQf.ʨ"S +_򒦬3͋Q:S} |2ƀ\. 8kU΃E^ypu3ARA2R"_>#/ ļpzȉɘ9RӪ%\4tCYסol7YA=~?gwׇ x  h,ml.7Io?2Ap{Ql!, 3!˛u 0h+}9fSX$Y*rgzĨ(DOxhp+CikC'Vs's3ﲴC6ffj}$&d缍#`C~?H!rm!t0j{UU=XW~ڌLB r-;> l6 m_Ix*0Xנn1I#HMOo} S2" & B!$ ~).1hN"e<Q>ȶ)ή8mY֬tl,gѯ_5T֬ucۏn4a dLdIIIFIgvgo?/\x.\EnjQe(((((((((DQEQD  AB#^Y7Q8z(Â3쬆KIs汃]6NBveIV1>gB&Ӝ /Y>F2 OƸP3Qv%v_$W:Ɋ൞FTe4@^O5`lQ 4 [맳?GNGH𡑥W˕k*|V]wTf7oͼX󇰐~c2E]!uP%#q"ď:n)az`($WVF?l[ُ'i}Ȏ65l /{9w~ L6.QE'CM/ NybrQU6|l%GFlM٘3TVt{w -4WPKxT[™1Rt6pt4CEױ a%Q)tET6[0'"hdmcT.‚c:ӣpm{CtcS00s ؃ P<[ %!",$ ыlȩO>w*k>tl?"8bj߈Fs}=n D[F4;@,Rί2V)!p?Zfx&4;mPʬ˳}Ę3-4'qvOiH#˗e&I"iW~*HA󩝄{enkTS!!Oø!CƦHq9o_<UQ:'eo&^vZevR= wG"2q.߸WZ6u -V'0 ڙytGS8X]dr ,d`7S.*FJΖ#6wJ>XxֱpDsYzH `b_NӆὨ$0w%]94ӿ=LP%MŒVUs6zmHzڐ4 Z @n ʫ83Zd,2:;VpM;6|˳}j~|H6eSE Vkw~2?{ ȢPCFR:7žTsU<%g3!_w齄tcD)4m[G֜4q"2HxASfRe$\7fAAٗ]b7 Vݒ˝43BbٳyRW\ i<]%l2LeO!?8WZ4q©D~wu ?.. )]4d*6!I8f/WW' 7 D]~6M@ܥGxuUYK^xX5~PnOP[ԝux Cjn\ΆAxErE; +UzՎOe6ً+#ܒ;[I TU^R㑸tn/`EXz$\O ߘN9UQv$ ,F:#^t͌ T7iy<$9/J2cV.)zL&.y?wӔxKSP4Jk?ҌoQx1e zVt8\ 'B'B@σoK zc6OHrpiź;q7" I1?^w|q̠hNyj%$`]}7C@ցk$q;(ٯ}Ո;VGy>ȵ7YhӸ{ d U:"C#NO=-El0 (j19Xېj_UF] ? G2pߞ蠞OQ)Pу ΍zX0/.Xdl,B!/>xFAWyɛ0>퇟gWW]>6ubGΎnP!;"d~:)o𰖬csvJG򾂹,.hHWGo!6%C_{sӳ"T&4R^dcKaGM Vsu(Ybhw\mrMMǟ%q<ԭ Z^c"ƨiˑJiW䍾)gmtj:}\iøPUpscs'd\̈́T E#S _>AݛjbkAz=*Qc&xn*rVԲ_?<([o{cIXз~puc@pm݂Hg&ZzK|OrlU-mO_V |6OxO#0O9>+0jU!(:v0$q (M )AcI&S8@-g>Pu=,bi/y?!7tm]nlU–zFFaK\bd^uX]gvt䅇DzINf [7$LW;M1aSYްi0G 휀A%ж*?Ev5,<Hm:涴1ϋS_n "͸(vea ٌ2ϦYAv=V Ugus5vVCt$rk*#B7ǵqb1&4Z۾UG?NlPnޓ `KUv zU^G-&PB.NE6Fu >vAWaKb.hh xJ"Lf"+0M>;NG/q^Ɓ$*G{ .U"ʘD6_M:%SWo7@8g0 m&q@yط愅 F8 5[pLtLX͡H3S}&:Qf2}8;`' iql`!68^cg,sYo q8|o"HK4[8w8h M;EƲeDGK1J99[@㔿#rAϱMYKx̅pr!wq'ؑYNo|޹'3@j. sN0Q֝lH͒msĥAሖ is74![ՎCڮp `Ӄ3M`_]@Jh"螂65!SA+t6Pif+Yӏ񾉁bu4P3V'-j 7y}ϥ8RK/:"'QqcyE0CT!!3dlbţKNvK;2.j;^%8[ ˇBZG4|% at.nYjn$\߱dPOuf)[N5]+;,ıDUZe_BIhVZ[7%+h-y[[gnerMITTGG[Noڼ*&U>GyenjAv~$oZ!9n;ή&MJh/Eá6}Nі^g% KeV9EʦBJj7/20N ۀ\cLQhD"HD,: EȅN[l$8lP*\}~;Tz(>l*dw1ú7fhqqxb7LVQ Q`첖^Xݖ^iY=>˫A 'f̈́Vf򎂯v?~kҕjN\ۖ/TU ZY<Ѻ,+V}(t ucF28tFۤs^T=6rNpҏvD{4&DL| Y]ၠ|w+[!lʠZbԹ|zUTfe*DKύDo:a&9_t3YAhAoSsC&QBɫ؎6Bte8g C˸ؽ儎9k)ڕ$y~Kw@.˥=I "ѯkk:`eTg>`3E"Z3qRVw@6Ud.K t 1`,ѷQB<[gPSP &~`r/g760z%͵?@2T֠I(Mc^n>;l @}zppn^ , oѠ)w&T5iG> /!ē1LPa+q~齷UWor??ӐFEѢdF[w!l0:/^,(}! NOeo=5Wu-_HRXRmhvM8̔b`hRJp76$HƟ߾d"N[mV|O_qy!6{J`yʟ>>jԭH=i` :\T! 蔰ڋܐ [Iƺ6ҽBT+:, E?. 5  ,x&JRJRx8[J5!=D:9#D{I+Ï<=^2hGJifTb'#4"BYm+$NEREhv.BQЂjH }!kK x۪aLfn"-+qM=;%y(2ܿ|RRINv%. %0H\5: J=?4|kR,()Jnf&ꑊU>{f *P I5C0$K,#E Ҭ$N20NGzWfvJ)TGT&:`ք 5vqaD3RP) (ŮVZ5vVPϱD SZzg7ɮ W2hDt`[AI&S8@v.1O Q_[ۀ dt82E߮6_?xvz51R.*t ,"rH M2[ЗYl?&S)ˑ{@`%o g*_bΐw~QfrKMiu7=d5?y(C֊d&~qJeي^lJe/aE^oItח~4ea h} a5ȿ2!ڒSP毠j5%K/?@Re۲ʦ^^O?x[@# eBeUZYo ŗQ/C@]sBDy}(F$,lBźeR>:lՙхg'Ξ ]\QP5'SC vePQ=.{I==QiL:)|u4Io[4h)MiFԩN'iK;`^3=!Ѹcz+*b.8FMܙ*0RDfHdX*PZY%,r4 LC`]nwhsvtxkʛO 4x?xXPm<]ёWGv.M> ) iVN;s7Ϯ|?htmծ h vۥ~ğ\V1 s}vy1,%X fJ4 =%kpgSʻ? V L>=ਚu:'1ߝsKN"fxWuW-0[πۯI{uU `9{@ L RN>tS= 2\]b-)Ip+%qYie&t-'Cװ-I7O!W&JpvmpbVܝfKTBTKs 9V„쫱NYeUdUp 4lu /]/R+uծo/^QHv0尧"m_J!: \g2V!g,Vy!Sm5=X81!̂%cw=O8 R}% @@\\!Kg^2ÙG 034Fz h@3Vlf3|X|H`xai>@\=ps=]dzɅ!/X=њ^ga-p <-m  7+ >ֱW&]- Z}`^__Kbhύʕwz8~d@2tpLu47r)hS|dM+ xj=cl 0+vb&R  8M*KԢvGګx0m")T:8"RLz4e}_1cR*emyDNA-΋ h9}gp!G+yOPY/Q9x"54}[,(TŒ,(qIqu|VI6jII iγ =L C,2& Ah3r vg3- `Y9U-hٍy(9:maR<7:.CzIBWuŀNwSX5F<" $@RQJUt܅洓'>_#KKᜃ܃6 卷3V;(Ȇ1%nb<"ty4$JvYPMB!!$;uvP0CgF~FOd|O cSqb-!XYL}~R8OTGȍH: L+B`1 0HRNAI&SgrsYۯg1#lIJ&}wY Oيy%SiLK(0-# 6VNڂ].{o? AEfm]<0.{E~S)%Jpaegn@vD3Y\B#\~!|e#}eY߻]YE` `N0ic6-VhhKa+al29B> rH~˒1yCW~zH_26`v֋}Nnm;V.l.9NycΘ6`5GB#_5(J0K~M"rD ~&hJ16 D&Y|lWpwsM$!f[„t_LGH]T1Z,Ώ(5}9\"\pEI>8x\$٦I08|]v Y$}ˡ%\Q7BִC +ΧMT@(r`vY48,&l0-kp;5il5 c%W?잼yYfOMQpꪇڿ)䋏,<T>NߌLg֒9DNU1s (E$R1I*dmHaOvEk]B. +ۥW=+6 W(awe#lvK\nG<e15Yd lj-[uC\ҷ7yh_x?\F_$xJ9=#n#~W`RT`of&ťr 9\+cWRiLK戣L¨܊~||eaR ="UU6gd!E؛+U{T@2-ɷ;ta *#1sK{e37܇~񼘰ZkEm\$~o98^O䋷W+^!zlGe>L3hnj5 >V:0.U"L@Tn^$}H x6 E H]EMCYEd67ޕ^`fgJu}䑄u$'Gt6:!(MIP„nO2x -QǟE[&}C uZPW> 7 J@ Gg 'zj$:ᾎNJbHmeeh._Z5#O e&eH۫]cke7rJt(yJF|JꎉOa4r|g.7̸YGk`~~n z u-e,沅ƒlP(,WV"yooxNqOR.Uwy2Rp7.eJθ,2vߧe SpЏ.-MBo[g[c;i 6֫8[ul,]j폕-^M}2* OP&*;g :+^^t]| ^5uF \f M,&h u%bndPu~gX>Sv\J/%P{JT'Or"ȮPdw7*OD"뗃?dI B?I wD1T W1TgVw1N{hU3 T֒BD),"G)4)%dR6yod;Z>KHe+@*!Ƹ];VBM}W$=&c7栜Id`S~O&a jmerx*flMʉ% %F\'GՎbEa|&Sb>KO՟ ~G_7bjv^{OR[Yg53#A9RS x@)?mP Ln1jwwQ Ui)] i5:oFA52 -,SӤt?k,:~PxLU)j4>]OE|oxouӌao2H:Tb]1jY>J!v[Q^F򰥚&&]]3vIjr|͹ԏC[ń ncgL@,DQ `qĽ@AyxdɂJvJW ?:ѿ2gu9 L!) s~>ʡ SN5ق$+ȓ[\,UYozP睠 ?NJbݥr2p3_wMÏz'6d5|]DP{"f |ĎsHAW|44Y6sv`@NugYMh ~M]Qg:>)v=\A͗]0JMR0]aL_Q/)u{8ƾ91$AC~j8ؙ=(ȣف=vz^-?-t/$!Pkͱ}^BjulC5 \ E)BzZ]N[# G&!YeZpᇇ p pF|dvjɈm|5TVuRgg0PGL'~gCWL?~nLQL!3UfpH$`#~6K0/}iڸVʮ>bkIB|[R 8F{'1Bh 8 AI&S8@ d-ɏr}~:3aҏQ|e4>$*߮cGD_tA }ڠeDSkXGgXdam`:|`3FN$; c6h!۔N%}?1L,U6-Sk8IjV9\iꎟɋgLqXR2uxt{8`8Qp~$Nbnj֓/Cb'͵NOR::=GW=md>]%D6;MuU>"(* p̕XQ}<'ū'kː'dFSOz&&!d -0s-@l\ݻH D P,j4x۩+. ]yR^Mi_<Ӣ׊$SUͫ5FlJWprZ9Vzn@$XM8iZ'7á*Bn K)qF,lS2-JSDl} "704l2 lB.Hl\J2Dxܳf3*\/jAohaYFJ{EhŶK*RPL"|jz]QyMx<'MY?o:. @cuqR6 i^?bFnEb3+xCn!|!$VPXsCǥ .FR{s7QʶYu"/^--3Njq>M\iǘhmUu,+N#7J uA~DatBp4Nc$QL4H6@2fc}ZQ))d*pޔ=a`Â*0:&Dde#З';,,.ĥQi^, 9.v,ľ;mvo!K2oHo|d(R. $O9#qdN vUk>/%/.( )~|\QsdFư8,y/}Jak.NTs5Όɓ*0[)fac HِtF4 Aٿse),QOV0Zg>5JNz}}] ;[riV`4h< 2] Ws1DIzj[aѤP Jwu'3 kuP< Z-A;n#/U[lpǠu$'5_p>S m+ĭU{ /&.ϾH.ݻR{fA*{}:,w"n){Lc*<+?|FVRT-?x&\<WRYzyx4,4<)lb<IRH*8{\xu(IЈ Qgm@t m5U!|Y|{42\2aA(/!˽Z 0+~:c VՕ E2A~E!~VlfĂQe;Z7 n!1̰ 7"]2"L*r;kr`%{r*Wq \V'h;ǫRzgeX_nFd9 D:ݏk )@KUSTdʣZ#h<^p-=Wc3$}Ts`=8!nޑaղLE ;8Qj0 bt!%A'N[+kWo8t)[6yrpLSd- ^P(vVüL;eNJ ,dUu_coǼ%zm^GmU\1MP?C`qXNP7 H?tA<1d2st_/Vq oy^)=32UyLCVfM9MS9L֖ B(!fFC36_lp$Ѓn?`^_Ģ jPHU\îˮP^DT j2%a^.@8w!yUP~q/YSX.|9[I:Jz@,k2V~zM05usFcIևc]JLr88Twhyw\R#i|7xt|Hj 5b"Cg-b'-j&90BVVPKbt٬Aot&6WS@>R'DqQѶc-7Ɋ`{A$&qw\ɹi wmIraNWPuS0xOc ә`%6SsK,APшヸQ+gOT /)3R%eA 镸ז/B;F^by +>]ЅWGcݤ(qUiAJ+q]+9RuAtAORc_Χ`p 9Zu~]ZFu"R]_'Q4g74O7P~="PV <5XÛq"ڳ\Kn TYnF_RZĖWyH˹8DNk"40W5t3C/ReϤWtgVġt|G'OnJmf{iT{̚} '3jmgӥ۩EL HSҜ<f?-ó#dkB4= {Abօ܌WW3@GE"J~Y]Ϡ ;ƭ8\L-SR<T]9'7J nܿJE=2,|PPv[EAZJhۄ>Yd u1X^+ě`LQ}^.PB/H^30 T9Mz:DfΥ 1d4YA:]P9"=i0CfȝɇX;"Jql$TdKtZ"idv]z%Z4pHl+ٺ\h5 !4v{ue'1{zE?:<Ґ܉ը 4xqT}˧8Ф8&0iXp6}K.;A}tWwq80]Cn&X~`5w M;\)S{ }f"0/\&z-l"2y:&Ce״(;xYQ4x=/9Y/i|^c_ ײ={͝csNSu`Mc`&3f35t-x7., 1[w 7]|7źniLקw7tY8FRT *')dl@uye]MAhp?9ɗ!(Z_ B;tWUT`U oDLm2p[y!*,+SM+YpF(a:$ԣu;lƈ@}^:U,]? >N"d""IT!ڇaU_<:|?u 9[0\g/0"kOk6[̷apM(3%*ueee YzݢcvK/5+*𱠾$F}k; ӮJ!q']WA@8,xZm׃Y ǿh&6S;)͉4SXԩt҈Q`1\|wU)52֦LdtP) Ɛ2::|iV`B ('<)|Eg2@D;`%X3(`w㾛 ڵ ,jBю(^(9:t"|AY|ufaI$YS)Sx27* }ª򣣞&x` \v; ^4{2ܮQ6o/YFtE۸2D"o #l|cV@}Tǁ3 2V{'e&f7 Mxa) ʛGEf|* |AIL*WA gyyPD cاJcтR pmp%WsC肸~jKݞ" -Ka!3lb}xh)os5D0RH66FG"ҴOm ̰} ^Aǹ|X)ڜj?@sZ{FN  v+͉H#(u.l5-<|ks`Ta(ӟBFQ6B$fx(Ȩ}?br/yq:nNqkBO [0n6U=dTt??1D:!#/>Vnqvs>`vAnvkx1Ŋɟ#b׎o:v>WL$z|7nP鳝[C4L& &B6YU"h|NCY%U/0ͫp!C6(} &m%2@fJ PށL\S1(q<]coYUAWxz̄&. Iz0rtNgW!Id˓+bD.{eບyf6s16m5N,hJ9ɠR%GKdw˱+sˮa9b2>2MZIRōhdJ BJ."4G/h_#M9|䂺sEpU=CG3%Os:5*t2' U܋Y_si@.5Ȉ~‰ ^x"W+،Sr9 _瞊E;oN5ÞJ]i\)"c\nNBL CoUbgKct&Psߞ %H7yM0poI}8ۥz.⦣ɞ''"zn{PϦOu meR[g(]7 ȺMUGoA2ؤg7 ΒySCds4̼l9HUb~:R)9i׽6 ׻ey4Fv{E:)iuvK%W1q!;+Uij\KXU7#`SrZgl(_ﮮԚ:Q~wpa Iw';NGy/¥*8ʈŖk9ظc7QtX=t0KlP|qwt71+gv@GPkU5> b o1yV(;/*;M6 _W%C2ve-Q61ߞ`Ɲ` u, IkǷ4j*nI8"aCe&3_\Dِ/`_Ň2t#-AU҉߃qVޏAEzbbJcl ޿DS[.̿m Z8>8C yI3bjne%dkUjԘ#4C08o4xܫ<,Qۖ%RR/.+ 8l *ϒ4F߳g`}p& s]~ބ.~IAEr0`QGwX6O#sTJ2(9?[۴ ѫ!lW]{K(ʣϮ"s(Y v6tbޱ74U sbQeE5kԭdArr DxYUl>+Z st_|g[>D%,p!vv]cWh1t|1.)a myRcI`"2ys t$''*}- j3H& 0jZ4Y' D+<$U(Va%QҥkШqg\6#{~+wX2ԡ G$11fI B^HlNHTu//*|0Dɝ`rd.+u`HbP;I/{vsڣ/> PR @5B3q~5oL (21H۸mjE N B~#y.^0%A| fjv>gC{)ܜ#7z@(MDF:;dIB5:$w֮Ѐ (ph9҉wB]Ͷ#GY~j>A%"05-㼠iS!$NvM\x[kPi"&37V;җ@:GޥEZvm)WKnۖLA|yب] A0fFGPQ$@RvJ _v*Q1ehtjqrWigbê48 "ҁ{\[cV߸xSE2M"jhO9" R8wIjY_|/i5Ҽ'Z#9&`/Fs ` @!o%W t'v C:aZwPԖ0N~;fpSB{sE1."ϯPCƏ'iY"YsRYX҈ K|?䝇]Ơ8y&+j!{} IV-My!OֳKkV^6|.2ɔFsyݰMl.Nz" {ҭU Y3Q ٬B+ lI+k82hX_@%vo6 K{ >r]+RJg&ʑ@ouAgB?IZ [IDӤ8,_d_~jC 3eʌ}GXC3 εٟSJ@<mHj2ep;lB[G'^Bǰ08*#;EVrU;S˔7ҩ @|7c\܎v[h"q'b8 H $,dLC\(z{F(f'T9#L3m/DP>7- .uI^ /P붵]VQ`^z=מ(YCֈ~/7G}'#w]FXS8^!t/Av7}`7} ?1,gDT-iˊ1ma"AfF _!T.05,'=o'm"-mVЈ˞ch!T(No9h˪r]vpEoK&l>(>yƢ7S7GNL%5Z+[a&xx%H9/<~؂v /&hXOH' Csk=Kh6(Pe!ƩwJ_'־"?#؁9"sܸx';||8d>Ԫ fc^Ց1TKW:Z{yi gس Von*&y'W9}SChZV'{g^{GBֺ*rp~l"LpE-/ guvܩ ʄܔ:2?a:T6E1h+x.TxP6;*Be},v@7S'n%Mؐ^O D7m>1eIzkOw_[?h\{=lѶAG ^ cIRx*ogK2Or,2M|}yΛHb)\ ;Ц )٫#"q׊qӦxOח+@Xrzk M4. Ʌ@^&s~oӳ\g0xOU>1$SD E*ܧ- 5Màĝmu)_Ej䠛TaMgvZB>~C:Pjz#5:$$xTT)T1lUiplDzwn!;I+km΋ֺ,biR3F%^0H);-L)S'RU$Qj\v( Jv;>7$HsE!#Q;3h*E"a=J1UlL\|:v;l&R2 |xeT*8/T @Y^5L+LC{k"PU)AJRFFn1W!FGr<'7,fBm+-{ڳ:@:ID~|WWzX48zNh+l=*fI #@ v„ 01hX5Q(8 "\ DhR۪ M5Uu'ŴONk Z ]9Z*PCuePTtL(cOl7}5`UQD+OMD& @&@C@B*^>pX \y5,~W 0V_: NI ]Ԟ#Q6v; _;D#R¯qÊr 82?āc/rI@ ]~0IօJBڵAq"Hb`rmDjȬlӓ"wOZt#:)_/x . V0Qec^ sm7c ZcW><8!8=!9*BɁ^Me""$MG)jf~(wȼmڪ;maOMnb2sI>Gywߧ_%?q| A}X%zlQ?2NШ[:VC{)z Phw%c$u!^2({^D?^vŞbR mk JiKY.`UbWu.bp|~'sF@vMcu&O*Cݷ'D@R#s ow$(z5S7m.^-Ű tk!IZsҞrEZfH iҮϼdYB [LYjnZ@t#xc 7-O3{V7#MH޲*Z;%:'ʇut X_4}L9ꄮ@AG_.v)"7;)r($ Jn-G?bﱒt4EK6 Sa  I VrWc2zY`/D|7|.kWkdU%C:/'p"8@Ȼ91x\ Kdy.l1z O[a;) ddWEuL,iU,MA\"X z>Rfo@DKPh<_>z3NحVE!z^{n'\'N1*O1ل_mĪ5Y=aո8r^w#g 6ڻe ~=RHp)`X:TCƹ7 ;Ry `5ROFeyժ*|O ߡt.<)/51EW,#Y'cPGtŬf8*i!ʥӶϜG78΋zjSH -)ߌZ9;[W_[.ji4 $lk=J$XVΖ5zX3-HmoX&t:>g#$7A٤8+DɁ4B05'VsLMRſY#Klq_˯vXF1"thdb]]IBrs>,HGEȱU9hwA Tׁ0x~b-ЛFwaTU* BL֊΋e{&je`|8xIKP׍iRTekXa&?Hr>F|T9ゼp jˡŔ ?9-\F6 te]7f ʰ2'e =!y;pDs| zU)هlM؏/5s܄s `A-lv.iH_ӪNMUf-`N X,@ >Lܩry)L$BB"=%xҼ,YNW *|$EۘgH4klW0e-ƪw<)&TJ݄o{[j ʙE@;)3=5{|+cLi T  XC[w*) Vc:6#C/f,8`-rB8 e:YǵhU3@6U c`jyD,7ES|( .L$m Ł1X&Y)z,qc2؋g9kUY8LEN*B 7/z\zP/l|*+5G;Uta kمCF ؛A{POsT XtxH9 `c_זXCL.2lg-1O:g4[w-n/SR'k{ 9yj 0gq+`om΀C0W8!%dHF, s֝c탕zc[vY⥣(~,3е=(D&ɗbSh*#M@ͧc'۹2|圍cq9H~E3o[9OЏ3N(`\};Ѣ1"i*Ĵmr17wn@aĐ,(~I x`ō-EjS_ eg&ޗmpc-ϫѰZn5}bޥoū:p^Xq?rK ?נ9eo"ܓu*=?W\ݔ*Q;(!`" k؁ͷyݓo~4yVT;= `h͠Dz"1VrO8n`F3k+mJ=ƷLdVASߣ`B B rI횘zbP9fQ mA`.b"/A[;\` b{DZ3 -{ % AW. 3I=Tá`ڸO]o_ lQv[{L,,,Yn!+F[?@GwpĈuqƛ Cձ)WAvxZyKnx%x;*a[BXQ eft3'Ph͓=#C듯ؔ)%5>-Wҭ(#z"g&؇^o Y=I p!Ray-r޴0)"~o[i)RR>q4 ԋSd٥dCm 2jB d5\QO,$ ߞօ9:uS V.W{a,W\B](ƃLXxfw D{8s871Id IkɨPvWwn~~tM8VKڻL;R}Hk.&@mfQxr^7D',SpTAH0!wqRs)pu肩$ Z/  iw٩)Eϋo}OwԀK%CQM-%}j7LVc ו TL2ڵHuP6,p0ÛXSo1J \ЂcJj٪/t#f1!DE7AiQݙV ;sI8ƧS+(曼Io"b 4`Za~GCBp Oc(A+p2CͣQ`/~C:@i89 Swtf+zW^~ *B֖۱Px ? 'WSyNK;ǿ}hݤ{ MӒ_LAJdn^m֘""7'J7Q.l1rWLKϱFmuMK_,/1E'{1gѱ C:*WB{ڸ)BEuuxx}?wȒF[f]=4'k { u d?/QAo D\>yv7uZ;t"{u"{=Zxkl{&nsV3mNthAZHәbRA?"5xKl &W*jmm1eGGoRͲ#[ en Z[<2W!&̔[P:7r\10Y~5CG'3D ,hӇ#Ƣ/ z4Yܧ`Dw0B a(w/].q%JmO= Eޚ-VJ9xYGؖhՍ&w'M% ?^ɮ镑}Ŋ- ?03L%co7s Yy'U:=.BRTĬ8:2^{xxgp\r4R@.~ :\~Ԫ'wVOy("s!fk*ٗ8l ytnMU{=I;ǒ VbI+#) m`tᰍQOGFrU E4~08@qfzq?Pj"UfJ^j2\aO#kn$DszZ=("3?q6=e]DT>^O {$`A/<9a?X]Nʰ ]5[UjUS+bPRVp6ELR+\aOHw cJ5do7'@-0TW`?eXDze  .)BA(GI啇!kZN8 9er&ƹ&@Oxا>En0uI0QVb'hP96tسejkSgQ'fJYAьFbkη:*>!%Rx&O].(%Z86 ^:RĈÔ׉*ꒅ մX·/El%'mW{SO%+,?{)S~ eMip[Z2/A!lB[߁=X~^g%??{ǸڠslT7HHmxR9q5߯ xl:Ȳ( [zJzfǵE;}ahM*IxyZDb!?w*?c)R=+E c h1s09*:T{ HGź;XL͎A t{5DN yԄ 8D8fVo\:JWU8=j{Pnd$U/= qE,Ux˻sA̋q<d!FN*>I @$ћ{UQ|-fy]&ȋlP 2䖶DحȖ3=,lNj3,{F͑Tӝ/V> Ux+on?2ٍ=p1G[ 7?Xfһ 8qRa5_B0^-سeS ';NutC}_ÐkOÙQ+k+,='C!uNidrnjQiA'dJ\cqblZ ;%ٞJh멜|!6TL,1s4G^ Q{FgZ!WiἓSČqo'صwwʮhutVS{B-+/#kaRNO8%3[wPC2a[!&" $̿|p!7l ya r"wT y帕l+;y%\._5T3;Ze (#[B٫$?$  t, Emfѹl ,SzEt{1Q T)1"~ M'*t@w&a}ZWC cEeUrzJ4X[)B|fSIjZȝ̄{g'oR\ϲEU)p;(Jh#5$F~|^fR>43 '"LQr=`CQNt8c^5%5Y#9Y,ttȝj< d dC5{.ejȢf?ƷlOіwax Jm쉟pB6UPJw&[a’ , ABłrvq]-%WD-> "sYuTP﫽 CqYwƘ<>i=Bch$5",)|~*eh7Jl"0+'uWI>{`'D}}c.Bo ;ϛ--.[DCUբ*5o&3!q= +E`B~MG`?휃a "(~UQQ0O0pИ[?;B0JQ䞚<|1[kNn;?Yhpc @Is9ʯli>5 .G"3m i!+Gci :IHc_SgiL=)4W 3Z%X~K8vO5-Y=0 U̔]v:_AbhH`GǶgBEG9h]Lu iAhN_ ҄э`>!gt%m~Uc@"]Oezu4GCkiK8'~5%v\kA0O;,lŹ{f9A#r(D3#%SFRAFM:Ww5[XJI#>5@&^(f_U$o_/͟Aų)3J?sဒf<56w!`/ׂpp vtu)}%π^)hBZ}J ͩV;֝K1'ݢ;Y q_nC.5ܴ= 5#LEӘuW=Fs{8j&0I6 B t3={F R II|2Mg0Sn%T^H_O뜖^DLd5bWT(%/jp*Y3\lS#,NNBzC -j\iJ-ZP/k=zL^oY6 KeWVpO.C$Pm沇UPH%ϛ* cPU`:zuwŚĝB`Ob= 9 6LOڲ0)i!WuTvu&Y-<JRi,gVI׿mӷ+l6k 9e8  1Uas0OADJ{t6yA"YЍ U<e z/8kTdEuEAۧykoQoOL%V/X/C w@U. ,'ml.ʝ4R:cԗQ?tdh"r>MÏ>Sրkb<7Kڝ+pw/6o=ѕWX؞PZzO&~G])!vڡeieF/.nS6h} :m`<%GJjp댹ZB VE~D~t:2w=ݗg`qkR=xʧO sq$@(EYItI>i(@N7 Kra'dkâH6E6d/1ޅ5 `⦞\h3{Aj|bMVQ".rn &ʿi /9mdtuae/Rp]~6:?VMg{PDq$]2"lw6b @AAZq rBxzzbp8:c^Ϭ-S7ۄr:/aѤϿ*̲0,@6c9;VBޕ.TW}[n܌rMUzWvR'b^ZvakJx _4 , znc,9W?x @<1+1_uaS7ySI07zBI| 4#/G8`T}ؖ' i+exO@yjMS]pe|Tk;6A'@A;i_jig%G&)w-[Z+弝@xR! ]%Ǜ5 ;{kWd()T~&@m\GH֬4>{f@![k嵇Sl#X l sL&h1|Fvi{ȕ @n!xKy WUD|Kϱqrv9M9V8D5t>ٜ^y؝[QS ̻~mY## \%iݫ|~T`sN#_!XUcd!7]?t1y<ȱ2P(۸+TEp]ve-#\&`'bS)xݦ|żU ohW{9Xaֲpst~mWڸgLf&z";JQH7 I/}On#AXNA80ߗ*╃>"m"9gOz2R[ŁB3ͅT'⨐J7߸WQ`0#;E11qϑ)v!ImP&$Ǒ 0bW0ȓ_D %[#ńssO1CS ع9dH[EWHKP>4JoXJ_ " K`%3\tYBke}2I[V0}G3[USyh^^Wh3HʎL *Յ )4ƇQ9iMN_1 qAN%V(^k>;DZ"κ `Mok(A[sCr8 Rz$Wd).z#ѐ*XD}AB뀭|iz:W^x4 8En :8^vYğ1!8H1X0@̎7:=WPAO\ra2@HmD3jZ %U8NyhWQ4.D(c@] V}D5;p B7 k,{ypɨ~?@ꟁ''6P_`:{ظEk෎9kz'C}ljDyf$[[fv12s~r@%N#:!;5W͊#lbH?us*zOCR~o3W9OP.l ˁ|M{doaDEuLta:B<K,x N= eJS[ΧtewRz&?8Q+LYLa;#D/R:;6*>DtGbPK|0K\4TfYH5J,H_zRP9pJv OͰ5][]VVŮmM08d B2 PGa?T%#\TqDY {2U)pY#z,Pvm\ yR*ˇNZY9j)# *ɞ1nիZם( B+PS2 AI&S8@.c%]xXgZ`_"7Es.N GWA u*ybA:Ӡs <,` z '(L֚34Iu]-k=GN}{>>>(AdUXT巀/gLH"nK5u5UwUo5O eI'UwK7m\@1ɶ jⓩ41H( I1Da%FNSt0Ʈ89ȹ*1#`mՇ ?SX՛/2f%þ+kg4RMGv~ %@loG{5(Ir%z?f+n4 ,7Qkɟn3XUϣ:vZ5AOAP@2c,:l~/}*( mߦ0%Sy'X6څ*-ؒ9<]ף hPN]mGԅO?s|O\p%SZ[LìDom'c\`pHHR@}#b d%*1rm8܉ op|Nm? H{RB=U-jQm\xLa`Ol9-ϲBcJjV]k5NUh :dE?|:ji+x"6+XKtg`ROn8<+-"iD^Rl٩pԙ]tD Ho~t^-k[3ς/ |OBx^VUeg!ZQL"<#;5VB$T4dym[1R,pX_dH*gZ鈉 ϱv6+u9co>=bWK7Tn#0 UNL{hBSa;vt!&4McZ>/ف ! L⁧ W40o'^02Soyvҧ!TEms_/3ŏ8AuϟIXj)ϥQm=b3c'jG^YF+s>)֢چgbS*V'|Na Tnb^A? BWJB ox0}x\ K6`k_?h S(:||pjخ3Γk Œa;f m']ŕ|/NH7 sђAw$Eo8&Ⱦ~WĪUNPcut8ak0Y@]E*3jby %}Gx9NZ8cBC݌CS?@.F]*F y ;xF+(< VZ[pՁabZP{ϳ4qmBr7v+_ymMgέ! nrKROM!kB76+TQ喐Y\42ɺyJ(Dm=IUŖLxb6-dj)[,n7~?D?wp:2ٮ<#rJv>>+ ])k~A8GT>zd"کI{go@:5bݑ@`]9`^ׁC_WVNTU2qo2.j yG`ð5 XɎ wT%@ -ԌMb>)șX5< pUC( ߯#6POcoVk~RS2 Xd&F+4U<ǻ0:Uw99/e Hg@0呝* 4ur¹:4t3[T-ž*e*9\OٌŀBj0֕wzfNzf8WH pzFُ2{*:n}0j[g^<?@ {Fΰ}&b, 4 .4@&n$S'Ulqx/V#•ͮKPέQSdx*?  5L# D; RPW4&k8I<_>祡 o߷;o-^F-uv$gn8__K K%XpwU2E$r _ ^ p;S(}džF}ӓtFk<4(f(Si#ReVS Y, U-;*3x4Ofg|r@UzԤD8Y`|8/`kz)m(7RsbH?FؖmمZ\LޛYL~D]5|^u:VIѧ֭;{shz 5N_e3}c ;g3|7=4Hfk&o}lgUǶDYyVInIxI+SIY 2T1^Fcɵ,6G|KIo@Se> MD*䠡*9,6E0Z2V"" vK˩&\J+/Ûz\tk:<(lab0% ؚdqr?$)b<%7ݟ  u6/~#9gp7DP)}&L'Kdڄ"jaIC2Y%CiOn12m6}< M FB"5r߷/̖/A9E U٢"R[s[ybZՈ iǂqpM{@h4B3hJ^X j7HCtHC D!W Q\#3^ѵ?w@q" 0CAS>uҠBs$:2s7.pYQHTYsPaY } 0ay;OS2JjRbܤX 0Us x䣐㼶=7O kә>HK<BmҮ}4l̻8e* 8>js)PղMb .lokD D)|_Vp\H@ʹBpaqkqN-@?]Q--0.wSS]y{,)hT.-(vh-(H^\TW)Zl٩Gg>2!"28dck$Wj9<s +QR '&L/;~EYau<9c6_12?>UXM<$D*ÎóPdp\urkK{ntJN̝w]t -:7~Og(mrR+I PEGPA0'afUef"4Ȁ cNFT.&W`^ (E5Nb*V'67}q+&lŠ xjC"8?z "2vs2G=E\K5L #>5Ą )J䉺 j!hC})#DGGmg՞2}0^, 9坮a6N2U2rA΁RuxaZ,'Mf0r:ǾF13Ɋ'ԘؠxJ~BiӎpK5;;\$$\x 2/Q75?<"̊ʵ54TZpBT(U(#j%\O7IZ9&>O7v8cێL5tV_$SZ<: Je|QZ)$}zH^'2b ;E0IZ K3L"G~[*. -qaٻࢺ珞 JrqqB^QYCȝi(HY3,gAI&SC,L jPm1kUw=q`h2}1 fx1I Oͯ4gu=9^I!9wom߾)o!K%u~:Zx.f6Y Ur/@:Vk%Ր< L!5vG]Nsd,#\FP;y]-݅y<M)ي'"V[.yzS9mHMz,{S741TlQ{.{m0¡Jxb{yg= t9<HwܻIiRhOtE% ?óBv CѼĺ,S8BIL(+>nĕ=xxhve> #F:7vܯ\P,ŕss_ QEzp+=J0F nM+n %ǣSگ|x5j>X4~~BV1_ ۙx!ǡ%zc>)HU jhfԩw1N8jy4GmݱUƲZ}W$% RJ=(o}Us* 5,9PC {:fD0Ȱ;Eum^t.ϟ-LQlK3 LT >ɦثFug:kR=/C}p@Vr=&P}Hc$!-H(rm8e3# _c~J/PUgw.7 tCj p>h&>fuik!_ogݾF6p~y<Asޱi t嵰e7'a9A0sF⥕>fwO7>n۹9Zz]0|?a/hq]ʀA4ʏ S]ܲθS եZRX0̈'+VVKT| R?^(2f?ǎ,<[lA1HMkӓ S몟ny^djWùr`i%s~!{z,U55 cw &g$܀R?nј;KkI"BT5=$֍?kxvwU GVv;fGB\PaW'Āěqv]Pg} ]*P؛sdDrU V j{9YT{-ӆ| TtiVJzQ95=Z53#֓twK r}# =+`BO1O-M̺l06!+Lbnb7@D[,߽]]^_'{#dN$;tHЫn2ASA´Q1gIǩ0bHiH7 QX SE`װ~ISt~:::}-kұTVi@`׳c sU\O $D& sW yCΣc)nE^E Q~cP؆ڗ P$ ΁jsdNn;=QJhV7O`p,PWḅ ƾkF!0{R7lsЇƱcc/9W+y@,QsdS{җGӾOmnanŚ ܨ[[z"^~%bLfť ,{+,Ft8ۯa2윰NE8t_=X 5I;3Hf*fL#a0.@?HX%9ji3 ?q/jߧzAF AW}6r @Tuibk~aŊL ȽC2fᩆ\D/ ܣj-%ݯ e^Ɖc*XbeZ@% ^`'2"'IjXsR.{Y %4G*7zJ$D~nãlhgC;4}Fۦ,Lxx ٠Z|#Ƞ{zѺ:F h)խG `T+c1=ӵ[iSrEI@#A,e1G ic},z'm/h.rFyd|NLMu\Jw ~zȽålI*ϺY$ Jլ:k&n. YiPe%gsQl}LƚWVJ ߡ!U%(].Q2-{mkem^3GfQ(2)f#}}1sc|SCޭ¯j*DfFmq6hR`4£N&BxE1wȮC(` &{4͆`N}MzK2F޶]3 | ga$9%(q&,Ym&aTM=&R:iy u5tW+Eܔ.UEėa |3JA \yu{qE3wDK븶m[!<jc6_\Hl) /) _)KR+2*Δ29GD$r uD"+H11{稗w=KN/]^yN\?U68%}-' 5td,v' 8GERB b${"^FW KoN úDb*7_2t=zV E0,A3÷s8&[ɇ;Z*l-B!btJ[;I=j@}Ts͚|AM*u,n&d7z9jsa-!Cy ,8 QJ$3e⹔YxCe-~4 oq-s;. N_B{鹊*;(S%wSj@wm 'L0LJQ*ATJQzJᄹ)R m5#E;~Z)J(4mٮtb_^-JZ__٫.CЬ*3Jd3PY֞C8x;l#yXA`}v ̌FEobR |ى8 F -KUM]JkA8K0Yi0=ӭD)oau*f֘p5T(Z!**JQBYEa5O&>sƲN7^ww^5ui5/etWEl,ki1 VR$+Rȯ ōHMrՅIb V 1BF,0.ٱY*ˬogkeNH1X£_Ǹ]^xyJ#!h\UW9d%UAI&SύjWoX>˓!| E"EIs"{{<&M5ڎ&ܱ >*#p(u5SP {^,9b&M%̼XkeNf[4ckӤh|DsmD䫷$E?h?7]^k{ +~Ok6?UKF΁ B+\i tYvS&n0%khu(8 BK$}+B|>8?T Srg";x&z8bM I=1IܮO7z u 魻՛0򞌍 Y^bEAORokόAGn\ώ>w"\ܑ"Šh/kMԢbXLэZO'-,[%3q(r}> "<gDFwUI(y1 V5e 0|u.t'VijtWĎ2/ uf 7c_t 4H0%I*ߧW,}4舺a=8VkX9A %PqO'UKlaa4_|? W}Fs`#;tCjU0(T3,XQm:>HR@BX^@AuJQ8Zs[9.IߜDZi1TB$G_o'xqOq ˳4Gƌ8W!+z7 Ra>_,<+K Oo3KqIUիGLV5kt@y˅;޽k;s_Y+FС#xM"ʭ;CYl#+9M֍rV$Su7<8FC?i%6WV> Č@ 65Yyn:7I &sQxk*!(@z{C6s1>rܿoq`c^>[ .h@{.R_4L{ Uu']يb qC^Xv:;:}[Q!hg0bjZΟeZ=&"9U @u!Z~cǗJ |Zƅ@B%uBʿsŻcpt:DWM)MuCm4B^O>iq/,E0WtD+~W.x%uyKDٸlJv hLPC҈y2T䥙ANR9f+Y?|ܢLA?\/0YV0<խw~1 Fm /6>ϦG43><1ӹ"\>eNC.M$_jTrDpԭ-N>?sV Mv۶K2R-rz#P豌l~n6c}]X,_rS*eBZ?TEΙkP]JI N֩-'NLkkqmBj{%yAGq__=?4ڐ I1V! /%>HsշԷנa*I yW7y^(pneES|kFӯn'xraYN\OeuK7i6$bwtj?9Siꏫ`Vs,bw!%fnDQgzcFu(r݈E9 "!A`1ruN„YdB+3ǻL\6",3,g7'}/mO]P<&8Nj?BЎRNVH7G3mo.څ[d RAџ@Aƹ{"E}i!zJ3Kjhd'{a6ώV0Ӏ$&8pMx+vΓ횲 r2G $>=n9r:&/ei@8j;!مBw>3|lk{DOY)-FRWYGi״d"i`nwL,Pdv>2߸R6i"iu`12zЦ͏.сK;9MK"\zOWk~tt˟c#!4"‡^N<4V78CIOxOZs];[kdotdstb QW*;UH"twڡ9]ώXhh. R5'RIwl b/={Wk)âl ńffsSP'@u6CBocE!fX[O{"a gr/V0~uK(ޱR$ʬ8u_h#lis?Beש]n& D[~xQ${La[IެG*^i+p˥UW0In~BQO hS6dEݞ&bhԝ.K!>îvb-uskE}[Tp!_zl%)RX6f$|Ԛȍcw?(;d;tgEkg},EO{05V3"+B()JY.' i[y#P=ㄒ][Z5cZC)mN jBv8poyN|2~L|9y'ތc) 0 <2rPA vpSj*io}VH5=3dޔY_G<M<4S9{aSxbL pd@6\x,֝kNnR^/W\V!wԮk׭1ۄ&[,2?>Ȗ{fvvٱ(l9"&9`DM ҂3`&OS/V#]z*p_#kیgGzu.;̛V(J3#D##Q~Оjv,,caY\7!l`#"D/94ϓo:٦fW|YRşJՕ-N_};pҨoT߁|?9Ҍa&fn*Įz{YVI5j\TI>BL5Ÿ )лy|"Y±= >'2\UG2Lr{7I }t[j(wp=iUJkDa^`$|T@UoЬIdPr`o[7\,t.JDiNX_G ޓq%GڡVԢڊO0F 6'95s5'¸1bVD']ozi<<̴o^j97=,@kc+wRۯc7q΂eܱ9 +rnb:ʻQkJ(<֪/lm6|MdG.WC<a)%k9Vi{k6y_i;pL cVAa$렃K~ox41(z׀_Xiw]bŜ{3hַі|_Mҁl%tc+C8M&1Ӣ;ݐ5C_vBcn ԣ_>1b,f5?rMn9U-8nU>Nz-˙κ d4 DM`bsA ~MǺV:2GOM(J]MN v^b˃)3Z F%bYGglW8Ы#])=e $7]d#Do]8|{,2o<o0iMYK|_aM)\}Qa 4 c T1"A,U@%']:{_ɻUq`ާtgJN6V+F¯ jժ+%e.P:N@:2ꇸL_n&0/rnHn26N"=Ueq᡹@{@ lhՑ7xH%{z0_ R,`w6iƟTy'SUŸ td!`94L1vLfy2Xf@ @D*?ʇ**ps=J Q0g/g{3@J(G A[GZ ][ bJqoPO xoͻ+sR߼zu?uXJb3Gvv@xI'Y=Ҁ˖Ob1/#,P;^%xb86BTSC&&%w*Si^jI\_/?k;LK=)Yw?s~>43+oY [m[QBX#ȯD3Y, i(_LKDe"#uZOqk7Y4e:D@Mݺh}90He`sl zbX.}0>ӸT((5ۆ@x֪Y:+>H :(sEIrRssqlv6YWz010Mm:UbL#'SCN^قF FìDPo?@pݟ4͓Gh:L4Ɉ#HѬ B5 j[џE<dڮ疂Zk<_a|##! oB@@5#ixZԲCzTTBAlV,XB>MF+P{~k#lx{"B  4.DN12:z5g u\RK،Go?ʞ_bRf왽y_(1!7ՆڻSK]VRl  (WeRN-J,aa5fJ&{#8**JR9e´ %|O]}: tgZ΋9ƛ8Jpi&yiUM[bb1CBћ6viYcǃ}w)_)ז4ϧzՓ4s_&CUsZUjmې70jRv Dz :l$gJpo^uŹ3&. m7 A I&S8@4Hw~]A?lx/1Z>GgDR?Ei0F$,:W,U? Ɖ%eI-t!/}"7"op盱(*7Rx.L^ `ӷMyLPkԚlZC)H؋둉ReEK&zWS n~;ț0R;A&(ϕgj>x yv͜CNkmO${ap0`S;&IE3[aZOAs@Tl| -( H )HFpV [d%y s+Sp9B?=Ђ`L ed8E0<cR OzkDC+֪0T!7/Ed`cs\;P tEĆ""QhUF%=_s&`sCC_m"];]fd m@Pǀ mwƭ7QRJy9䩈 1S\31{dYL =23?M-Yc`[ :=l1,a*EMqz{[>UjZ#Ymso7/ܧ{d3V|Qo4B Γq `dneH,x#i֋+ӟV*'I) e(mḲ%Tw8WgV-p/wU~ASfr%mR Z0ܲ–E5j2\\כmT#t5N6_7k?h)hKCYAAjmʺAZVY=>߯s '`IaOsFUbY<8Y0%}JO jI']FZbMLFlAȩy,j9~ab烲ۮ͆#fff]fEW _NH5"Pƈ}9 %Ձ%T,Roau6nV?kGMCGr OEFE#F,K|I*){Qv#,rll >eS4&0U֙ {Lo [⯈: Z@4XSQ_9?uxR K (g" m<`G!\ o3{:* "GXQ7uAI7FDBT4%\D_L'8_m[tdsz:{ I7`ɧu zyqA>X((tOر|[͇q5>YRaK .48Oa.&jU's6 ]چgh[#$ U}le̐%Ji度tK&<4Ma47͇ͪ="7'm藙&z 3-GUABSͶWTh}2_a)3Gh>+Cq ]I 4uaHy;l )Il? nho3atl&JW`V%yn<529=ycR@Yssk FD] }@$ʀ.ސ}.R*i)݅ ¶xPG뾥fUWԲM÷M]lAR"eFwQ~B}c,*X`{ǿsG;"_T&ڢwƛU%w8׎l=gX#2b <2p=Yii|mcV2Y.1ܖ֋Jbf$,AAՒo:Gx)N4TSzKa׃cdbD$b s_o+ַJ9Mөf=\x5.g:Q%xw1/lw%1|o>rb1cV0B+VF!\@-ۺ/I3bwvH8UL D "=ݎoOBj`5"(N\I J RЗ;kI8{|c/\)-8 y*)M jM7 ,jB!YDpc0aΚ5"#Km։ޒVb z/ @x5UWzzUp֪XN5lz8R.d?'TTMiTZeK9"DL㼑/"D"1:\›i->DI`aXFF@dqb=Mu7u~ֵp3\k3mTgDRȊܢ֝^hMJDžS.`@Z'IH_B aB}AxA*I&S8@w_0T&ՙ!g:<Mǩ>8?{ZIyI%IracRHF{R#o>71:nR`,#6Qǻlen_nI`΍N! [/CE =BBL<$/'䕲9M+0uYݟ&DU KNʺa{|Ov.F*CV9dU/ZrnU,e]o_%he;&>82OtVGA1 C{}e%yT|ٶN7=0C/ؐ'=:қtYwZ70tmb|&keeBáz)Paa^[' G[>R_v^ےku'B3ZZ;*M:ʆ@ߦWX!zVľ{muje 2|vS 81Q"QZ"|X{r V;G |Yp5Ly낚x#m#U>JLڥ&Ԕ*Eq_X{⣃E4!]6}R> ጾ9qq^f#=TZMt%K/K4M?8rEx_"R8W- k3eE% k'yAh(Ǘǰ]iy_WDڳR,=ΐf,>.NQQg߄KM 8$LJ .`!65 E:X>K_xRfS{{T^-@e-ZƩ/9޼P gǩMqTkKJf8O2+ cPXi'։*DRͨ7}u3 %n)b9=$Ƨ=0F%R2~v|hۊOŸMCbjlէWQTg/+3Ĝa7q粍R(RR՟\'g)ȁʻ3 ؞RgPii'1LR9ybwBS_=ȗcà :ٺ>uQ7eݕ|GjQ\E#|*><>O5='U G |7kTH\zwniJr FY"syH(sus+m ͤ=%7uF|U0Q:9x atٿ6`uk_ms99) bag0W{KQi&Ey";:22lb51?8Heb{ fe֋rU.bt bTB09#@ jlY[>U@}{[uMCHOOBػPQcPzMżI& ܖ$^́73CvZN{_ʌLӚ?5G˺GzeZ>ÁӨH\7bYGMf{}6!,yE&zKute_ko˻Ԫpc' 5]PFw4ĩs˶QqbA @Zx\2ɋLB&}ACyB GcHJ JLb!GjeB@L"H%HO =yr0Mn_عXÐdK; <A]]Z 6 d,a}(xPzN V6gYtѕMmHZwǴksa0y+Էjݓ`0$c`MZ F މ4X L?X)׉on}ĸrtn)\q,z4DRMٖ@~_zn-72vDd'N(v o#k_]5*ʂ&s>mk|eW'Ac3f4%7⧢`ᦈ&ZL\ɨw%=֌[kABS@,4. ~ycaZUhQdž{(duySFtOeˏ-|<ІfpT(jp4 9FZ3x`SV t:#A/oRƜUFʁ%q&GO xaPfB0s,{/+vh dRxeMI^RoɿG"9("CuWA>µ_;gX+ep@Ppx͕(dk<ٱ6&*~toVT.VV=AEU-׫nK2O(ymo_;"r7p|3G2nW,'P[}f T%yʩgWG O4!9l|4GD:`=3v(>N꽢b^[.zhA![UӟwBaޅ%^(Gs,twzo֏Ɍ~3na_ FA~ vfa>@݁QЕ,|X=M,I1 '6BފT6w͒bt[G8.X#b.Wq\yjo/9u]; M`VT}dUV{qB]Mzm. zyNw mc_n"'ȨJtB$vJFE1HZP6PSK};za,&Ordq))_0U{_i䮴Q-g2J`?u]z˝c6q=sWj˚t5[- Tf)%dJɒJ/?Kkz Z4zWs ,J;2? gbL+SBwi|("I_+n&;SkDZ#s`@?"y̤AF%WAQt0 6EH'2}]W\v 逹+k!물" vğ>_77M?AAP)3nцHӾG4X/SKt͔ M uo!,aXMV֩)h-PI٬^)sSsm܏Q E{/!aD-TPay|~/c[1t+ń V%[:HHiϟ-`0oTGm"E*@Ҙln XAer?aY@B%^!)EԿغ1lz&@Sǟ}]/R_@"{(,47ZzO1kNZ\MnQ70 uni?X<]7 ٴ1OryQuWr v 9$ą8 ͣzG.i&[VL F(yq\PҿX-[m͏R!}"UB[b0]P'ܨDHɪ(0b ~ CLX{ʡLRPWCʮFž\r s TmXM]+zuŧK1dRz3.z}Ώ @^S(qz%.N 8%xW3b Lj3?4? :Ñ5 $ $I)A}Qb Xǧ,E]>=?ޝ]:YEEI(1PX,*"GC1< z-Gxqb]KE W-vNL|HK#.g3_ssV-Ogb!`b$ cz)Zid#IJ{%#\g @54(x}֚JR#4F0O?8p ^iiv,kzjT45O}GkL:=21G7TӛnEx1H,WCߦ(4q2D^gvL^]sb+b4 gŭ']$(ƄB䩕DkCAKI&S/+* ͨǪ.UĴޫLI݊&ULEĨj9pwFW(~_4 F@VҨ:R(`b^3~.L|]lv3~`)4 -Шi9U1ԝfh> iŃ.7UvByIt{+$K_KIyy f54G$u?_Z*4&f@Blan(rWcb9'`[ lG[fvHBȑD׺X2HW u`݀dsÅ5U O}G"՝Opx}ĕ჈NKLsgkq1qi RSnŤŔ76ڢ֣TF<ȒP!LL!cY["g/Fy; m^V`tFɓau-qQW!Tc32xdEV/boH }+DvW'p9"ДEc0B17go3.v%RG_WBUh$ 9ڍix73#%Yяr1r*qK<xx50u迦d<,i+J= NH*^7,3!Zݿp4%CIi>؎ ?Ц8=*ݍ'QԺ~%`dDJs{F\Y(n.ʎËKm![iNb'B<>$3&4'LU&f*%} ,KgC}ur33z\^Ɯf͒8:iEM,8Qx?@-rP@ZWD;47*W!/'=I>[ 濚mtfv8C2GFhwnEW2Va<|%78FE5%H rôܯx8HSX,3}C̺EwߴþA> T_R|u['-1y._?:RRP"a3-'dٲsxvB*}kQ *t%aPFn?E0v>kaF"xwHMY/y__Z1-bX;I쬤2Skxs*&2sbuKJ*W+J\ : WU9iҋI[?{2 ;3lԠ"ٵ]K%tzT쌡zgyZ|DxebT63}*7fj^ubw=uoqjҊCZ.a⣮- w8[B>..)F;246aaBzobMܼ {8P3EI>ϓ\ 2O~'S1*MB!i\!v]8x|uM;* :κu%I&'mB.c&bJ#3sU.#lQ<96H^Oûܘ PnWTh.N<ow;R E$5zW=/`/qn xTϓOBv]Ʃoh.MYF~w%/'cMM>FŽ2YLmd8) vN Зŗ\wH)c:8o{DtZԵŒί\x@ žhSÇ+Z+5!QWu>MU$czFSw7O(^ „>u.|<.o!}UQaxO5-qay@$wu:&y֧ȉע' "yl]y/T/u?9-TkWh>@~:ܓDVQ 'x 0\Ü[v֓o  Vr?4[@>>{aEGbkr@̑cKcJVpؓY)-|?$^{dDů<QzRIq^b۹y 8~R4nwO[2mzb `RSnUpS8|ӧO|nA:.-B`I~7%G/}J~ Rz@+$GLzʤVB=&n,a%* '::kg yky=ꥸT_+Y'] L*#QI }+yAFaEC4 !U\Ds/dwluf>߳ѩr 6@Ru'F }z:yZL20s<"e(UY\/!YuƧoseIT'PwD4"<I4>Rtn6'7},ťaN8fE;Onoʨ`g(mv/%Uҩ4ͽM_-c*? J_{>R3B +.[&0o71 PS~'\RmMNpQMjS4%~[69pƬi@dhTUܟhWI9@2پr!C l`uuTsV&Y#=EV6lQqOomi%Oe!4x:CGj>m!fWظSZ|8юEVGf{?|r6H0?A9.im>VemVgJ3$C'E!('ƒ3cNsob|%[޴LE8\q"r5,z;x}P3*VC Э r.CJ]ؔ?-v 4{>T]& CߔEf&YCscڥ`jdQ[vA>Xm<;FXb/0؄Ϣ"!w$PE _AI&S8@>Ofe3j! (ʚ)4y456< 0 qSw75 |/ )+2U+n`e M~33` Σ]R M"K {$DAZ/E92qXsNs5YF{F ##JMH'ZUZ҉ZB>EAh@So*UjP TNc+er3 vVQY/IFRߎG>E\z9R}mKBF- %EM (H?0>no t*}NHp|Ż(ɶzٜYlQhDR6_tX!sTӹVHG;0Bo`r^NQë3O bj#`(:j5G<`R),dX)m*#G?#vR^gQ5tx| &Ṻn#y!C۶o2F ;)'q;6 ۊ3 3c8S?9PЍGCMl_fJ!J .HźG7 %D C aHʘֿ ^U!;_'wFJ* }Lc9ɫ+*VtfqxRGPLKKld9i%&e56 Fg#Ieb[ZP"QVwj9t1H'/EFM'ͥs49HNeM NcS"*ȨJ*On[@).2C8tUWNELj/[St1Lc5a D  -M=4Jd {:[ot} 7<6UwIi/ 13G-O7¶@r\nT.bGn] bLrcNm)Ͳ!2؁mQհR' )/a-hN%"7e鵚&"jTz$jD b[141 ,*ysF/OM`hͶŨ/ jf$Fk)xttcN5 {=PH 5=]_DYmyҡM9d0  04?q"N!h+HW}Z#EA`f䘂 m#"c[HR|1=e! 7|_Sn1I# GAQOu;.<@]M:ȍQ|B=^`)~x{OY|'|dbI1;@qiwc0pkzBK SkRYVp e.!Z[ׇhGPy:P *N:KSMr0ȭY-G~<&f WO*e m u0[@] 3;+ɚELzͿYW2}0N_H$Yl#Vqz!O6!!Gc/m76>-} `v _ ywaP˛@(2!j`|teD7nZ2/5֮ë\-6h~?yKQ9v@DVFw#u#֑ 跸ۻf'V8Zg!t Ah$R1^ Xю no Q>XښO8i(GG[~R`tbL]U!j5ŝxRfP-{ K!7&|Ohzsi=ѡ~^HdoJ=csdLgw gJ8^b |y%AWS 3򃣱CJITfէj)s-UTU vUQ];Ex> #dYekG cҧ[^yx•HB2 "~Ⱗ\k#M!~Ѹ "AI&S8@蚽_.UmI:qCm"UyY!xt@O#]8:ʼDR׀ƴ)Gw6Y7߫QBJy0SS+=^4w1||$o+ƌNCi˪)Q3Ny~Rѓ͎}tcK@اVÎrY6ꑵ^1R5<9~E1ԝ'Fc0+4bUL5A;D" sFyel(kYS1;iU)'VDێ}V̶:NסGmrH ۯ}x cIc䫄}W!]W6]< 焧hjXnnNM3̍6$*AzŸˆ);D&rT?̶-[myg QG :6Q&z@DŢierL]Ye=r Nl䓛w/.|r ̤SPTDϻh2)q;8F204ʔRZK=! ʎddmC8W~sq)+uMԊǦ$ԝ1ōamm#$j!w Yې]ЍT @XR:(L aZ|6,_nY~m~O%Ep%+m|Ӥ^'iBwnxPC:v <[h4[0~v8n}=覃7V>(+:csfuN@o}4n/O?!E, ~ >^ g9(ՂL3z)zjO[$@ q߽UhW*`H׼D|޷@)=]_X"Q-OIa$>cN"o&Q?Yq/&Ue~0z&G HXy*JUnDt!jK!mэD]Y&>ʖI-Gx1$8˵ :-zwЂ-gECMe+`L9' "Fcr4|ƽ\Brba߻0amo`+n?d;hު Op:EWA߶*,Vy"J.l7yz7)Ys1~l/fGMXp JT T8Jr:ZV0`5rX^ {Ff!9l:W&Bq3J@Ob>åis+HaH@Qtv;^ Ie+7ٳ^鈬>:Zx:^D";|Mta9͹T *c3o8*ndV=}CUܚFt]bpL!W`wjxv趝{ (ho^q-RG4q;)uQmcB+Nf*U{QT#v6~D'y6e ׸=ū#Zj`iKC+(;K; W:+uau-e:1/2ʉCw[W}Vøfu^6AVcvl= UnYD>TB7$c5a`wb(|JꇶGuF E ڗv?3]EKXf<+0I# Ht4|LaW7PJL6K%5 o|L <^kUZ/jL%GPka})U7 (-=mlT>Mƴ7fnH!MWd-4Nn]C'uI0D% ? MecZ}8^.ZRjVZ xM$ю!lZ^"2`Ӧ+5˫(aWQ.fEd ⇲N_xi Caɇ9?B30 ,k1?0s q er $) -DC,fjz͞ c?̥2BFK^KbAhs F: Ж<%% !&~~i=;SFGʧS&@irrڃW;?x[j.>KIꋷXc:8a9,ؙ]UX8oQ7ʥXI{RNPe]p95K>wDpR!W B#|cE|w Vݙ޸D#rF[cM9[ٱ6Ŷ9V~(߯`?m7FQ_lڴ@&w۫*ksd,#$ۇ&5oh.G/5;IvMiǣêeP`ʬ(%(3D>TJ\UQx`q2o$#Fun*EyP S2 D)DGzh$Mc8ܢ݃";yK68KNMípdrkuzN:Kt -4 Mn]^g5 R坈r.NTg> &. }kv.~awWuXB%G#sj?/5L (*x}BeJQBXSK2'|!1  L.fpwS=[H[C`o=Wx QKC;,^sLHw9ĤȐ ڎ`]]UɗОMo0׽YKV3p-:մv NoD]hUU Z854 B+x{kWb̶YT()J<< s/ hB=Uv54MOUQ#* = o'?vT$VrFܡ Fpc{9ICa8Z0@0 L8K˃H&a@)(_s]*X[29nO;[a[ʫ `MAI&S"& DR^#u)n>ax ᲒK|ޢTǖyh(:aăp`%Dt0=?OkwNDw8.DNemuFN@_7ߎ>AjMzl|Ag61ڒM}+y%#H*Gb1V+bFYAAe8;j  $ˋkeZg3۳:m6.3Ȏ HYbr)tsbw[?X^emK*s@z[d[ r@S, \4\4ԅ7aYpZAĀdU1@ `ՒNxdz9EÎV,>0#n􈖻|nVN̾㺖YǧHLԈ+3×xaN2,=[#G|47IG_Y}F֥X/'i~=}A ̲=jQiTd R-ۓ~WIx`&*O}WzwR χ5A+غ~-B6 Ǝ%A#VF~J<]V_" T֟rkʹhe*! ToeȻEܖiC6J9!Y-Nh84̫|Ÿ'!Q֥IV"c SRa"JNXBa(!b"0Ix9,IP`.*_OS!/[a4ͱqjҷ̈́JAD>獯׉5 ަ͟To(Lr2'JSo5bسz +$yш 8+i]|JM%J$4HVdS^ȭLۼ ͞V1.O1f:q6.tq?(LssWVY~/@"qENAxĮURt!e8>Obop4қ N@ zFlc'Ҕeycqb^rڛL]4oDCj zb"xL}0g٪|Jdf#]/{ȓT`F<[>&x=P1pR(؈2I -’j0 ~~e䳩Wυb!P;|aʯ6:B!oަN5Y.+i7Ҝ6m̉Et<%Ѩq!F\x9ZA$1Od׊,ߤ\1C gE_Kӧigh1[ީ)>39rc}hpb++Ȏm-h؝ۅueBdg;%C$yqđolZ$chW9JK3gy5S(d۷K-[o뽻fK4spZ4K"Ыz !"<n0u(\owLte+xM 6,ɕU:jnkם߮Ϛ *~kt "ָa ͷA 8JS'HBP?XVD[FؒNɌ%E]`̹ٲ`k4C!B,:xJ8GG_?0+GɈ0>mBz2??5&GeAe*{O::PpIT#{7osJmNj+Hv.jxߠ0;&H]R|{J&jazڜ T^(޴&ziI,G-eMLY;,ǵqmؑN`/g&]dуr.r8sxgЂUSWx~jan}+twȃA,NP|}$4JZCUP=ʟa)t<]80O[ab\+w[%Y|=hqY5P+B_ >iܹƝ5\_T<};6#qht'-8=,3ӜY/d|xq&3]r"D[ǖXfOjfYb+ '496ZSQI$ڄۻ G0o+5aǾS=ѸY 3ӽ>A[1\;XЅ];~au>ėQ){JsS$8WZ%DI݆ |D,uQO U)Q<|⮹*ĸY9B#C 営ooqd&<_WT38G J6x襙D8]j,:zZ?LUTL*f57˭64N̔2)}޵je!OM߫0qKV' j;]n=t 1xMD=Xa]XGj_2dqa<Ֆ@ aw6DD&Kw?MYSw!'w{W_cA̚DŽPӁ 99} l*< .0 ̨ ̤qi^J϶q ʎ˾~ډF<.5isQxVTTYy8@;{= pmnP.:uJ/R?ڹKZ50H&u7 9h;X[6R!as7s#=[~JzS~C.Ҥj>uڡ˱bmL^4慦MX#H\p2S}Ec1 5@o1FMPm:yϜM|e|b.HFf_G\m_Vq~(LB@'Dr E L œw@DI̾8,@PUJc" GS.|DHH&]m\rT54+ LQ0ֳ'?Aq u5߼o!:!Y0[k]'SL`d4`_p&X|,VǙR'ԉ,s 46@ONH@ϼɢO+Ӗ2Ս\~n͖ߴYd7|&/q[z` Fu.0O, g, .g8(^awemF tQAƝWoy>Lė]LUZV`aW%Da2|>]}VW(KiFs3c{XƄb:ӪJrT;-8Wi_xQ2MwC֖\-E(S1r̢3EEod LS%.D>L]FzBUM1fqP^]<`ƸiEz@{_Y: 1UMJ5xaOOazB'aH| ^Z==uhnM$4! V.6Fj PSD/y_9 z#@3귨4Zx͚Ej4˨a`%cUar$& "Vϳ{*WPٰh JZz} .SAI&S<+8@O󆘧A2oV>nEՂh=NL " p>fb }< #u9&=9(jQkHΤ^íGyfw*3v6$'N^4rJUJ@gޙ3ʪ&5^L]Hhh|I 409'=%yvu}2!q%_.#y-\S { ٟ1+E L\41&Tp !1) ==55^; Jc@ݦ,ɀxv\SX}P7sXYjܫ \Kυp4p\3 Oigݦ4wc"Iy+O×뤙Veg-N#~l(|{{@13yZ8˶5XԎ ]Qn-% }FT|>4JMjo&fO9׊)-k!Y=0+ҍ;5&6䈶!3G<ɇCgg&Zeq5hf!MS&or󸥰8}l|!Mf+ڻuDa?,[3N t}Ӟ9%wۡcl2& )ytq䳓8.|m}zy㽰f4I@4)JB4Fa%,q' %VlI@TjiV̽7QIwp\#9yH=z#A"t4`Y:۱}XT( <&LmǞi({ )8kEY֛'8!pÏL8R\H-[~)X M:w+!w57Su < )6f:<`P+I;Cr|]}۬ńlpZ m/0K~~ic3'HpqWEĭvϥb"f/ԒTB!le=4>ūjr^<OLnTRxX997iu/ yAT7)6G7iv@G<=H`-G5w$d#i( GmoY2մb2Pꛦ.n%{kZ\} Ӕ.f-cSbd`cB~+'!ul@DO e0\*?RStt $Y=pTЀI|Y:{ Cckv@bRPJL;j_{Gj-Wb9G F_obg!s3IK뒸M-3.a8E |猲d%~NztR}G,DV7<@<+c^jh4DxL!f+pُmr_Bg(!.juT)% /qXא^/95`G=>˥_℃p-d")PْU3;Oaک6ԉ Po g.pI\:UBphh)^u=8l$'3 j-DIdd/Zc]>]+\["Pu ׇv()"PBHQEDkSeb"RȰ(,ga'J{N) ǶoYV* V;>\Ѳq"6nX@Dx7XB~T2uu۳@lHST;)׆!O-֤<ĘĩNUmH8zaEUڋ]. vL%ANWN h#9C؝޴[:+%䬲6`Rs4\Ï"U)r-eUw{$i*n$@Y<##jOH"w]SX*_tVuԎlw2݌1hѻ=ε`-bRy%䘿-G͝;m:_'Uwj$&zZ XΌҭ,PbERmxby'm 2˕ş j0 6jv&i3}髶/-ufBtqa*hVєybMݖxmxviLa٥Iw5V!5 gslgASSN&?}zxέh/|BU;6s=$Tžto^3ӱ=y}g2 !_{|1*kڦkF*L??<6;Ԡ o" C]8A^eȋlLNUK3=@l"yO`W/xxZhoyW~3$Ԥ 9^p3i!2B9kV7:~Ti}No "K4Cx^f?gȆsiv@uVl) `3 8JX^|5T:L(XNsWjMpqKQyCiaCa1=Zb 7Intz-dWs~\9uM:䴗 rQe JcjV٪L%N35(OYf6*B7ܡ<- ]ghw>Aoz"0%n/|a0ZP 1v#́V~"tg@܏9I ([T*4A՛CQ$ ZqqIogw}'Cb*G;@c#:Nƻ8ð}a4Ꞹ&]qA,'eDq+UzAI矚ÏsXZ72;LN?*3>$}^٬<c\Wwd()J[Ӗ٬$ǧ'K_ŢZ\= ~[[[9mٽmv@qĆu)1ƣliRep('Wypa(M 3adZuS%e_VRb>e5FWG)[=GSj&%5# #!** (QJPVKSǑ H~;CV"6l鳦L$DIHphȷ|b70J1`ۣmŕ' Q7Vthaj4ǬjZWfQzrAP<0yGH=fyv9Fw [ cnG:z{,$FüQM ێ. ;1jBv20{SF O5%K2ɪ9<($Ҟl֓m9<?]{繅;'ľI3'֑]b\et v(pUI) hP$\L_38~R*cb9(ܑcHiK9޾!39NXrAmv^) ?)rܣpL`X-1Bm^8AdMiaQ)԰ BR10Vp2<~֑H{w=Znꒂ:s#4ĺ '?4I 8㑂}&`2<يHO9S?c|H婦nM\"<ӳɽƞF2/K>Y?n r("Z>H"Y77.JL7+8Ću8x Lh˭ei_|+ctFDyo{*wf'xbUyc!pMj bR(l-WmI(%ڎ;=BIp* hjH-@wIFR+ L{c|688`A|{|Ѳk+Mԡ1R6=o<޸WQD8ánH )Vwأqҧ28W#tjTSѰn#@}@ ʲu`7/3!>ҢWG/r(7_5"l`-wO)Cƈ>PJܐ}BWlȅVe%nP Lv@8Q/<hfd Nݦ/$ESX"[Ya4'%͘[qb!jE[}xAbtp],olBz%^ӡCʠF9]6D H%%Mˆv@oF%k!̬c$[܂0%;2Y.Aq1T}BZv`hژ!h^W(ywgqN] a>jlnA?a(*lZ]mHnZ p'ѻ)m2sӘb!5r$UQMӈ=,e-ħ7EhZlѫhGQ0Tl., ♞vZ]_ q[ȵ+T_Ug?7VQ:?ݼ8J :KzFMO ̼;A_ň6Nbϕ wFt9udxMn$a_+gZpiIJ0y֨0=Цg4 "ݳ@RUZrf쏌ON~$Gա:}K9ш|mv\|o\sx( Ҵr@65HKeWuZ SR]iF<J[(H]ĝp4ѮlɳH͖jXUp1_'NgAۿ3-%v@M]8JF3G ^ "bA%E}bǥc@\? uL`t2OksKF&6g1ȝbLߘ@{Ьg zG3ᛅ.)DQKP`\( MrOc݌+ʇ|-mSqgM)\ P6&+|2l\06S?g^ȩuZH+,d !WԢz>a 1CW J:q(gA3I&S8@^dQr^l 517\e5|ge7rj6FZccZt龜=+$խX"loz mwR_v! s|!EF,nH;vhVX;[]4|Dl=$ܷV,G8I҂Q4zH@%XM9=ҴGTJ]5ȫ#Kga;fM_ 嘒ɦk93H F3\?s}xWغN4+h+1 qyD3e0V&\OH{k艉vP)2"יZ+\:3(uՉR:U˞^&U͆)̠(UЯc3%+ = [ bu?9!.îÀjl R s8gK`X=]?U:(S#]̽Tmd7_ 4{1[Te\qCj̛r'atA;5Nq(K#CbYp#8腽N}F`Ww -СUK 4xW?!o)ūe\WM^%׬ ֥bϧC7wP8I. qE{ש%#qOt8dkR}|4u?+'*{y軕ޚGKJSSy綁$(`RPL=:{UaIĵ㳔rbmbpAp_thVDLr ~ ݿfշcx ={BGҮto(uN@E/8s_rjYHlEϟ?ߧPpW.:?Ry|S 4ݭ6@6Tq}t)h[T`"ơ|RE1bSĘ nٟ#90J4v^vr\_SfFD.*g30r[HPE]Q10dBsȏZ>cF1F=ؽ)>(M׵eC$.) +yB6 fvHܓ ?:xNg{3;S6߂Zܮ v~ˆA{,-"ZS)y0%g 5Kui!vWeͣ$R4a3:!eĺ!c`5+4Ƕ-u 4XE^YzZ !vQ]MKa*4e4Tr2ǖM?옊t^ >͇&p͉ݾS8G׺ip-UTW4Jz a, ǁ R9 1LEY^PMB*#XxӿK kPEFȶ>9]E(O]W g=63¤ aߝ:ո ET8Brr/:|bוؾiO5јs[ 93 @j=FJ־4y oy>mrbfY/&YfɱB+Ԍe}E {m]B͏ƕ}!+tm}=I]X>nrA"h%-'A&IJ!2mlzP"!nSQA[B븆Z$uQ>q6?Ώlm2P21N9,yHB<8g{ɹiC9l'89Јn筐,4*~Qkatclƫrztzҏ^PO@[^G"ԄgE~WOTr+^2K 8C'@G< 6 93ǃpw&ѹ3718imQwu|7[ 4FA.5{T?t\} L=WV6o񘟦|cZ1%vHhX|*X6Z^Wd )}cͥGp/^h T!}z#/wOIB_*F-ŇSYѥa0j"!Fg~TN=0t  OHOQo9 }r[9|3k8N-8Zd Q7fmLIQc&e#+%N>}&9GC6/Vfs!?*uB$:Xژl;(ߝb\ǚװc#5k™?َkmz[:L`ϱXUHp-o{U'jI#AL[гRLV0MFS/9sMakl~--ǯUΎ"2~қbtCQx +o/(kmTCW6s;{H2+@Ƀ7:5\:\F~MCu2Ld?!w,&2͓p_i_KEL}W 3;|wnS:ZEIC-$pH_d^o:EG!؀QOC4=L2 +U: )? z0:?}aD;^|OB]*A.Xhr]򐔀I)E>p[r0N,@bGwm <3Ty78x]Mb6LK j+-/7 9vtU+sץN( : ?Y倸 ϶~'il^Wk+~~C}X j 5"$x~GC, (,eh%v (n̚5LULμ)%2' H5)k',Run 2fxv 4\aA"~t?`?> :c,OQKb~[omZ,T ,[c;k`#XOĜ8Ta#tD053L+w_a-RR+l[ _YR~$K^\CZx_yoĎa:%1f()i 2gE=hƈ0T5^O=mctp"7sLu1uyM*) "#PUHݡܓ|/ηer%ʿ~4$]fCoQeT3z\KATI&SKhE C&4^Ʒj%P8YQ*S 3IEh'udj-=D0mKAGvJ/~V+= S뇟%37{l(I*%yR9iV5.L@2]1?8Wcmϣ҈NH[zE_uYqQ4_:;*0ZD9֚R\qb;WĄ_@4 l^!;Cw%&{̪i_u37Y #%\Nv*uM."hoj$Ks>ptI+U /;U#sTR4ZAj/ "Zorm mmG4-2+%ɆK\7^sB(!w2F*16B!y@,wQ@ ^y?eZRE.ksk0:q( ]11}W7/X0i8F\r4y}[sLv(aR-w~`p 9|ե2~XD ٚDyde4-r(uQXG!]}u4ۥ_Đ9󟘼ۊgX_pOʠLU7Spgeoi~n-,wg4hmsPiuaȍLIOoTj7cqLѧ;vzkʼnUn/79/@I5x XMs|A)V8jgUUPPxm`0=ˀ Û~l\eI$>[rŐSOg,r,@E^|7}1'_!u _7ۿ[Ȁ%?.R-Lm8G7~k!No+&@1T4`v4~+}i1Y=1I01Op9ŝ2+X( eu>43e"Ftd A@19Vu&qS0cxAIyK~CZwX!r=:ghNOrɬ(U|mOY<)<fܡu18ǬT]䆾_WB%LR5#LgZ(Өk-%7J)0.ON6dGP;&T<(~Ч}H.d RZi22A w39Aq: J $V/JpڬKo QV X/>4;{d@TNH~ Ҟuހ|,J &8Wإ9قu1fpԠ\\h2bpȵl8a1|/Č%0:OWdPRJ]oV^E٨MyǢ2ݑj-\8 {dJ\b.F)qazeC_QGŏz7툩=\Nu؝(gitE&{u{Q6Eap%m&vJvT)MQT dxH;ݣun|PBlJ  ? ?HB39"(G;?Q[0WJnDq%;B<6l\Ҭ|",Rr4Ɇwg=*P?& "?PIdR#j\A`ĝ iԤ;8c#:X0fHI_O(g罣v2m|{eJ=WC Jx9#! o\cȎ݊22Ooʺ3G>t6,iӌebJߺc`hb꣍|t?GewAl&>{['Φ*:)"fmlpKblb>0Ig6-YT_,ׅ|K$|j|l?N k{PUrpZdkA6*l< (Š ]]r6+U/iM.`\]j\\E"qڦg_쯄},70`>|njL8F6ʦgM1 '%gE,|ԼJewk꫅D65<#[jtЌ?g Qأ7uZ=rWn I| Pi >65~i9XhB# l,pYNs˻:,)σ fNѻv%=wi/nrV>2ZbY[!ĐSꢾhyʇw Ekx+#H:=HD宆U<̬[6i]~4ooPl__PO6 ms`8Zy3擴 /M#n~X ' ځ9xBV OC[oٜXb('y(%=/ii, A|l 5hQyP,lKXI1m+0_T[qv0Q.<(B)/x|5?ғdX0 gLq6@GP8^Tun it;5DCA ԫT*B "axatHzwq > yu5^858b,-^=b4E&N(C< V2R2CM;("u!)z'I=RN!n9{aK*sߗ _%BV׏ m6Bu|Ǿ^i‡U- À5:$z~V.eR(R1mp!5g]a8I+tUk&+Dyo"_$AiQ@q,>La:d`8NE"Bqb@9 +2FF>|(}6^Vs=Lm) ӲX fC$J1h7 pjxI%ZAuI&Sc&uI*Ÿݴ%owR?5$S^_"1FkoDyZ O=Z.,g"{ #@SP^Վ-D, wvYn,V;h4&P^7M&?y9H1: ')Ӫh\+L=Gl2*nVٻCJRWWT0,nf]Dஅ<_Y]ml2̿~b5}UCEӔT *2Di Xv~a FP B>; .Ly{zc>B}`ʝƥG  !LOP ޠ墋:t/xvRs0R?Nm]F@w-2JFKWynώL,svqK`?U.'Dpɚ^?* JAuvǹ R]*kIOhNG77Ȳ_`!?r'$qS5:n 4wtf+%K Oϭ! ޾AAfWbV⚒$̿~Ѱ\z.8Yɍ0J^~F%y/Հ_|Xߞ>5? `4_0Qd<J1?03"6nay:dRWxc!Ô,an)q`1rV/pt7h?5b=z).&灚?[ xshmf%!@݆N!Af 2Ŧu yUc26rG$vk@ۯV-\LWS[%{wɵ5,=Y\ 5X|5 FYRPMydzY 4_-%Tmm{=}W} _SR+l_͜U-Iߪߗ:+J}Wo[*15FMIد(]Cʱ&oS![y)f$Vx%:EmЂ4( AI&Sbp:zځ E:PIU*'ZQ6N d46`c(牃pXc=:dpvc#xÚM5c0 ضlAmn P"b)k P7^1>wvzs4ߛ?[[ WWɠ}%Aݲjj.+4b22lGYZ]`IM^ Qt￐I03(pXQ913Aًٌ笗4?wH!.!;[|;Csh"ƶy#9?^t>(ʛq/w߄CT hb̶K)E&u~SεҴ|L|bes-&KTј 5 c{`)!|^x>ȥ} d6&O[֩Q$B.*{! @X2_I#|%Zվ;&BP7Lȁ*2q4Pri_J\$~T K<ϣ>Ti!6k e mRߘvr.3SḦ"O~oa ; W5A:l pWqb޹7&웣Ao<#T  ),NzcţsO! >@SC߈O2*}rA"J>79R[јfY)~ +ܕC7[y wͫ4Z)n⚸hw-ZP ,7sp,%EV(cfBeͼ=S{:8UʪV3`J{j'u(+Yd0l[p{DAf0 ,ŘwkvLG??Lp1<@ /vGɕ)r@^`AՏA'~{gi{s *6ӥ­1_N@IC}DIkk,X> irz?O)|{w*z֧WL~ C4z 3WIv+$#])8/> v % pQ_Z}^4xHOہ,U>M:GZ4[Mk~X4}1TGQI`A}>I{/kq:#2SzWf3>ioă{ӁDOrg:uf^<7hQcIϝsLv3dKK-lTs 1g8׬v:Za:YEGz7Wv>P=u&#YqB_UxaoY:7PlHS+ܣ;pag@8B ]t7+\ASE Ewss/A7W|#6 _azm!q.ÝIV{cAN z&~ j {OB~Â#,~S)2n.KHc,Uq[0eP_h Z7HiqzRoD9_fډZQp\Hݩx+*[ gShX"0 pcQ{'7cZ誄/~T)y"v영0vPvQ-~1@47x/ |XQ+LA9^`C( E$5seFDWxX.05 zIlIf;C9 |(8Ầ1`6UmIQIOSnLD+kmʡ}\ guAdjFr /RxTNo]nb.bB̀Djz_4spJ j0tkO9RX?k3ޝrjW}" u?wjl. 2IZъ9|x#s ػ[ ICSL@gل;Fr̙@A(.La<٧rĄF̕տ ~)c{;c>>I|տGI%qю g"vBS eVJyq 롎QҕHL3^e!α0 hSd=3CSgdN2cG6{"~S"T+^R7Z7|&Na–[t 2,dzūc0:݂{n B;dkN FxįJRHZ7j6vF%ߞ6ɭ_ome[q4ՃeѯPH|HH9ALAieXj˽S/&yF/Cmq҈c[KqSsrWb[.WHg}7T%g)a΀2?GQk)LҸp 6.q_|rA>f3<=>uuQ%ծ2Ovpٙ7n>ݓpO,2~xµBd~*$ݽnP۷"ѓgǪ[yZ=jľRt9:4v5 ;/H FA1)h-b?^kI7Q'D$=ߦq#?J˂P$eAu:O63^%Re &Iӷ=LPbݤ .2 ` h>, j~| a&29w 3ί]*vmL_1Co ZgݴN5PS] ^PEtsƠ"G)S1`',#VV ʲwj; FgLf%[Nf[eK wU3>b_܈}U'kY4}8`N 羒Q̙ vZp)+𥛣,ap c=/ѳx`D2e~Oٝ.W&MM? uwwbK/ (+^MzyX|RV+sټ"\4BD^b*,T fxt/wO0织jS+GSh\A`Jl5):8)\OFH tfãOA,mw!H;}_Χ²G,cT| 0x*W>9Y@R[N"X Kt?SH{pS =}2W} ݃ dC.wArcwZw`%11<4\$6ìB+}>Gvl@Q:Fq[Ff2=dۍ6lph8pjρtd$E\'%zcIm@\nl F c\2g8|bHL>=3v8ffOSabTc7F/Xw Dd6vƭ Td r3=Q?FʹcIIs߿fnڣ/ztmD.7] a6t^2M?BӞF8Vn_rC:ռ"C?yEymK4(7Cdcڐnl_hp\5A(4CHv)xmz7Q~Sl- yBUF6eee54,V=]YrːE4bSd֜{sZ~w9mqڑ]H|1ͩe=+cn#"EbdzBئu;F4HSkaN1OD-PLF,H_Wm 7Ϭ6=5s]A>{L/M GN*A_ &RG)PXbUvmz鼽NT7&f^BU8-e,Zg"T/l4\xH%+tv}SL~5TC"-Yu\ ~"@xOktsƗI>s.W} teA"<o\uvk[GF?,M@rNJD3RX1R-׆֮j :]/C.)Lvu pWT()0y^Hҍؼ;w%8h#5PƊ{k@RvN%,Tzl'X0 w5;%l'MjZF0JD G"dAZόTt`WƂSx{w.t:11/.(((uZLf3ūq7XP!Bg?i wF JDBIܖ G9U#\%v" 2\,G8pC{r9iiw銻Њv)3e/Z0:(p5V3#2íj@|Fב !uM[1:EeL(dqjXC_37sye[YL0G SDCΆW%c%[M*jRo%JH4Tp)/35*pu]f5EHh 8d71g4rT+1燍e+BQ2nuR8+e?u ±W)w\ B,F d4 P&f @D]Jyk[ס*yoG*?H2ҩV%Ӻ\Zh'lMlAd0GE'R+e'JkмQ[ RDK(cgZW_r;X@Z{]L>aU')W꯺]l``RLBBNryg>.6/ ͧ3:S,DYF ۴uЛg[1Sp5S@:Ow;̵P@*c;3tS٥ȇڈfy28Ȼ%ν#݇\3AV]|Hx/j#v,zgRDq}T>]OQn)*`@>أkZ3Pn1}Ӈ12|ѶBf {(g"!gUIB뻬l Tw  wO1#OLt>耎ˀ@U8}=;Jӱ( *SHz=]OFuttF7}[߆J4D.L$N|>s 2ˇ~;Ln+TP9\^}N B99z902RuLX4%6S. x`uxd>)EA.]+G*jIl'n__ ^}HszF_X\Cm m!M9#7`g㹍ԯ?w"+5t@0nE2tlث /bw;-;>8J% b Md;&*\0//XrhZkQ0][ &.5o ^=%FiЇ,iy%*?#j9Fi,u 'C}ޠ]^ b^ gD6Na9xY-zWQVUd>X8V4]7{s 㠈cVyEnt 7#mW`RNIKux3ܗzׅ9IͿB]Ro WK35_TMލa +9#[ 'R+ s5[qf+϶ >64uA(jJ$!W38ԼѶEW/e[6lfXFѐnUjIۯMkv:KE[ x2qY֫F_ZD5]W<\ZgT*$^lYzKn³ƤjeD\&BM1ˌ_1BKS Hu:_wNW8 2;)jPh9JYGg)[ǑGhʸaOS a;z,CBqnQ:cd`33XH8jv}Ƀs3Rw \٦8|%SLAw69hDʭ/=i+<7kiCXB٦K2O7ax71r @.r!k}Қw0p-$j^8*ʺȣ{KPҧ`KF5\2pK>)E JTZr\'KaPb!t u|JՄ|)2eѓ5:"IeFow at3Ոa'΋33&zqcY8)rqc?㉰"0jT{ⶾp(w~Ϣb)]4<HaVBPc|S!z OscՋ>L)O4'*/>9f;6㐬.sƕ6\KE:+$_:h~yq//ujX>LAیHZ+Cq@JuOsgo+RhpgXкې<3a6=f-arHF4O#rsNʌ6qs/mI4P s/FeRYh Zܫ&=ct79)w 'iRjE~4~H24O'6յ@R@VO6Cs7P&*Jt5xlkW ]~3?#FoEDjsRY/KaRDUh1'Wkp Vr0ܧ,naG:r)?ѐYI. ܇fR7DGWBݧE]e! z}3OF͝$ +pr}KGSER5zP59Jfbfyh"^s=\XZ8 ~t0+2!2m@WRWgh{m.pV 0{ob{[|~Z}i0Yl-T\Vr:2UHqj~ao]O#ލeo/w4>sxU@s_ږ/^ Ɨ7=H!v:CYJ+Xح!%GQ^Oi7?iԣ/_, +2%~nȔsQ:mID}bTK_n.+C9QHhmNsegCT#LuDqK'p;%٭1;SYփ5M@DA5>-N8n-TfΪ}ḨsE{g2dNShuPfg@ϸ+ N6<ΫK"Y'L '3Kmx h¾gK8F%̽'?vjH>C_{YDnW-OVN>]Ȝ ^p@$`I%= Qq ztFp8L]ZU oƾij6=?<9QK#K]Nl;a*d.϶Kh 't FCLp2?!-d}eULTt=#A  1ZlaLp1U4~Z5m%f֓ Қox8~,E+&n{*Nק<0n򳘼HEP,Qx)<蛨5H" CỊ oMcd+ lƾU97%ę_>Ahas&BL{㮥NsST-ovYŽ"\(ɭ xMi@ mvIMa[++ޕ=p3NT6]NjIiY8?kO&^8cp"!UY+~Cqc3WĄo.~oW /;}8Y,8{",iE7q]&UBw}vJ FOOnYN%_۝o`ưpƀ%Jc0}V"]y|d~-#X7%ΗNM'Ȯs#9Q%NzHD@2c)0imZ_}œx2"=%OA=R3)S Ί S]@ϛ &36~XwhȑrX4\<0+@ŏ,-aKUwxΪE<η')ш1I.IWCO /.U6֕Bc*|D( X?ŢC.w"Iĩv-?XC*FHo۽wk~;V7I(ڌf\w4vV˃LjH=8?G4m~AB" M}0j9cVKxB,FHSNwszE`*7t(?-iq}%/Td%-K!5z̯2KZK@qhC +a864qYjpqA'Qrjr!>TxJIju 6.82tJ Aki@ۂ63| AYZc{;`-S10ҡQ`.a%`6QpXzS=&ij!~ `G;tYrC&3^ s_b,Xnvvp6lLbPPᑴe7|w<<?^u;we}k+ocVHm6d˟ 1vPq"zⒾJHЍ(8}}2K)*uT=yTɵ;ިAj3 E`ԟ#emsi,fњM3ВBk0_Kϥqۍ|03#FKQ`oR_}XhyQAx=pi۸c{~XY)hV?dC2Yr:c䖈x.+O4H*5DPG`F WQܴ(@B;_th(-}.}Qi*NP}<,<W08 R%|<)ohԽ2|#4PD@Z;ʯƁK]vFrh^;/胢."4 e$B+TS{^xU* 3he+n"R5 pVx8csj)z @ R-AU-0e o\e߅䰴Lڳnx(HkEҕs듰Ga(·{6>Ce獊CͲn)XGM8:ei%\ѐAr3q#7-2 GcCT6b>szmŷTa|dڢ0LL Dz>n(L~h֑Iw"挺F߇猌 ЄՕiVy&aVt j,NS=(5MU *B:]Ԇ&9BQ+5 ~#p\ v@7yРj;AUhD͆Ш BHkh8=qGp< [,-I/X$X#c T+A(8OվQ.Do|2WIH@ѝ}V̔!]a`Al 55p1H8MhH QeyzE;mG' g&oE; W057/$t`4_0y [:.Y&(1۽.O}PvtfMz9p QUaW-k^Qp*&eiFʏ%0{cx)$r͖2S:s(语5ӗm'JTϱj)cu@x :2/0rXGm}Q 3Em!sE1A7S6AIE9~ex|[ Ā-Ce#in#Ro+$ma! yTr-z)--Qp|R7|@0Ww:#:WXg`GR2bf bl2n|:[?Ae cڤp)w7&  <]fW5rZY.iJ4>`)d]tq2-;; k&IzezCou;~fiFp.tIh.P.n>[:8ylpps}84mQcӡfXC)U kAR9Xig%Ln |&ʠ6-|p 9 g053Uv@<ɋq <*nW/I:7F y,M@M~CT0M L5u57pJz/!䥢 2 Gןf>TPm)G;DumY]y\mp$F4YW %ڷ1}BG&>wj&6!!te_yQp!Lk͔,K[ qkh 9^o8[AYHm/yִӳ /̸Ԫ/E~?@XJ,c8hl R8m;}ҴR7Zt6$6gl+to7 \Z >g{nyJ0 ek`P (qi,yTG8z8_`+oj>{y=^'yA{6A*! X KgMF.#o(iW5^Eż|(0\>ڣ|iZp!*YXlW #:bS g7 жxm3,-6|˲χ/D$|πs7%y'k$5NB(+\-g’\qؿT jNѯ|ԥ|Cq4PoV*YGm1f`/@'NX2|J%CLiY8~3r3S~*pjoU$è<41ezӽ4| f tM֒k<}U aItK4iS#5@>S)e뀦gXT'^~cYlӷ7#c=댇 Op9V0حρgL,x'>!GiˣG]bgusd ǴGͰq5%<' +`,YivOfe ʤ@~9#̡wh/v|vc@cSтF'.% 3M+6|>>6HSуc}r&Y'$$6w®{^S;lj+שXu 脅>i|Yi3!& )'w~O]c')@;bmAFՐ){Dkw<|a-0 ,ylac}'b!zD w3SmN4gdxdnʃ!plE6xM썩# `P5"vG** PR\nh2])BQK~ˉ#?dO5y]cXҹ&UKi0Ie#N KWn30ChMzD(/32QيV+ ¤ Il}.!)i&Y0IMc1%b1kIE:8RB4GىFqVaie<)V߿ۈ]tKQY:i5P<{lZUm/'^K!t_Okf=9ԪϊR˚Q%-  dX($2gn[6J=aZ#8N.r8.zr^NMrQh"51e$3QjSqҰkL#EB0 xVOy81!UY!MU"ĕWv8?)W ]pҲ)DRB:CgpA!lC?"yxh2ߐb[dMq'3(ELW˨~:sLU_bjU^( ieB^N@$ڽý+MM!j Nx"0|ZZB_A܋" z(0gY3tgia|RS c(k]!)iuwܮ8 @נ#G.뉫r"?X+'}Eob]9L`JiPnx;J#ȭW5 ʼN\3 bb ԭg._[)\O㾶$L4){}\sA;>3hQ\q'G$9Eu@gzy$i!.uYU5*PzWAu~zԂ=ZC㟅bN7sjaS2*J-ѷeCco}j$2bR 궾ۋη"SSd26pABC7T+ac3G.o۪?1fSL=_E TA[pEP)RF#I,oCcILAjaGk~橇4&fA^'9JsQ⑁K2qx[oRBq;43x/ME3PܞARz*qU,`Pk@"1X=b̕huՀVhNC3ILe{ík Ţv5~"QPIzPMջgQD; 7mSyhKM8KѯuIdq5g=~-i#weC6- WҸge$s! ('o975gCJ'`TzV$ `TYL8}.;į"{e@1'-%3I]ǖZ2lJRR P7Mډs'|-`(i;> vY/L[z ˼k(3:l1}' _/mn`!YMf݂[A%VK'u=,iܩڛ1߲hc`%尵(IG7 Fv;V=p 6IxQNꈐETu9ʏ+ѓ7dVS̚?cL/~/>N_}XVK$j3P#xgHCay(~a+xHґisF3pxb>W)ă[l,4o¯lSM㺮\Y:}&;}KXQ  AW3tILtESESj aTAR-elV߳ eyIdN)&P|I{a '@tNgCOt$k" ZˆT%{6\ [ъs9  WMݲo㟐C? "D@&,d; +LF A?. r3[D_xVO\jrWkt$33ޣ*K jk' Vd#)mV1/)j`\jcI'ԓ8}; N,_CQ;Wx\3ܔ] ? O1@YxK*\)|vn-sB(k `褴 *R}4KtȕWl b:1uj%N_2ņ6ߛS16DmGP[eÄe'n3yE;.BO ؁Q4$쬴O0inx/g6Tk1>D:l+dgxJ%n{}_)ܭM mfWSO}UsқWc7qM}Ƹcn.H)l6aG^k2tvZu9nz3\$CqK=/o;mŊmF,[z-wKV]> uhS 4WO+'Dq4P;mI xpaDuu7q`=P!z`S ha?k_rM~ փB&$,7x.Ò8"&Dx\z+ >䬐gD0 b ꒎ J[#GɭѲ5 ڷ>ރITPL ڸ4'D )6ȅ ^aӰ5&cy:,B YO@י?٘~u ФT .IRnHGG丶8.S`>_fR!Ñ+:,]"yV w;[<;(I=K~+gFQ1Du"f GC=5"1%sS*lW.ooVy3͠~9V=;sȆBQ}p]> ;I*0#ٹw氝7/0ٲh8:{.a,- l:xit6,mN릘Ckx6!ݐخ@\X౱}ˡ/Vg߫7`UJ8j _֗_Tcև:]EPw/)#D }0w'fοځyt9o;EYS,Jҁb<nroȐ37g#?jo}<̣I<]f2:b Zρגb?o} Y"gO ^rhT"5Ħ@M ~Bf0x$cP%J*XE=TDyD)6PUo~&UF2'3}nd>ax(l,̂1pIU /*历|;t#Gn]ۭ$g_ܕ.#`0Zo/x:c)*o!CX )0( "ͬ(n1t::y ل&,~o)﷍|$%Dfkr0+8×<9\;=(SmJAGܨ~NP83{yAz_PB\XQtp):ˎgWo{d삥g7>i3mE|< {*Vvv-XoGbOtlHMě2 svU*!3.#b^KFL`sȴ+t cp4qӺ'@޽{dJ_ǍXi¬"Sf$,jchf 2>vh͖i`GE?m+H_I(ȸXojr­R^鋽‘wv9Ȥxx: D`ς[3q1[!o\uYn?^&vzA;Nm4 W'uui=)9Xj_Cr[mwwlhv'U"gs۶GЏ{dT zlAYާ3LV mz¢%MVlmoݙTI%>Xle~d/BVI7I"|72ɻ JWXS9iS¥wbȝ+ZK~H=0! V=gO|=]@"ke{H B{z 6A{ $$WJekO16 & '+ )F!e݁$,ov{w_z}$ Ulz"fEے}О7 u J@D>E3RF#8S3]**"W2uW_[1 &}f+^չq tfZ"G(EtِEPOp9Z0W忮Im ~D\yة8!skq#˂rx9KJAh>KV:1Q<){_v[B/Rbϐ1Ƞ5D 2b53),n2勆X /xhuծ{݋wGK[* mRxuʹ+x*ё#3oU}DNXHgCܓB`e)y1`Z /)Գ8sza9np苳 ‹eP@l'u{|p|aD'PS햼Tg"A|JV[ɼ"=%*GS0T׳6 ւMjO ']H#L6UMO,׋Z 6 >&ub5S IumvAQV9M^vKgӌ1u,utv;a"/RP@t;D=^ jК3uyS~ JtYzlƷ @`*5(=qXc_irC"Dё0&:)?m)@A/&E7>{,*hi@33}֭!s5g"<9w][O6@HUzl9ZY' e *ywET]y5tL^{ .ju>&ڌK f= =p1 dR+SEuQ\$O@,̻[M@F8 )UPLaB0 5㙁.>}*ۓv$^;v]n}ȆP[F5o}*@-1ƃ0SKpHUc{`,e>)* w#rY}]Rt̕no n lijp9rkig9hÄ;03ys?ɰDB=-Tg/6]3l{L^c1p})AJh7Zω%=2-]Qed5KIc 9OT[FK贚J»EUUXRbGm(8Ϊ_0M F̥ӱ/(*]EJ{)A&@LZ &PY]+[֥ʨP ' sB/)^ Q~)L}Z7 oj55lx 5RJܒK c@~ZO̶Vs;2v@$a*Y;<%mAr|l3E%gyVaXQP]B@ Ht%s`RxB_a(ɨ&?]8WD"^셸 ƛ{f1z9b 5[8Fq{ϧC de/?~HX R1geAdI&S̥Zw'#]r~IT c% m=:mIdK$T"ޏWh|M<15ّfoםoY/=H}L>6gR:%S }a')0{ rW@ݪZz_Hɍkc b_44`.Id]ŠJ@q&ox{ZS^:;C1yaEDV?ܑZi˩w!0ByX7L btDoņ#Y2y-]*a=S .2Ћ)>?UKw U0p'!xQC&cV0z+Fz`Cϭ!кlO>hItϰtu6h*'궞ف6jkk`-6W| *"/UFeSg;G=Y|rW j%1Publ P=BN:  D'&(-@'R-ҹ\᱙7I\lS(ܨܰ_ƥ㣟\5yf& "HTmnV T/f!($r/ᄖ=hݑ^RI{$4'Yqo'G0߆f Okw8 vHjG7%I+4vxq}`!p|ֳ]i] 6 U#j{BQ$h۠ W*k~s{9ӔIWX8}se(.t??3u h>.Ĵ~񢤠d&WG82:8 ߩc3];=+Vy0JS0XtbqS WhkZW;T!x}-Z(z lF j?_dDq nW,%/@$c|7 >F-_:>9S<ƶthͣ?*L=Qa%\JsIdDZ{05,!3 HI/ ThV,{{㐪B<=^ 8Q&'detݒ4]_}aC8CYH X~PFǢ|$?Xm5"3g-HQ3G)/p3De !_0=n'q62ԝϵjzb:#cTL;Ic ;#dɫ)kSm%P&QDl=7lj -[D;sIӉSH0gV/#)S6礼#LQq`7ș!~VݑwJ*X>)<2^u\Kzy4.1"ݬ| 4A0ŦGΟ^\ú=}hڿ|=`q2]F%isIQ@ y!#ƿd`|~;n_P KAtJ#<D&w2@ĢH-rO(pD 2v溻Y3wtgz47#}@5)Q0$}WNYCAQ97Eʀr6Fo:؆F -Mj 5˶Di }K6JہG 8ht_su=(pvŬϟ/xq^dK@+Ⱥ:9|^2uWgva8xn]$nw}Imk` b6 ^堩H?U΋Ŀ/6 C,\-keCB}z.zoAZV2=)oɤr#o-**Q 皡 b]魯ٔz`M$2GJ8},{QP "f K\wwq(#nfR<$ɁnAW ْPKÑh+l$l=yYz+y/ ω yމ;}\RֵuF>tcH\"&Y 9;Gd{ӀӰ0u"*g93pV)Ƌ-XW q9<ZcHsk^8>tVh}D.Fc{8~Ѓ|4 w} ߵ14@{lNn.!J(F)> &mp\OM:y\m_ÞD^fѹEn%l p~FhVǩ>cPn o-?T VͨJz#x.rDzJܞ ٣ɧP:v>|3lӀ@j`([RU E1m|ڻ)Q0ņBn@HE|N*G 4#~ /@R]1m#.A<4_̟xtpfmfqm6ըS Dw#(qO9eG(9ݷA"U{1XcV$8,䮰Y²<,&jRs7;&%P^r$ "FiY1][Db\8#Ю5ƒ(܎=k8-H ^7vyޭCQ BL{x~5L )fTˬ@e.9'%^Ƕbci٧KMo3C&K3O坉jv`Vy.j;^)d%cZ@b; II (0i 6|?rhMC:#bai@t;I/ܩp,"ם&ݘ|X G9pVAH i/,.o8a(+o jBv]To`qlf'Û| 矼q!&y[_yq[*YVi>kCmHq}ƘLTep,?3BfMn1+LbTRUAjȡBGpvu(2oЙ2(ݷj³8Uo8D)FǸ|p ԃSh^+끟QyEU";*\r$r\Kx\;H:' H˚!4i~#7Nm$*5ٓZ Բ5/L ~rAk)q= M2Wj+'),`+5I ȸ/@wՄ GDor"nLg.ci馒` sqe#: ,i(RYf^љ]Mq!|$}W8;3'+L&+%M|:eWS.yG'hLCWLA('9Mly6\EL3,t£[D}Wy~=7^[^8)-$jfL%Qm/‚~a~V_~#Q76z|_܂@7*DAAdJ) *&YK!sO_qx_n :Эn43C^l4gf6h=\Ȫ-A.rI8^3>d[7eLQ%Kk*Itv)N(>Dxŧ{֏b7DqnA}Vٶ4oqR&%tܞb7櫹jt/4[YLariE|0Etڵ菝Vc)Q5.g0KtƿsySWZQt޶Fsn:o 3FkphH+YI\:#RZ˔mJ0jbs\{q4vkJtvF3KX$J$r9}Z3; ʰB5 #o;Gpg;i_lNEq^8,֒2fIU=M{n{H[1tIaAR$u*u5`Ji(nƏ<}YLoZfn_X*%Mu-5 ]Lc6}0L3$*7E+~ɭLېq:3n7qSJ>;TzzBPKʣw1ds@%Sx{%={}Ӌ$^9#<v%+3YPLj]:2|(ph9 lei+IWa(}(J XW&[ؠ6pE$; -a4bG},c|T#IX蜢nʛSAmx4]AyuזvQ7W !XtWtb܌lgg0QRY+? TD2PZXz 6!a:{ w8>jy-aL!ƻљ }&a\"ׇ)* ;9os趞J#a;;C,.~ /U}/CzQ.Xu8=`H ".W5Y4ę%yfzqU`^^cʯ/dlA`̏$m\g 8*Jai*i@c4zN>XVt\BҼ r2чeM`T0CihC%Z:%(<Z0BRtsA&,Tgt+7k<{3[9YzCaY+Zl33%BSΦyxM*E" `#rm3=.f߼Nc9(#c:jJ"4Mr15 x7{S_0M;MG鿧߆4R#ǁ Վ:ε*(;2N'Ljd2leV1M=U/Ӈi@Y{yί.X ͆Z֘i7_CTבP4T&ENP8)#aqpipP)3+όb~[Bv֔[x~L|jOH\`pcw*+Fr%*eh<~N-d!.zQrd>_7R8`*oaxjU j]c%n r#Uu&>aWi*Z8#C 1 {;La_no_ DqdUjUocNc3?Ʊm6BX;74tEwH5i$ov#j7Zcj6ycƋc'qDZmbbOTODR5OgS#8ݰ'8E=,h؆]:Օ!mX5H{CE{X9D@6_ma2S4"liƶJ4 `/@wl-r f,O,[ =Imt/yu"29}?,X5X1V;SVXPos+|Kۚhoh!{v\;7",wLYp Otj߶hf_yTv =v#+9}F1WSb]+2~S/f؄omϽajwhXz-6y;y:!n'<)Lelb~D%5]N}w B.&O_w &\iJۏ1AՌ#X?!FR~~ UxƦ0rGpg\/f$DӠ`$S$8T4roǝWOl3| T2O5W?kݖh,L>9y,mP5UEI|y ՊJ->H%"%#ޛ+Nl~@'L}>'=ӸJؐ4n.LhUZ螝Ĭ8KP竴5"⧔=IV2IPAF'#cIzžCXu2[SҊO U"\H@'ꊠ;<=WݧFn,yK)vvW}rPqi> s(PB 3]ֵ$7=PDs֙[2bddis"ֿRP#8 Ͷ6u H-:\Aї!xFJ l"WHyv8 ԠY|0NWcto8pNQAR;Vn%L'FJF*Z[ծKuӶA鸽КG(/hi1q:Ňl p,=+mv\茩Pyi5(Ncwbp _Ph:܉>20\@1>wfjVMW;v/N0~= C{tYEpg+,1@ң k0:]ҫ\EcIfEJa4(>M? QrX\Y r{d_ܥH_TJAu6Wo58>۵{FYmѼbBjҞ# t&%cH|չD1*T`5KDأ|v1B*&V(v#^ "mCI5]]Ē!/N^Jڊm9ÞXIA9$,ok勵f$5Xx:\~vxRq%1ʴ%fPhWX-0Ҡ! /*Q*q%?@,!@(H&5#.؇ bF`:c4"Ƥqa5vw /JAI&SL~''9')k @ E)ɇ Mk,1/'<-aPy@5yMDjõ\+Zb2Vί}wDu 3 xυ <ti &p*'QKO[%i~KW*P )@aCtebO"xDLEK&=9'3'R9p`IJ|]& Y/bE bn䁱}l?ZMFy=r!-_Ls?zou:p8GxM<WLg3G,Q?yTS,&;dZh M<*%se 2k(hX/~&.a>dur8y m]Ց%11F#vPLJU DFChx9+dO U-9<7EN0x|tG+^W'yR2ѵM-GVD_p̧XzČ޿; չQ7t$N-װ}ͲkoGc$Oi8{ѤEuRlP2nL0 ^wYG5‹ {c5n(UpG5pt"(~QLj!':دlh_~n7JLŸ!jՇ9)a-E$Y4S]l@2 "g*JS~ n nOf0y X@:Pshkԩj>}Z`)*F+19J}PUW7J[!(TzA>o";?+Ȕ9YVu]Ƞmڈpou9{Z'T Z>NC8ϼ%ngk AY.rsfg5X=~Uʛýcu7E̝qD8% 8 fa+UWuL6 SQH~jڀ[N$ublTh]K`#cI+ i9閍z~ҥ}W)l[530 8`y7 "Zcp.ju c~\?XdA7$ ^xh8_5EI\Rj,1u]z J#*ڙIC0l_gB]fy`2fC_boKkpf[aT+^}1x(h4+ӀQ)?< # ]+P.Q85ÍrJd}$LEd؆ɡuq9i)X{;3DX'5CjZ咚wyw# ;eH  ~;4Ew:* 3\sk}&PPA"9lj!S:%bKLʋדGIyiO=eWWzE`)16JpU=5)7d0 J7Hlٴʔ?mR IOS^G^[t5xRQƆ^^O3Kd" Iz>!'`wцKx*y5C(94Ey¿Pa!aum3Yv5›bjKBa=/<8Hm5,de R٠F]' v="t5=7KRid> O턯MY5ܧe|b7Ӈ[2QMQ#Y򢋓kvJxt$%5xXNGˡ?)Ȅ{U`!<WO8z]d!_K֒tp4~\& |J??:qzBI>Em~Qql'e ϣaey  -S;y7w7c\4hچew 0j*ٚv8hFfCo{7Gx7 !h_ɋ }D VEll3;b pFvqCa27轀…43Խ]ȃ-} _)OwRXf1y/N71 ]cW`(p5K3^njq\Ut3l9]Kh9hk82%گȗM(]\%QvmQ/ m7spB*+:,ՏJM;+57PÖ2ɀ)1KU@X\Zs*:XZ\oU7̱S~!mBr Jۯona -&* {n[O{uNFԮx{jA+Gߨ[k{ONf-+ ,Mt8c"OX@wLJvg&S/!gGt^5F]Aҳ`u.劕҇k9,<s 8@)H1gcXeJ.81 ҷ`MA|pZ5T+}Sl||"eW&Sj[R>NŻ,Q+i0NSR H$7:w5uf]5[jkQt;F.^;=?r[@)Xϊ>"r^b!)[\7GI<fD&́ۄ#wgv3ߟ߻,36!˓6KdU"T!.ZZ +i"Q0D{ ^A>OW)ŗjp5}+8<5`%`T.;}YdԖ;>81# Fz`ȭf>hY;W%48^@T;N`*.E;H)aV^$9LfdmUܶj*O!q} q$#:̆(?P]s,t= D4/n1k+W~S?Ӧo&XL pOgI@ ' "RT؃ TjB7, FZ][Q񉯮>.=J~vGs〢jϤN5?Od gl.@V.g1ge L&0>}hW*y1ȫ w|pd _-#D CS 3Ѯ\ѭ ou`KnTKqv ]a vEVcgZ=8hX"|/l/ܫԆyAPKXy*Pr%5]PSVpq|{|^EhEƃ LPI+MIJU|,UGq& |(<wN(wU-͍+󫌺a<,Y E9T2DP0&fu&O$&j :bƾKW .Uv45yGi%$(NP.#GL ^GN.`*B91?TΊƬTz< Dyf:j) qZ0hoM=(B3>0P-+5{m* GM'ʁ䧈c79vv,oU#7usaj]Io9v`1˔3|No faؐ' Հ0BKۖ>=FB=Sξ7p5fDWOql^]^"O#'GCvMMSH%z-W3-H^,DZ,4BԲ8Ps ,ERN.(@ o06N9#!24<M`e #RM`#xcx"YF;ᑣS: ·+FɁEf McDAڃgiV(DƓN5+G5L JGnQTb*@&QIr$ "Ny'YT_^%s5'"D?wg-}Ba ]1lROH8UaT @OR&;ձ ߞI7;v*o&\:7}bS~n ׺)[YzhJǪqM9w%[`^׋,Bej!}hţ rLKBs8a e0V?AI&S8@<;3lhm-)u] ,"Fg8h=O2UoQ񁫔gY˜U/䶅x,ٺT 4l+l/EQAıJ:dfr](W!T!Tl$f<F.Fqg זQjWdZVҬ=W@<n-M]ͺ} j `^]}u|#vNoP?-!m%7NJ#TA!BXBx68!ܗ,Z;_d\5, ,)a&3 XNu `xH~YEMR-[an+hPN3ԿOov;΁|HAqFp\MD 1@ʢaRє@1v=n, C OOlή[|&,)4\s2"(Дi)GM,[NT?duQ\Ln"7PV"l @AI&S8@o(Pre%Frxoؒ^:\AMd4F* p}ETn2>j2 -c~ƕOx`\u,(,{jA by,5t' %" #Yԅ@+>v/ݚ59LGѶA-po֘3x5^A' gAn*c8ţ.4yueP;6<~K$f`a9H )ѕ`Dh  I(ҮO^cd!zg D]{ȝ8^G5YXysХ5H@&r c>*pE}s~a(onkyTL]w8_mܱGS]Lۆ8"sf! ["ZfM=QRu қy6,1ɶlf`=o]Ya8iCCNrr'bl?t2!TlVce/aQO;>ܟmLL Ѽ<4¢h~I'}y^s e: ?ށB|9zخU↯3>; ?h>?9Ph nE8F |)ʖoEaE;nzc'K,7xj~BQa0'[xb%6\\7<|ןO& 2< vKu\K)Fe s4eHp.0wjI2Қ\0Mo9)TWn,SrKۢL5u$ *kM(t*\whj[.U,BpCA |rk!~_.68 y'8L!j`o!F]^)YGwɭ`!,уVkoe}ʅ Pr9%軼mm`}hm|oo8 a8R4&+F{jZ@:A+>7+2m[㈽qh|\Y-g'ٮS:}Hhs H},}dXEvRۖnm]:/۝D-c0b[z> SfWHa$R*?NY:3=n+fImuQn^,hւu "&<$1XqwM]e^Q9#E~˦ڷ Cta-C8wVKBՍY_-9'as xxw6(-/X_i)- MiB#@}'3wϕjs@d="NIuPwj>٤愧nƹ&dWg!؟NC/ĸg\"G.}LQ [}_ܚ`^4>¦,s`j KYG*%Lmǣ+7Bɩ>^:V&difׂYՐM!"gɱ|ky`NeV[GK8n?ICeɺ)u6PI036vG~elg"E0Vi+nw͈Jdz.x|'w[(/Z޽SBGAw4JoԌ{$BmLS)#Bas֗^ mG :) q0nbD}[y *+u&#O2ƂRoEU% #Cئ0L~p}# cwh^ET┪Y1Å+*B[MUVeBJc>)Cr,DP#mC8*T@ o..f#N'ky;$pPKKCZ'o˵ӗv @(;V@;sLZ DV:Z'D l?۵;4-hw|s<0wݪYrGnJx_xSA]mB\ރ'LrH㧥ZmLu7y+9ah_V i>f]a?6 UZDI5t;A|7D- !JrH}!XF={q䑴+5$氁ŚIF2.[7υQ!F:|U$2xVN 6 0^[rcf͢|\uVQt'ݭlϴLSrbүpH27b3y/yVѩ_W3T8˿DP͚ߜT?^ɸ&Ghݿ(~#I2!eo_w .ƩiaKsA^l(M|D Tr[RX8iVzIgps!7qX8 YÎ jt?{XuD cTޑ5o\!|DRn\3o1~%EEޫ趎 $=xOx6ȭ GV+] (|&AABSCYFJ 7 V UD:I*{]\ı.jD5LĨݭҳNx޹"U5k v FKQԟ$J;]&1)ƗLuM#(>";!6͂V C7:^Vy%? PYΑ؂(@~w8zesOūS/ƪ!+=*g&* ؍T:j ȧD㵥J3[V >[SeA~LPFq)(se m1 F)x}/=QkʹdFPu@. %| T_k2z/_A ڥM]еj1;tD16o >^qhJ_bI1)RUBUH:msOP=zwxKTdv2{So  -cQVY÷t]nPz]@Xϵ0#պs"t}rDgQ16>Ri`O1YC=4|\sBYTИƼ$m563YQPއ.cWD, @dܼ.sgΪEV܈CF2MA%$"(ݩ굁79AT2V>H}Np˃cb 5Kfs7=s˻E!ݥΣ zfسa*|74ĞS{ZcA/34i$ *"EȀwLwNe*Pys[PW#.1 ]dR&q)e Ɠ1sTz&9hHl:!F.Po_ J- {SDXQڞpQzjch-ϵ6Ru^-1إ*.R'Pw vƑh od ڪBenĈ}EQ 4]66aNx[v@ aQc|ZRAs =pWZKS+ xmN!cb^=}|96"jrqϚG߱6T35/S 7ut&e~2 ҁ%'ho_8;6^ 1P%q(mY6gCAi<K>heCDPMuu_XV_gC m(+o(U35ؙhx&qzv7Jie!jzAiuֲU~8<1ǬEFB.4ʴd\=W}Α$tc1oѢ\ѤsgZݞqN%~֧}ۅKDvevGVP@/xyN9Ad̶Hb'ۯ#H䅴o$ྦྷ^$Vf:2:6bZauQ^XM o{'HGG6o L<pY (\)ZtՉ)Y.9̐mT@/سho # "p0|s;L9~2 ڮc3khpqF~5pEX?խ;P11I<(ĚU@}HoI+4sVU5iKLSI(ᕊxdrriK@Ah1Wo&pb6h:o%($u9 Q `MΊc}r\ kui!z&׿}]6 ֮yN_I4:i^B5g׭?=RA4j(eE$:RZ7G0YF C{X`8 e:0Z=R 䆀˪|rysH膂DB4l4Г"68NWlhX)JC4=<8NE= .kO$縳 A "]Yk^ k=/#(XLO1uxDoEƒ! D&qdwvgs \ĔW&5``2iOTF!k.[mcn“qVPܗI[Dz3 fn5B[[!Le1BU"wxIC)b&,RРxґæBY8 EA*I&S8@:*NΰhSQk mֿ}t ?KC3.5VnjO2nCp=` q@Lf~B`Q&7h4sʳP &HL>̮tI[jR=W7p%|}~HnK|iڜX,l~hF>3rَKuuj=ҽ!f6tY^[_ֲl$-c[E >,!DeŢb!Wխn^]GuA&I`:67ђ5݋SEKDSU=ʷ{V ym\/@#aɞ R4C(qDޚtKsư+3ڬPJnQj+12O@MIÏɰk.}%f,454Gj~K39yM I/ƝyxW}tC\u[Cʻl։挌L XW"u 0IJDd"vyՄJRBcU^w~[ k19:t8eiQ>W0݃cԎn/76yh&^C4j0mStpŝs0>wZe G'9I2 glG?1( G>EJ?x4H#SͼsMUW& g420)fjsn'+Ƀ>nbbLzm!XxJf+xiMJuq2zC5ǥ-TpR]k&?-B5zjF):mŬf/SFB /"^bo*X|B졣Y8*ap=i%S%~">|i,ԏp-b`tHnǘ4v-to+Kƫ ɣ0t> b y^2 )!9k d@A 5^#3e99۰ poD^] =G81F\\9H.`Z*Xˤ+pjJ* 0fWEshTP},Σԧ+@gwM+ZD6S&p (MSwҿgd ֦;_UP|Wv4ξYI*PIK'$`ʮ`hY} rP~pX}%HqgQ}L'=nFdUҕtmKso(\G4 @籩 ?I{J#g!J+?˳_Ο Rh #: |<}p,W& y~ue"3`{̈́h|( ܬERqAo<jkN6ċxFv/+#z XhF.\jb)Кa"\_btn[\:+I͞WTN%&GcKPƋ0*V3\@0u`Ķ_+ş{۵=)n Me3zdKd EA[tJ{?#]->50-h3*A4:W\-~y1w!R>i'/k 4Z;_%+yx&DŌzN[۹V{15egJ˜7 W er{&%#O, cd503KmK3!iԼ23qFk%;","X_!9S44я%| O3/ w_x%l^jb_ݱhZ0Rt|,nJaDC*@u0Ў]y>[PCe*]=^&yr]&oQ$ ﯳɞѝĊb WJʓ1f5,IJ>In\fqS;7-Qi´.?#=zC!D;I+MgۣV&i&tkGG`K;+BRF?JNyjC(d;eig27})Ԁ8G +g{BXaFh Q; X49dc-$<-<27*z׬_ZaG(5*e$#g:w)%$OoOh w.؆s,H2Ȇ߷ѷ}Ӆ!`뤟 ]wdž:tgjK_ Ӥ]VʻHG҈hd,GG-Ƿ[~]n0J6s9\m07EX:]|v@BJnxD8`߽Ts4ˊ{LWy+6,rtQ]h_HZ#[0D$'O qLA>[Tec鄢|0UHCWo 5ŞRkUN&AYr<q%eSY4^y)l/]>Xb>S'^t{%ug $e'b`JN ʼn"1zJVM`E YW)e~ Hr8kYn.T7 9n]X84{6P&&OVu|Z0y\51Ҹ'uOVC*lP_9p Lj/ T3n rgLcX+ܪ5snk7  %Fw™-K Z,귒lY?rԓ,+7i/gހ@_ըgC1$ q Vln& ?C;|gaN 1^uC?P{LdRBDdDc!39g^νHWtb-*Om:`ρf׋#+5JOvkDQȘ/_aXu_QݕxɊnޢibȸ/8ȀX_bW7ŸYn_7+'4"lU;-U35e/VfNa$q#[, n+n0E`qu @v}>BO<҆:b!-[F}/scC`Of HHRJv? ,u9bAD3"%k5$6ΡU M<3z('dw J mE-lAgwXxA԰{x,$yC.sm*:x dExwlo&;|eXƶg6a+H \D CN\&u! ڞD5,A)=D溭wSKSKLK{X<\OĵRӥkGn2M$43# R-St09).^//P'Jqfnzmxf= $0e\ʺG;h'W}$E5y9oJsxX1Sf΁[g-UR8O|njWע땗?5H Uy#}o\V%iUNk^O%MK)jqֆ1hcvvI5mw5?nIFvƚÓ6%؉7 ;Tl9w1Խ ~'5[x?lsqu4:j+evDYPY(ryWgsq [4@i|/AlI&S9JlDz]{EO!-*˸.X0ÇsZէSUNv*kQZݞRߏ̎ȡViI4n"-mOT&ߗ *_({i]d*Do <d[S꾾b^i'Lby FH-CP%62;LgIۧ17 V-ôGkǺr1z W0R.YBC65&Vr]e:0x0'!xk`Ƞa;9 由 Bw;s^~#̠irWAT-&5 ͜./wg{޶VӤ_&{TzLlƖR ioP6]M=$\ЎjsGEjR-+mu?nĀbv:{ؖ<0)=#{ASJg&@Ͷ].A☆\8-nWu{BwU`J ̄=ȧ'Wq z(@f+)$~#!92r˳SnAɘ> ziV #jN/թTItWVnyIl `5dk6E9;X`c{3RqD16*5W.BGukrFȜ(3nĥfCL2I_[*8h mod͡T*MST6oW;w^5,aV\coc]H1a L\v{Ku'El ?NA6ap;ЖBCHj/!>eGlwham3K&JU`dPXkv9ݡ>>jWX6bfΛU!r ufxf ^hw&Duz*?X2_t'ⶖW2l@=] m~jʰy_[~Xx7!钩h)zN͕ J?Kd=^FCu,?c; 921. Nx+Ʋ>aY)'6O:NEtx`x)>BOAUxgdYHb!Z֓~UX䰴y2TYLQۘΨ/`:\?K8%CKvػa˕"#=ϥ`k//|sחؖ&X[j;X0(hVwȳ);]݂u+UQw\lZV$7Lo5IB{ŕ(=$DQVWG@=Z@ѿtqpSlmeT.SuR_?U]};ǙdoslY)XH+GZ<+U0@( CM善TaM׻5[^S7q9;Wfߴ3TDNu /1̞\"33RH撷Yl A^scvI:a ~mCuD/DG;k+[jl%NqRK^43Bi,fi˗w:?a.B/),*/6 xlr$ҭm92)&itF5 j_sF#,Vo{guYT-B!y/5=GYߗ~9O#'EPȋ~c'_E2J&="]e *mCsS,,SF4^rU˱M?^/:nRq25C@;SgyIXq|Qv% T<.m S jh[pz=Z^OL @3mj 'U9șVS";"卙"DFxު09bffPy1-a:0nׯ.> ـ[%B^߇D׎Ңʰ@پRYoacOdٮGp`Os^=@ 8(,J"nf'pndm,|d lk'jIHnw.b8Xgض$< 5kNthE}fyQ_ikA_:!R"@><PID`m Wb{hcU6Bu}X(:^ŤNeҮ2Loxy!!탏g/0u9'/<Y2X\w"᪔G-ALJ0ԪKj+R*.)v)+܌qYhpl{t5 nGxcU(MC X ͩP+dM#TkE\rPoc$$=-a չgzJ[7kJ 6pES D8sF7۲&!wN闋Il0mvMB "񶤋F9J}NHM}u/'> = y VFurGDtQ#tPn! aAe0`2ji͕)Ÿ58Vzlbg,S w䁀vxp2ZJ*C ʝ=#6lH),h(FmaN.N (uL9A?>@Zb0o{}G>ܒ?IH2?1sn5EE{.pk+huTa/R.d[Cp)l嵐XxU;}hMEvDuJ3&({v(Z&Ѐˋ"K|H)eT4, ;\ 7ڋ75i<'d!.&mڗӼoEܧ?>á&4<;Vܑ`o-G(*!Ʃ9VX^+z6xlo8TWc\uͰ鄚Uos5lJAI ǢT$)'mk$?t`e&Ba\9_-+}73+R+RDz\-2bGmOS$$98w˝n@ܵInKݲl#w O?ڴ\' xF RITI9Be =Jz$¼jpGu5 -e~kYW#&CR;B vkڶvgЭF@E~1pcw:?_' ^g+u]fgo$ds[k)9B.3!`;}s>r@bu!i0&Vo<^ipbqjm72$&3/kAI&Sw?;ao>nbAg6!^vv٤n&x).MIx$F1@yB!1[V`k.pRgdCdŌC9IކQ ;O/Cosoz}*H؛4D3GK5$ǴYZ?3'8AR|Hlv0KķIbvMͪ]uؽ{)kߝous $yֵy n\+Tg5]5M_f\#D,B gnUJSVl3&*rhlO#w8KT͸'_0)ნ(|N}ρ-Y8]Z1kV|6tS50xV[͞UVI2OymBfEjB MYU u[(B AVaҤ"6%.g'(4Lw+q)@’*lӗe[,}BRX'x-{ U:Z$sxV(ͩWϒ_um&/[h ITM-6 ^J&$wMuD|P'0ywFV!S1U.fR'.dUa"KfZuL]&CH@ޗH7ۡ̽2 1l `:xf \5 B4R7n BF}lX ;Dr$eUSeb(~{s#>1K@![z`V{fV` ++4P\OVrfNnԢȵL@ĵ[*{$$(jB,݂e%&Ł^ r"K"[2lȕG.LBC))5RW<>LIoKIh|Ak9֌6v cj%)-lr3% ^ږ2QPݹw'v{gJXb=TaKi-r}0:Hmts]dQ^*)]Q0?V>rXVaAc3KvIsC[{^!]IL>ń|L&JhjZWq~f9z1X]޴gf~[Uc%؂Cm0߁/45-)W/(os?ܗcboʭh;o*.lyZB\uwM$uiCPbDCAieAjbb8b!*䘃^]\q?PH7y7qjܕԽPxf<w a)n6RE_hU?&V$A=֑牳( A`^~K$C}C:~*w;N޻^o2 pf!\YRG&eE4d> I2&8=#K8OqnZ kH7 ^WJf^>õ 6. _f"~QaoP{F+ Mr3ĚOH+.xѨ5ۨ86 'om}{hh16ϱV%ebPXb_YrIx>ms{$sh<o6 >]:C\ʴȻh9yaV[H!H-Y!ʣ lVν&s?~2j k3-07dc~sW1C'ky [8PBE{fBzmk+[zEm%\j=-b6ǹ /^Ń lY>$/u{|6הLVjò=c[@hʓ]am"IC(4v>C;aéʐ"|TuӢ +%bs묦|']JǑrJIs וep%VRϚWa |JLrnIP(ř(;Ūp#8l}4Ws9^W jQ t|ِ!--U@QLפC2s:\X"};v/o/Ol}Ner1Y([ČhItjh^K eliV0jmӇC{ j# 7JJѩ}b2$ iU;0MĤT(JyxWyyCῼi~˖!ښ]wH(P!i MrфN`45Uߗ{(oB-G]_Vrnux ͥH}ٚ&# 5Lk|w{fH#J6|,妚8d&UYHz$%z7,?[jx[ϑ@D~Öx֭wK NIh^A~i(.$lZ-GMKF5w˫0S$RgJu2%k夗)2o/V;(C,ё707( kogr74;Y0ZmAO 1D;(Ӳ/;(/DqS"i@wМraJ?'PIgHubwTfqѫ +lel J=Tb˳fkd=h0yh{ Bմu[V5W-3\yHWfuncɭ?MG%wio6WİNp օRTqk: e‚d8~MT|0Z^z~ $ @o=ك4z \;[as@M,Z_Ÿw$ k5î 7VSm:6MM2oT][~Z5|{a?q8dIqe`z1Ne3#0e ',N XW4=IɳؗO8[Η&?NjhoZ$bo(臄sØMk[Q oxmʢn$p=zcFsnkVxha:#+ED7)J{k[o1*U@N†EɎwb"]D/%җFJnT|C̞ckyM<e^ة-l?%ܑعJ:ÇUF˪0<?u&A:dB ch5;y*R M:Uhx¨ײ V#U`%`BW:%m;S(FַŒ M8ч(t =M%Q£58K}jCqTEKh yXߞ_%O]/V&:Scλl ?iXatYi{X`3)1m~Jt1p:7CX.AήM.N[Ml/̔[xgv{+)߁xx`feYCǾ7z([Z`qI z"?vXQ?>y.z< Y1MN +/KU}X%)I#:~!stk"u^; wneS`U iO1﹇EHHxE 54Kly!bo(wH}.Coc]1@5>u*P+^RH,{۾[ZùumdY1{dWsi(L`yo%.rs{><Ț(zvܷbknm|@,XG/?;vV+ >&`z2.u;#syu`?wz^jEy R!8 RXhrarBQ wFQi4kDIt gW86n$op/{Ihw8 fX;΁o_ vCRuZIDRjh>@ ]6]kr>$\IOt5aBu<27_vB(Gw2ŅI v˽Pr#MX'ozWU_`8qF䟵p b8NmujeX&@&qma K%n)| ,sU6 BE =Ijv!CVHk^Ī~dOԕIh}цo!Z#IP'1?MhIX71fro@QU' Auo[]*ꑻkd՘M@}EpqzOR<,F_,Bz$A9wX/v1-+}UL[vcsj"" E37*Id^3ixzDk%ZS%`=r7r̋ eNv~1ε'/- l| Yd2XNnj͛6\*Fw; w 6 "mɩA !<ߣ-& # 23A_%)bsCA*|%+TbW|ķ:|q뫉 qʍ  b(m8ea6wsϯe+X5F͐%cu1wSIL Y3e1lF~~S*!9Z\~^t%xFsi{q<>d@H Ak9"*V5 LFBq\vwӾ6%8mIڒq:툋]7 QsQsP5B;hrG%?\)ՀANKIek*k wWEC/V{j{P?o5aITʝ,mw1#cMg`؇Bּ9 K̕?{]q =AI&S8@vE_;dȄBK"sOowDSO( edo 02+;MN1{G ʫ$nFX, IT(o#gjX+ 5<:\g:C(w$lf)qf2CjҐ- B=ICu[Wߛf '8?DgUZ !DZfEC;"tIR'ݷk%c͟)H B_Oɫ֑jie-yBe9Ufy-Ζdy8]e^A ]`0:Yk ywe&2tz3e̠bJ}JW$kȿ]]/< [~!Xm>W`_R}PTX>q}sw겜\ :+cc C[ !$G'r9[,@ZUy})RF .p'st=zM5 =_a,z`TEEscJ@}W-qf~U^ ԩ{Qe%@^zp}5[1_ٝXe| pT|-4Tqܣ 'Np%)J ]9MHEjPZu!ɩ 8]+Lg /d_栾BmQ Y#I&o>Ľ6̯SXA n{soS]cYFaZ9yG4<~edKD@dt5G.Ddeu[лczHŢcyn0VirUL^f8Bgt{>&{’{KGfJgP_V e4^`+=ŗ43` P`KJy0f9 GT?3" ';ͷW1(:} j?GPTJMnǐKHeX<)V 4Qݑ|&NGOp\ FUv;ZPN=nA) "Y3; 'u"QL@Ju.n="rcN:*a/*WjKY-j2fmOJLExѹ,R ;dkg_ix 1E 7Z⛕\ܨ{/1W}'F:Њ0dxklkhr-8S-9r%,>ngXgZhDntIA//97|sB`-}|}q> #~Ji[1SWs/!{ǓfEM8/J>h^[awаnD!݄P.P:_8#g^ٙ~e=ܦpIǔEghUѹ0r)|uKv2@FWX$ƅs$\$C%dBYYqؖqbR!q+'pDx=]DPe$P?{]{Z]Bh^Vxj#Z"x"W/Ljh_C8~verAL[,m]}3IV:3_+Sn"nw@1]*[eTbà"G)١O {5ς,&ۜډܺB7@a$"$j̰/I@_El-ZɊ Vvk]⸛~XP xxzW'`&Ѕ_z?߰mgD23rZP CNO9`}Pq?2RwQ4sLn]XSv._PܐD @[_ 9ب'jNsbG7PpCGqCĮqj ؀Oo/+3b#t@6vӸ,+OA@D(?"1h2F% -ZX¨GM5+B͞KSЦ{W*]jiԡ歄Mh$Q@_@8z}f*.Aaq:IS90fAe$N .bPM e9>wrl6˨LWqZߦ24ɥո;ipoōA،d^!T4n$SeRɿ?u^ ٦z;9Ѻ<tjƱ :Ϗ{[dc3n o$9Xtg zAp}#Z;r$݈4O?FHcM )A-M'6|)$a+fr𢳴/Lpk;^u} 9aWP^hGֆ+",xےP7O<- a_XA<)Bx ú%FS/r*jnj/ Js:> z?okD D쥸:w:p΂Co,֦%{ۏt:a[G<*ɝ+PE5}#Jzj:*(HkSQ5Ii=֚۞.MVS%ZYֈJJyDJjB^w:gD8wT:&CNzq]U %UzP8jY/|!!@?%=0αpduL|p ص N $&MPBnj= vk:&&\ZBiA)g]/ Oߔ ~J}H>gU0fݵpŤ9籦UW~ˬfUCEK _p.|'TRL.?l$+]8S<,+f@BZ.p8 AfMCHkUQhğ8ț8ҍpi R]㣾L-#=zW0#߰zE)ip=[t%-j $x=^&l ADDžS&kAY +c!nW6e{/#uvxQZTܭx"Fl2^ *0XaԀ]ElKJ<-4<=;V*qދ3d8laܞ54g'`%A< T!gɱ1AZκ OW-HXjK|4`y{AoA8ŭ-r3%b:s AI']Kش.)rwC$oY֩7F!B)TVЊ+WW2w !+%’nV~f 8&LSCppe22pӃXZ Z 8 R)CEAoKh֚%A';.bYu1FTbVC~A$M^= ~&ɏQDQa?>S'Rz \|\,Ы{zvfKACe5/VUu+?Hokv4b?6NI *Iw~ n2*m7(IK3c⽃&^fESWwr0y IGOG‚U{Et٧{($zņ[ʹșN *J%?uw%4{7W?_  z0!3}%ŤB[Cd&t\jߦUgԀ+߈F/Iȕljy+Ec=qF0(WEW 뷧2`9#+'#l_lbdp^MP\>Y;CR{({=un({f/9synج=l T.ǝ [+$ :X]XJ#"ݳ{2}UK~rԶ̢Tn,`-:4yEx_fhmTjR f4K#&]0Tͩf*oئ2yڀٮEP% C@g ' /JN^% H5NYysk#%CЌW0Wb\ϒWqTN/^t 2.Oh(@Q]r]2""ƂH`_ wb}lßQa;rOk(yA3O-ݶc97,Os[yW)JݚLAbG`L'sw/ո_b`s_nܞ*F0 n/9=ㅟG}n3/ܷFؠ%aw [_g(Vn{=Ȫ|Gw|EV2'Q$E W.Ahiz)5)[!W)Ba8/ĀwD=x=Cׯ4#3B>A2s@%Y"0QHX(Nd` HZ0 k’oˁG'ă?e4x*ŰU~ Uw]rOl DGdz=q"j ʸ6bk ˃gcesz$u14)86IZ`|\bsڊriYsÿT%]rFapu_bS1tV&󖰾Ozܠ)yG83-¦i(~)+S΋n\Ad1dlm E:4RWsh2I8VU.P/FvzY#Vmc dmm]^<2oF 쑧͘!jI(h#xx =)ծ2hL ˔({HƋ!~7KI]mFBG4[9.zKE3Z#[=䪾CLQ,>Ku 8$,0,OK4/4[io޽ x瑕IJ2b߻$RK:) M.9s>pW5F>̍5J 2}!,X5 m_Fj*ua0Ml/kSt(|q'`@\RO8]H|:8'nV{ PYy>'!@q\:;M"*I0_g2P 1@.WSt2P :eZ ԩ"= h\HJmTFB0Qҁ4m-%,xɯZWMDiw{P7Y3S/ naδ9%OՆ( r[G>k"Y*WQp->o욜TΟ7t6Yqˁ?ff@+HX=V8/\KA:O g8w1N,MDVҩ9: t6Y VZV7⾮әy|nʑHX[ {*qO,1$H=𿢱"LlH(4@[Ωg ?: wwh$*y0}P#W^`<2\0ʒ݄jPQ~#i\0 ~ ;UtLֿ uB0`Y0'sܕT5 dz:L{{3QR}u5L0|$CUy6fT=gS.H)RJ:+ ? 2[YlûXs*ˊ_)IdEE*k|#G!y.U)o (Cߴ拠znn- I2j f B5s:#)YaUuU(%0UIi#O0c摉Mׄ;;Q_r~ڊ9IoՄ+_Ց cr3 a?ك|(ޕχ%jҎZ׬E~Ouyv'A2}H3SIP]",P3Tgʠ"*݉N7^lJ:GIvR!:yOgyfU}ίm0K@X7Q^^,x)~xFk~ӑ34d9 q/ɟu4CmnrTv t#AL.67a2iI*|0ޑ9^1@}@"g <&BW  vP#pŮ7.?z/\Zm Uo?>Q# <9X*h‡SrGV S5pK2.E{.cDDs yR/KL+6%Ћl,?CvI*u{+?.N#HZŌ!u{S<H>MXG;P0J&vX 6fpEÖ;q$5e5Rr3'îF=D/jϪAt1[>026sd&vMX G›UyYP )A!41,@:X حK%-P 2 &;ɔhoY 0\?[_Si)7R͍*0N]D.FH>J*ё)D*C0:Vr hm͝MZf,.{jF G.#5P/<_Xl]m;&UB,w^eB+La| j 3c#zv}\.%-G,@`{` tKptxW 9 eMJwW$JԍB:T׈{M w,yW:GU\8u1N00\ã?#&rf$>_!yoj-=$$L٩!UBͧ~oF,9hXG]!6B8CpGKsOSh`N4!= iJ[_c]FDU󭜞$͆p[?@zo˔J-6:{ v<}i@)8E{xKyNqv@Dr ?eÓ;~/ժxKzH3AQ,[/B%Iʭ ˲ B5vTx 5w}oϟI<[eqx`c*hԏto-'I5``Tg$%/"oszd5ͻkL8g8T, X Թt-*[)@ј "N5,=z?$k7o=o%কj(HBZon{9 ijijcݍx6 #H3sVS 5A0ڰcC$;oGzZYqw{le}OMX{<c3 )ҧw%?rQuڥoAʌٕNsMU3é"R?3rB1P_..pXvEhIS>[{Aht0rv* 0Ŭ恩[aLV\~~<IQ sɪ"BR]y|ha`\_ c(w#ƃTڿ'Kj H*ڟ*u?YwD_-uZGIʾ PsaޒE;a'dul|xӷ@e5LAz3dٜ*)$0F+lQWr9T:Nd`b Vk+|Ztѳ>TI&1Hzϊy=&9&,dI%K1PH6Ժs [Z*DsVf 4·!J]>-7uxKxГ ¨Mה•b^ǍG^>HW8Æ9ԯ+#!3gJUeWlD52 )Ut71c@, x =ƇCPˆ)@4'kUB)9LPM\̼%]P/'唟R:C9mQhoyc:^ֿԛiR/Gyh+ *N8e'v9ALq /S:7KmRU6/o8M7SvWc8,C.*b9ݕ[QSMqyO;Ch%m$$c]H泠5,S"XG '5/RrΑqh.) ҕ8<*[]&!gj%=iNgW3`E a .ql\1EղB7-꥽JwmX7_)x(3tTX/_0Ϳ N4[_IQB6E,g?FX(&^-kjX Q}jrjb-ZI'F#;NxMKn*CQŕ9VAXTYoN6V9n$Ʀ A q V}{KsR6"zdvqỳc_zo5 Y=t0ڭpU\$*K Q{6z藡U" UҠ/ڝF?0б4y3~ ! u(UrN*L*Ln =cI%"w}5?tR2,e.`j@;]dAgYA; >=GCHxek+v /p]8t1.Mwl7TTI/ 3a^|mTiodjJ{H(DGt E6QZ72^ a;҅,=$ \&MIz\$7 K#tBVFrvzdC2S5i!+uw'ޜ ^K*Eusdx?G%%~]JZzui,g51",uP  ȍlZX7]H :ƚe\nh zm$M^vi[U0;  ]b*ʱHNU畽e}N.2t&!+bD*#?9]׵L՛Κ5al\A#C0{5*'m+LV1FFSi<|Q(] b §fq,e__mcܽ#Z`\rB4F?ci}=`~9u $'I/MEI/g0sQy 㷺+0$-U _z0 WchIRTΩ|L-+ W mïmKz%cJA. Sk mepAsK30Gä~(`HNLw)V&cD8_Pi!KͯtĿD*[`~:2YVNz^7L/ĩߔLf)LF29U6 ;byMj˃&Z#>?L7j4^'P2t(Z@g6u7qTJ;v߼("Eb̑hl'Azj:y? XIVghѕ:9ָZ[Ajt''BA>ը+n3jW~=6eHvs&D4:fz؅Yˍ`/uQT{$\@dg3ڛ9(&+=X\H+~&/PXK SIf;!,g] O2(]qXǶM3R`jlD7RD7]qeEqʡZgs⠕h :!/ߨQVz1T1"vPFNQDtzM?\Q 'u/ţ`S{;={ChDV &qKAƧ9lӺJ*_w*VR% \t%V 4 2ղ݂^ E{coYz%|ju"<]/ơ۾$da5:TiW(w|ȏ7'as:fLRE.-ubBSFhc{0Z(%n8{Q8u% įN߽j5BƄ-:{7%^JzSԆ8O#B `T>#*8 6 Wwܲ(cBy(4R.+='9xꛇ' ՜/EAg( c41r?->ٞ{!:B_:%xBvFx#G 7IXF/~CXQ/_ـ]3D z5T\`*x} v17&7ׂ;J̮ 4d<dA^0(CxnEL&1N۸h93,XLmU12QK\aT9 Us1Qˆj2hMf+dz!khGh/VXN=ب L8}& `2J;'5a/@oas+eF3w7pUp[L~C0 Wϸd#m[ypL!&D1bukCJff )F~SmA|7w|ݪF+%'!h;aƘgr: gKy:*V\]P> IfdDmW9R\yT#+lZ2,cjnp3oft V"Kt9O:XG-#]P|HyMQmi1B{GVr?d hF>)/+!-y|Q9~Egw 6rdy?A*),l6.Uws=0҉oNݏls{PDz4Z~}@XF"^YĤJ xy\jA#ZN;>hrpW}iw*L1ZF4Z0t:$2g77A"F Kf&'C9>>z-}䠁 u=8^y *+$}7)=g"`G;R @ZCuw_VJ^=̆ńya\'O5jjCL$Efkt;צ]:hjEP3Z+CևPM*Vz/7TY8s{59! q.Z;[21Zya2UeUeQ<tG&0_Ï QTQS;,C,6 T09BaPAF5 BطN\-"56N[rQ"opP,`WQc`RIaao 0̻O_-t_ iu)^eX)UlrAGZK5"5aHOH9wRMJ*M! V|/{_’204ףm}teY2R@WS@A0!b˟7tt 5BFĕwV_|Z1pD3XTycAOA]ݲx&] Jv{P;%LnF8SWICLQ|za%0cm k\Uxp A3I&S8@~#r*/:.+]ȶ3԰v㉖& (G?js uݵD[k {vv b[^ 79QyJfߌQrԂLSxS.G%0Gj?_CY۠>n)zSG rBX9D;m q'c]m83|vd>ۻT&v Wa$C|| Z Wт̳T(CesTcUࣱޅr @ANJKyBWIud,x4>?!5);1R s˘d@dX1W >{fOK5woB*vb+\-- RVn\! a|.uYNp mR iΨ*\5'zc͠Upqf%+~ZZ:Ǐ_{@nx[iִX/;>S.-='XS=.(YU3-# s!V,g9^%`3Mcr1~j0VÃCMZV20z4{`#?(HץwH~D) B؎iOmNpt*jgipeO8g]6[!8I`So.(8fTrUApO˸*s1à[\Ewpf͉OԂ3"N$%CU ʍ&&@\ OKf)K0u qsfY/bs:M7ῚU{ȯl38s77v$_즅JYD  2]lcϙ+)h4ѧiZk(3R* _ 4W*MiGN;bV6oӫlj Ltef,r3س^RzΉI@Û,~~p8>ӣ.Z(ДRdL]VZ |qcJJ'n:H~ᛂ OOYkVUbØ]֥( O+lr@s)4זfJ1WIn 'ԧE=j-Cӂ;(AhX%'U]eK ,}o:ŭi?#ge2ƶ-Js h1CY rL:5]a7|tUjy2{:i J(fbľnj3P-CHP.9CJC<cL3@6*K26JVLZ*))SpJ$Dr$:.) iqA{)a!p+5xI4 1/ ɪRO];ΪXW^7]b%0{1灙h؞C{5;Fl)5iW+UJ\{SP19 ATI&SPCBȐ,- - #DF5\Ъ4F?CNrZ?Erx{{Bϝ^IY)]cۉ] >FȴammmY6zaЪZxWc|ĠT}hVWݰ-O>>|56ex [F rjD٥1i|-m4]Ej:=L##*U?OU nu#h aZ0=ׇ'5|Z3| /*Fp)}!-8ѥxX j!N؂9YG]b[=ohUT㒹.C<=rO ^FEadF}c70oUbi?)6vOH sWvE2x Ol K#NIP3߽mTd=JgcRҤ-'zs@lK~kTXCLWHxrx0Ya^,P|" ܯ7@EoslΎzq ^%6WO+]91FѮ؎n} *l Yol%E&zJ q".@rk%^bQRYkWe!U2HiKe}td+b jW.1,ϯff#[D:P jDse(~RfiEVy+ ާ-}4E!U+`g jwCXCؒe$;R!qt> _/r, XL??Pg>#>Z3?N#z !z( ȧ k=4.l&kMySm)'i.[G徙ˆ=Scnw I%bŢ(mڀ $oRϚtiDn jcC^Cn]cݳ aW4u:/!~ںabflvf!(#`}<)"g-L^AQS Ic% 4aY`3QcԻE1ݻ,IJ(~^6P( /BC;澜R#2M{r2cJow1*C15==2yIUU}RlqE$o+#‡׶#1$ȾY749GSU]}#CY!;U5U6\g87GwM֎9]fi)27>BF ڮ4à?@c*t!Y0j1{RV.=uBdrᝩiq_bIxPqO>/|Uo=Xs ("2! s@М^)p[s()ۇ9y#+TyzOA*p.paWgڂ凌(|m|CpRy5:ɍ^|;*"d: 2KG:F[8w7U{ds6d}˿5)#UBʃ֮N}`s;*KօA¬䮯˄auϒCa .MIT86VH3#.-VSȝQ>'^R_<T`mo-P'_،ee6h0EXCȍ~^YIɻԐ?v}Ce6?Wt ;Qħ5`ssgN_r|s<<=ųzZJr[,X$XW/_ C(#Ap+QtxN1}7I}d_{pþa,S:^IӲ DHNB^hḬD-v=EBjc|ýc.Ie_ܭBx+ M6Ob-oI[j:X-7lb޼D)Ta(0 s`o.jezq4S ٧mŀ S )776>QCCP^!y92Cԡō蕕-Kte=2Z ,Sw+,Kۉɬ |d47'׋sȿ Ž'C7JmY},@/BJj(+u޼7 #foښa$&kzAF׬@;#Ȗ) D'zpG=t9R<WAՍ]rÚB 8#^2X̷IQj?"$6]Vw{瀇L@Iw?q sѩKfܬA]f.m'J8JaX{_/0~R4ĽEûB.*%:<>L\,{ Cg_D.tڏU ~*L(tWD]Bje؜egz#~$e{.m+6HfXr?ueI̦qwc-' j0@y_tMM;]Vm]fukdc ] 0GK~v%1vbJgͮxgAq 'i\ȤWث 6#fTc& 4r%g5a ȏO5*w׎EU376IBO_O. 0m˖,kaj@Xm- -|4gx+ MD tq,E/5[Zn9ֿzlhR@-$avܠ[Tb`ٸQɡBǢR~3DХ}o 3d(SmY}Q-8 jnP Q%V&i T2B:1-gMng vyA#˜#sm/ F5g; Nc-URZ n 3E{Q 80F>h}8+-ʆZ3aWp00>@ҦlrmZ`'zxs7(KaKg!t%q#OKajF65y-0Ɵ^0vӞ^[qTYgV=(_tݓn+~y|qT$+aa\P毊[=w46*=QOX!#7}WYT)/08~EnvG72I\osD%:X\]Qޓo}g4}gPUrJSvvؙ_z{KDJNPD=sUzY@W((QqY3L7P[IURoY<7;ZJ+a RUYqp'ya))And3_=Ȫkbƭ#2.eA4Q5gt(uÄjۛSS_Ivs46烫Z.c^Ô_2IAR$49+.’䴫 6,K[HH>_3w;e{ew+oHGKV#/.v@sKg< +<Q$K:B$vt8⭗ 'ntKJ TT6>d Fk+NN7_G F_t(0A]vݘF^"qzLarʏuHfI;2:v8ݛ5@t@wV=j҈Yvq kS\φA%&ٙN ^3N>tKl+?E;0uO\5B<|q=`9.Aa| {T(&UjS]+9gիg4#!h :Fy?m揥[SzAsCX\[bZVAKlWL[Ȋ=~ :آ"U"hܒps&3aΉ歏Q #<"DU99f-XOEج^CN;&ɞ" 3K/f{QDďY{K6L@]cLA;B?/PhWӫT˕P,\": Lk_p?}c[ڛ{_rBRW^z7}7w~[xkxˮKDz[=FrV90n;L[~~6ğ=ITvse9!o{5 c# I^;>.Eo;W#ҏB1O[a1Omj- Ki!k+x}Rm'*\=]k8_ByO7Wi bn(H4isK22,޽|S낞i$%'w֎)0|40-G:1.M%pJ+a?PXXbR i}pq2raYwUf.ZƠLooȳلޛ"Bvu'aҷk& >&@$3:xBtV܆2pSBX~V @[hq,I6?$N6+L/1nqTn< Hd+xOb`Q6'_1♥q&ԓ'zVi!=MfJTrU~C G3w{=;vZ@%y3 T B5I)W%YNEӞ2#+;#Vma.녺h5Ck>mmuŒYdhãu6c.pux{b噝Orwz[͔34xKtA0CoDūZ+<ZfDJF솰)ξ3Mr, U΋0Tl| M #3 < &ND|Zv_ȂpAU23B/bo J5,&_\f^KLxsAd^U1_8mn$qxTw=a[֣Xz^Ň6?z6mɰ[rRH`'c;';%|B6 zDy&+ۀw3C0"SNQ5p W[2T gy`HN+s2sTM/4AJ;o%Ҋbz#$|&B49G(n-F=4w8"&^DŽ#"M )LLsa=ZvhwO){W0*Z"N кUT dՌ.wC:(\/X`O"_|e tU3 C$p{&$j~`t0j_2Vf(*HSLv[֐M0Z_Q,kD7 “LdyG0h`YjJ54<)oz:&"昧I[)ʰ嚖Y+ H8r772-%Cy6[ _RXQ-(-5,MevJ( D݁gV֛ɂkh!QJn:`O]^NX>ոai;BݺLٸZVBˢ4oh\5;OӅx{DZ[p)˩'K Zun'ڔ 9s`lnV)`I>6);zEbb"Y\ $+Ř 2Rq>Pʘ+%I=<1;u64:#k8xDx CQH)e3I7MxV)ErB\8hْM76bh=ci\N1dBᴣ]Hy_2Uŋ=5F%G bhBZtr l-C>i<,n|S׵W :Ȅ!Exv%J=i@\_4ċUS&"U4G񇙂sRFjk5bWrKHeU}0 V@KBoYJ?r^LيL[""AfJK}{[%@RKEw& dTJ9dIe"3H瀓K>`U^qnO68ğgn˰ik6A7YKW!hm]=W%>QjG͋& ?·zedDåRJTA6l ] ݳ5k$|i+N5I{y;PӜC]5߂8eða+ƷTQ_ٽQz;X 9԰Isq` U%? }撨+r^^/I=& <:1xٸ&71^-@q캐 $*1Q / q!Hk}d5DJu՞IЋTQR x}ρ@_MjTLz4_v\gz?4ܯ-{yJN\]s".)$nc(4Dbc»D+@ko5Z5yB ޖDTe8.TAsg澼9qbZ%nM-o{ r1^8ϧ@ lW[ ߞjdK= L\ʟjs?E"RDqŌ\jHWyOYį<ՃN,*?eV@v0Z| z]TgK !mCqC/;dOޏ )igoMZe8d#IUVlj2wlCD<7YHrd:Š7!sv;h*UXC\nhpVh#}8~p}WdV6ҢM5| aP'٭=ND(wB7YJϐ9?)G ?yK{(Cmy*$8yS[n+.'˔;3tJ^@@Le1?}ó2 Tf$)jS~VHQl-4aSZ뀓R[OAbJ.~G$2Iz𦮒;ͼ½. 4 Q䡋ZmJ!>GR^^\pV1D&oқWW!SGˋ@&+qI{6"LD [8#nDtm}4ǃ׍<灺UʻO13L߃>ZM=AIXx4]. :~C:se@UX8tC۠q*s*C|bA5C J!g[^;9Pb<.I'_oľ V7GVAX5|R++5PƊ~hՀJ#gg/턗O:nC.N+Keca0PKi(0\_9  % F(EsN&[ѱyDYTF.AIl0س9)FHmR[70H,|*2dAz Ԃx1>`mZ KUQ") DijV4lft!_@WV`PpQ̮2^3r; }#V"^M^9F !e McChUˑL]q`ٴ^ge;% "Cw|@=&`[9+zl%%kRj$sNe+ -?Y!@՚{;e*BF!u=vh5ӢI^):O/lCya8?=j4xP p.wU#2?J93l xÂ+ci HBڻu;fmCqiLCnl_M7NEn*)  JFFsn)(-hWq*s" " 5G(4n[e@ցqRċXV1PCc58(H|9L_q.il0WCwݣ X20!`(XF bᾨh-? 3$< 3EI'})*WpqWwPh)?,h7&E Haa:V `b^JF$ye*Fgt>O+6,n\3m`eEAJݲ[e ;{P4Tľ)w4DX D)"z; \ѿ괕!glUx!E|.hUs)oi3BB)nh.b<,Q*{]PhmlT%K]y>n}ӄX×ejtat_Xa;K-,RV;tfJa$#LaJU hfEB]&|&7yY:/fr w~Β¹@񟋋q\_Gu` qNЃ4B5@k ؾ7S}?[LN*[k*MZ f0j@ϔ?ޗ'M QNg5gFm 7L{We'}MPRwD~%11Z!E!9jqTz):NTDYC:S(tJ!P(hH@T!G`RÌF۪D@RqAeܤ|#Z,^drY2'` ?6^^˿}1_^?dH9cNlh`B D8~_ R,"7PL;K3UT/K@h$ Ap'U0N; I8Hgj'U8@,` nK!(1E OgA:`pڤV5gjsF9iD}Q#3]K1laջyisj}ǗqY8y\iW^G–vEEwG?")D‹<ǢcMk4_ᱫAܨ6Lئ0X1.W _< &gEHL2qҶ;6\YeBwd1c1P;^vw1~䨉#*fZjb2 Dp฾E*ܕκ;;ş/m4z ,<:ZAEGN3IW}Xjq*JhE b8y^wqisj$:RFX˼Ni_dn6&˚,&; iҐ[2 D*^/0˯-X^hE,$ԁh|^ߍ/0)9@ +/}Qmx.mnE vC1,'frx3 rI:'z:2``G]uzizyt "kUti0wgDMVi?_+/erj)10Xp4ߋmnAfzoDrd;x88ٟr{OmRyj )*m41iMA\t|:fʢw 8"PpԫU\F@{4kz*x'n;X_h/8)7@I685d[,[G͘54lsPX1M΍J>$<$JߧdJnv[3$OP4Kd.&p j&Opo^QZibȸ:^(B [W.\y]u!n:Y \ ;A51-ZlsE-1yD2y.@,Qc 6CC:,dr׈MoNf%4C['f P23N+@uN8d삝"{%j[zN@EUƛryDl4sVH!J1'MU%Sɼ,ޜ~T?uh!dƞ)ml @I6Uh>Ci.u#Fܤ6nia|ae]"8y/Ƹ-]X;p~OR4MP2L˗)(*?g&x7]TLYχ9<"mŸSrO߳J{9VF(i7[,2j._ 1RnAr<ЍtƳh^jXLj(8qEnXSoAhah yeb,B#|2H% /(ZZ! &ם^UAWfZQ2|E7E>@'7 C~8V,ge#,߶ˈ1[NjuXa*D{s"cFLBpʏݠA2`_]^܂ͅyLB|X)132n" @~ F}ը/Gq5[s7"ub-J-"` Ć ;aZ-ŁXKM$T^@b<5"3ت3sDͣ<>ɗu!H92dz(F\TO7Q+f#/ ^VתN*/hp8x*`,%bH(L;.r6%r 0y>qV;aYXa8?@ Ii%ӗ̓P?)770NˊlL&=壷zr]hpRs8iMnӻ`8gPs,Ajǧ6 vkUz,2ʱs=,"+O3z:RZEYAApfړ+Oχep7%{ (9IĮ/)|WZ^ޤlKo4]K딑ܕc^^C(]1AB}! hw mT:l:D@ _@WP./v ܔ۶f\\gȓt54\ɆaFnӪ=Dv `795Sg It(}S@3?Opi=YlX|Ω9Ţ,=;W[ $v|ͯN[wM湸|l4(W|:h #1}:vꙪ˅E륭2QHjrL'MVj,iPXPچ-ܠyb#G΋B2GDX{'3 QڿCO$ MDU^J6/xP$z}e$jϐ}Z%ny1/d$%Qd\e7̌:_X$KྨzF`_F:ޕTj*R9צ;)`s=Iz3Cs^8}}އmdO} ~1-"w)C9a4vPG囊> |~-%`4bFSM} SL090sa P0>N=M 3R B T[ տ,?j>ȍv\s~Rk ֕_%y>7~f j ޞB-htUaB'M'囘y~"fq84tKnҶ5p^ 8JVYى9;9hs(D Er҄nO峥'g;Άl%YPҪo[+S&uSp{w(s<:!Rn-yNdl2SC8!dFRDn =r]zx5Sgďlв`䫶OGjx{<'k1c 0Yoq~1aٹTz25̏AqB2P QVsX!,x XquOg7Y+,v`5? 9yh}SݰI"/ׄ:K wʀ \i53O3vʋ{AP-] C$ʚ)4<@0=դoԠ+*"g wcFi:!4TJOA$ ύ VdnQ֠y\aeX]F‡lC2$@mD}D< Dne)GhɿuZ|-eLotW6'4]vZ$If忛izF푵Vh;Ve. 5{8{LomJ8:+v[ OA $ÕpS0pi1e4!jB*2eJ j< zDuȱIb!!p1Nc~UjE`~R4*)S19OG 5?U6p]t7f4pgg.U(DN)oK\ zRaoew=B~X62˶PWaR`R6X&RuW&}cL`n 5gZ,KC86k)5C>+k8>)4{HUŴ„%Us&kĸ,V0|0kjZ[ ط~E `H p/4I,)G \?Tk| 3c5O%aP+\s߉)o[k<'BhKr0zճ]~w|WHF9?L +K8d?H2" |xtl"ʱq -fܕ Ove`oa67֒Xg.ʨ h`ЌV BTF9 $sad6RT͓A:|3ϱ\ߡ9psK-ѳ 9yB7+?КM*%籪޵pC=IG\*!Ӣ++ !z(H(p+۩&Wtϰ_ [tipمҋ8Jg!';v𰿭[ansu pG! _v?P;C#'C>F$H>EXD(vs)؆{6P_66:!FQS/HO#ľ 2gΩi:2 FbK4d#-L4PK_zC.^(I(AD=ۡ]|MǤ w93rQ]m] |rnl~Qۍ=P̜N:HocCf_11"CvHX"ly)G/_ҫ < ̘J3' !KTKliex2L-Xw8 ~mf%փ"lh W$.QҔeK8Xtx"96wo{UIy|kZכD?e ;Rgt'ddX%e8; `EI bu;Qf;Sf? ZO mu%)`xHAש-gYe{A]AU"ᓋZ%ߡ1g缚uؤR->׀މ hD٣#)ٛY?iq6S|V)TbtXAPk鷵l1{̜21Kf"e,FXY:*L;0ieN/& ZT Xxl d[[C0VC|!N }@AG[^kZMlZ$D} |O1NQ$d°>LcKEhF)RME{h HBb,ToI(5 Af9@!;VcIÛ;#a 3X [n/Q%, IJtzEb.F(䴉%S-+M ^Aqp >3*漣r|<9O^A!lB8@5)'7ZE6~ɤw~BLBN)WJ4͊,N w Wt.26t W]?`g^_Ċ!rq.2Sy/p!KqtEg(3Hq|.7V'Sq֞ V%P6x4C\*)]aTH#҅) 8?.3J+2v 2U9Ƒ5 g>|^/ǬCI:hF*GP''BhI>FU2oFcS.lg#p_G~*޹)5f]װQ~&0@--Ae捜ooBwnHY3V=y WlH}m[9_lRtR޶XekF_j]Kْ䗞< , 8FuPJ![d/W~^p`'8|Jޤq*WQb>W99Hd8 xF:,] T:U9kCNPpd4@߶QûA9y1ci߉/y2Hjvm˜Z|ƕtNޫ?Ԁ"|o€\ԅ6ws 8&kX1Y ngkCOf@~>:|hTW;lwQ_ňxbK?78,3!MZξwUVC&`FSS|-uՀN> Sϔ}fRک hdc_.fq)SɁTBgu(y{, WJbM0 M{M f֌>Y_}͵$3(q$!mEVDp1]fH ҄ɘEDecIr`F)q׸{ߥ+26;զh⯳fՇ9fr3Qps)ev/As\X}\֡Bd=jE?s%(N-w\8;CKՄl1q:Mj i7WR܊~,js4}r\Va2[$f'Łjƪ[EW+R灋?A8nkSeq;w38vdݦ/9h kĵaUwNqD)Bf5 K#F'`k P"7&ă=& Q\)3RYQ_cfө/r',eo0_(^ɟx^ɷpf"hl7&Q<c8 C #VXꅬ֗2K+إ?z_4rJ hE7wr[ȱnͶq0igFu!V)+<5O'57@?%P 0] {yh:55䶜PyU XY.].K2K4Nj\7MP9?%Q bO["oZ%4s\w@i?..:Q 0hb<)/emm˘w|?uv:ƾ ?T͊zod'G^߶gT0hz\yQ(,;R-e$4;7ʨZƛ@h yrə;qa1-8K]>_jZQ=pSO YK9#޼c\nOSf'SaI_ސT&#WE!h b|(oİCikmz|o GXd_5GI@RԊ^ 4= n]}fYE;ĠdUy=-Y: EGveayQĞ9($| @.ot7U^0W}z3]iTe6^ z MNـ-Ca Y#_Dv9$mz]|qYKu"W,V]ӊV5yW7nR5Q.]Ԁ2ުuJJ9v]!<}'tf g[zeX1(l"ENs{Q=(͈_۲8^ 3wIm==n7&{}MJ6a }#N;B@wlA`/Ӌ@LCd# 'lH%ۋ:NecL7R+Qbs^1r#IS7.2ձ [%[(`֋EWkKSAD=6RRqrdhcI 9(|D9IBT*&|Ȕl]lxN `@(ޛ֙,zr/C@Y*zw1oX,mhGIBԏWJ\><ȕLaZ|ep_[!Wn+w4XFs%t۬*4npcPYߓʩ3,Ay1 6`ɏU]Uk{a/J3‰m)Q¹kFu﷕)'0CW0okoN~snF!X@tY[y@KDA :/L³1}}bKK3{ +p6TijEmXf'h5\3* ''#8XW;=u'<=гdӷipˏ_ XRe xhe7'M%>lx}@!h\}<Ʋ UƂFJ2Z < du[%`h`Z@ /VMqMS] Z)Hs`OIv3\dmxdȷ CTM 93M?:qU-e!ٕ/lWB\'!5mƅ3 Hm2a+tL7\f+$B0ɝ0WsR {>@0`-5}VUˌ, ᚙ#A2l9U%eFj\:<|ۈ:&8 з]-NX"lq*M5᪶-7+6J?͸iF{ZA'yE@G&_K@< qkp׃yQS'YV|++mEÉMf;S #n $bG~WQ{둦< L$T>$HPHiڱ*i%ՖBiD(~^  0XߓSߪLn^XD#ɌJ ϫWYԪQ+Β3`kt.q4hX%̗F?yJ2k^YVե[!teC緩JkęM օzIW|\ l3rNF4&"m8A0_y*gR* J-ӟ#g"l,J{r1 }kb5c yoS3(6KO<*+V W}CH6 /\\~(_\Zq74=Hz!jd&OOd{(/NHHY1߃E~,F_{S~;u Ī9(Yx"형{L.9`Ah  }b+f^^*5d#l>/yV1bvBwrO'um^_ М(~/1DVII8ű~3MR"XOH,(j}`s I)MO`uw:G{QSit?Mt~i\?y>Ml0DXqPg%}z4䢯cX,!JM.%g"5/l2 KB :i6n4 rlw(QkݜKX0ߺJUscfGv0~Ǖsj)Ryw$sVBO6VEРYxt;lmbhUfhnUg=nV£w|;D$ccc3A;S}Fs"yozq)G-ᡎ3'q')N;4zaKPdݯrS^E*aWacoK>3\XYm2!cK>Wazսp fwPz%*]I ;s>EZr9>H +δʑNXwM*űbLS !ٿb dK݃`ot AM?FW${f2ˋ`O!XE.w /'4N_gIM {6?#Z>6Dp DB0 Eu/i5Q#dO䥿KRl#ڿ'{SSQT= ,.~D&c@qZўO ӼL0Yq#Z1G [FsfE#n=%${Zw]K !FDPvIX.݇Xn#X(WY .r^B L3o KRK3T7`mbݱYpѭ*㨞i=S֓2Z0"b[+YS^/SA}xV9 (F`fKPa2ļ2~q4AO%" ."y;B7[a*ر [Ӳs7_Vʹξ\#2DYQ+]}R<)Ȇ#+Llh4VgO8)_nʞV4HYZ$scL5r uW4e|?ŴB^m# dҴOnTGLé)}b.H<-6hb=oTrH!,^3<5f:SzYKJP5mowle(<V_Y1T]w ?ZYAێ=lBm k%ps$imD.78:WxisΏf=~ SL S4< b'~t!$#V{b4,+#u.Ѷ;O EqݒWo$k43/PQl/>*Ps9c"ΓM&8_Qg_ױyr3~VNb5I͎ UC!h=8@ʻbĚP>dbP-m#woJ3koB`ᇚ_D5͌PϵT!J'HM2^&>BJWg7ͰΫ]hd\ow2<ϤGBCsn*6\ɬ (mB foP9_(-?&ƣ5YBKF$!W|x8l4L BMaD/IF=3nptW(3Y5h{iDfW#33ǘ8H]Ka3;{FH&QRZTqJ&Nvlyݐu57}tNR,t5v1"Q tbe+lź!BD=VIDʛiNrK΁f[e+ >E@7jV[4"}kP3\2=j;)Hjźʝ?1H+qCVMk{gJٖWĈ>D o0C8E5_(^]!ApGFXlG[,ͱ G gx<]LQ8\f-[~%Uݦsf3qfUUa" z=_A@mmaאַ,"H6Υ]h{P$tSs}<̫=0ʫw b. S %#B &>HOAiQJ*tan͉lIFiB"`/'N:<+(_dLLb++VKJ((Ţ0st;*99d''TBLc d:ܞH&/a"sXu:)4E7*RpKp31ly?%c vhlE$uȚ43n=l>+hI"j;[#/̜#hW5+ &>@k^GY[`K3h6aXfY#q``lqbAFŘfZ%g`r4exKJyQرXYemWe WApѫ>;rC0TA^۲A9䤀<NQ!oLC8Hpa!z!6<-voI.dd9 d9AZT<ȩKۧ&~ٞ('S=˫Mch}d?pW{Gw]1ۿĆ!G8"@ETGAϞC ||^<17%6?5}&OWn`f3ӤRڀ"1^Ivo6X1{կt).(!3`CZis}D.31xd1#iY^4@5"3S`*Is&) w!fDe!k- VDU ̀!W.NNX *icFl`EEd[?1֒?@p? Ǿ %Hif{)ae]nZ.wFƢ*uggbvj jv{WTO3.ٌ!.َ . iYBqDf3;+hd|M opJqʾ?r B&k3 =;ɥSLt9w nNuo|,lyy:X;e%`T9.*p 8@&+-2DãY4۠uL49-W9"t@ p*=r}9p5;p䅯m1gsJ½3={0it=+?\vhr@12lᾞz?r$oZ\W4_}=s6fj}Ѹ@Կs;#O 3UK[}}O=SqC*w>4"闼, \|/ea d?{b9dT1`\2Ks#$\@b k񑁿UBD|r(g ً1fWT O' .?p0aUvsrDcprPܺi.+__CsNn"8"Wy+}|'-4BsNL~ !Z)bAB靕ڣn:Ap'틲n=Uf4QqB˒?lXvbK"w6P8ˍĸicA`'(#糭s͑yДQqX́D5CSSo|a%9`S3 6Ub$_H9ɥ.Qdk7揻c%65TAjhḐm*noߎ*qY-ѳ$tY&gB@ d/~N= 'cbz9Ȃz=UN(ƅɨ GDs/'?y=3%<~;[P3Ky/u"&7 ,1F( &AE aOXO>p"HiK15[nD,- pRY!+:8n,(`eyqkIxCTq`bPxꡞF 7w<[qIbE\ &$4*|v5*.>?Wi@9dO—Ѧ;ju+Cз%I93Z%?խIv/4ę6'j PCVȩzп\Q#h37gkіUfU [=O:&[ZY%bR@  ؀HƸ'l\ uke\rK}@!y@jٴ?Mܷʰmx\; "U"H d3RlyPڀ؜5OE+UT&2DW?WYC%fB˥:!$ W˲\dIW2IH"ӟ1P#rPm乘r@4S)D&14qS@2n%J@MTIhh%eG7-Pe0RF,P5$ . HH IH*ͧ -҄f!d'G4l)Q·rd+T#H&H.~Ibƃ<ǣN/f>V@AcI&SK,Lko9zض${IIƽzD$E 3؛n)/%CYu'ILd9k6l|6J aeEaȅh$OщKRRpK\-$8n㘬IB{%} 4j< %alO2_v\g_^Nd%j_#d _MC漹Com`gQ1ڻKxyqstpg;6y4kM6_Ѹh;똥Yx_-BMwso&EKsśsy,!0?|LF B:>Ǹ!(׳KS92n/5V>HWOXj SDdsf& ytr5.M&(~J?dJ1n3cY]]*O,~|eawfyM5if{Huo)n1OJ>_eD4 "B[+QT9 #arSR &LٺEj5 Qqn}shl4˿-% {2.$Z)kB֗U,g%-dAKWv}/bJ|>P'=2le`^9sggxSfЃ J@+NHCƩ!< su߂'CQXxF9hž=I HG(/};$f6!&9{uOmauq! r&cZtoE].tb*8جs:}PrW]r~<6L(e @3;1Ha ?9>3mڽx&YnAW8d굒Gk $'d=7p֟񪏆M?$&.X)z㞁)G't%pwmddWHZpaHx4 pZRH3ag̕Mf$(Aآ1v Y$Ԙy}Vs9ڊ ȓw4}ʿ?=^5P[̴fTqMTsu]pM әRظ0ًQՋ^YO&hwm{- 7<Ɛyl^-=43\ǐܕS%Q;}|oxu3~SvA!rHH6x.h6!ɝWMU3ٹ+Ž,xIK)SFA"khquװ~Wt5F>*fmwEǎPe$=B;2p8LC=c K,+y(2FL' gV- .Uܧޯwt7Og qMX8|O4cu994; 1TcӃ]o"'2*@a\N ފ UI4 b"o{-T ,WB1d"e*\LW @P}U_}d3z%{1~W je舯4ӈn-Ĥu\I"}-gQdRҊGQy!IbO#i-Ks|1 .v"KG|Ej(*Jrk_A%h/NO@3qR{RQa{{{;$ /^ Q ^* _rc/g@cnOUO\n,'_BS$` VTt>Lfr!ڄtyf|Yy=˵#_*eK'̌ 0`zEmOF钍{-.Q7`'Qm(6]$EN6Wckڦ|:j-AT$=F%nJ\]%jG~ 2^tf,Qiڄ'"HarUZʿie_{ N lS~}E>ΐzx3SS^v0jĵٱc|"_[gN^,HhZP8-ZzWQ}`:kSOāSq^iBnhnk~na`oF6UB81wq(5Puz ?bDtm)kf.zBW`.3`\* yd%wӉⰳjh[%Tb/b95຿.cL(rT|Ωio'-"e[AԅEf C 0&" W NOqN+b,hsք8J77FnzY89?iz ʒ^r*$ 2cCa4Q1> OuI(1MPO~5xh3\K9OZ1*yk[}C㎠nϯ!.H$cT䆅q1jPڽp /ddIЛCN-ڍtaZ'$%ri Q<5MLH[d!zay(-vzȄLWi nnj.%Ī)P9.ݸ+bbV$pܝ>bouh }I=_;?+{~?_o[..W)S; .vtoȄ`V̋F6+ɯŽ!>=1]@XB66y6Izu=~Fwvc[H4K*bzḘs=3MVɭz18L޽o9{V?p(qNW[mjc+lpєw|^?:x+u!L кzǂU\f~\ϓ|[5ar ^Z2vrrρHe}7 U-\!>ljZ۴)تQ]CBҿ k`n@^QxsYc ӖVnrF&,"jFPظ*q'mb3Hh0B<{8Dnn-wо9Ke/z?ȓmJ"Jy$T8qCq෤=1JM8'v #N{g> 9ڃOv̦嘱YR_, 3:m(T%qBx9"h} 4ng K }@T߰v[Kn>%dOԄ&\?E܊^]՛iCnL\ ,bc֗Do 641 1eIG.5С?.>^M$u˪4N2JX[\Ⱦ|9#qh% |(D٠`ȠS|$~XY}YơXK  nD9ymulch1VzA-HT!1eP yw\/#ٹ׼]YBu H^:6JpEKՆO5fn6F';Af4RxP?Q. G7aX!k7c)}'u( mO4 55/a fYx6:ZCg?1`V,<;$P+ذӱy72$ΈގVt7GdpݩM lR*T* .T\'Y>g04՗/>op0I$b(B:1MmE 7p<@a;2-QNDP1jPi_/X=)Ȉ=ͼ璞< bTQGw[?7t’8oDzscx-TpvZ">+!94R j~z\EשA{hbhX3#3DЏmkLCƪӕ'eq M0KJ-lD4Vi^G%&,P9;Lw~1#3 qm>Py)60oI58!{ 48QIλ`֚z61xC`nKUI0mwwue45#Ta-h^2d6\: rAi O%.\>2CQ[֒Ao/u}};6'_3 t~LiF*DAzQsw_;e`lk}orQ+j耗,. <RN)MɪX6W?9FN,E65AgzR7 *HfKvR3#A6Y9,xn0< 'H ƺL,f_a_LYN=3QLdl#L!@@ssW1;^Og-|fu=! #9CZsZj,_"lBx%YX e*xH 𸥌ȗvr%SBr6wC4`aO:hI$*y%$+UBo3kCO%B^-)!L 0? ûsd%Nn݌2I;gK81p^'orQ ̒(i?Rh]Xy{6WVE?yUv%thl&IjEIqkiOL;@4ۂ@f=$X/~eN:2 C-|R \73HwOvb_  {e߾m˰(2C"0->/RoҼtlb5ݽ0 p+Aݲ{&Hз*R, ǻ+TJoJ%V-RAkKe,i["P%a\_eW 6 ZԱ-?TwיС-PasSZXb#0P}_(w.Q$7Ñ@!"dEW9hq;r5"'}7jJSSǐ=p;5o\E)3צfFSCU{H}xH)ŠUTKj}~RPQJD39b~6'U 86[9rwܞVue% \op8Ehe>&dijhRW$ֱd+TSXj݆FDs@߶s*0OS=h0&:p\x/4+enVֆ1XqlZDEF~k."YYh 0JVve˸LLДْ!߲y5zzZJzOJ>S|EmFNE7<NwPH[|BzTSͭ|:" ~zqy_ =j/f#VVfub6L7= QENSLr_`CpZǍ~]O}|&ޏzS_-7p4唸X74g5ѵdi7d&&lAfuX[B͐7fgU=^|~%|4GlB7ug1g_Aֿu,+~VH&*(w}G[H$*΍_ 4黍ZU!ojZ3RHtkFHGoRZTً;Cs0[ lMĸT] pߘ4r4@DžV sKw!Dt1:tԟd_H&I)fP-jY<3#=0a#t8;$~}*HTQJ%U!k.|22_;yWY4Ge{c@dM!Oft5L %D0GTQn@}Gk؆ZC>=$~\7#wn}|4pWYecg0v fCȊ1BʪY$ ^3q[aV` G0YTF]X!xIMHICd~FCDXj0uU1'xpVOeHneG옶o[0ưG}`0un&/1ĥ8 'c!AI&S7"+T(vjN:B= A4zEcgKU/8wgs bq #µ9GU+I`P4K&%C7kr>[~bʹ8i-9NP+Por5Ȇӵ"mVǚdH5NndD XP"_?Msiow*l&U&ӳH g#.89ft#02.'cbRheN=H{ \TAhpKog7j<W0Ց">7&; p)\z9@YΰizZ9zM%a&1|_ J"'6Eq!E?BtAWdݗ$Ek3]=0y2 'e׸5Qx=.7Rke_xx#/ws֨')H4n%60 )ka0ŠdMM>`)ݙU?iߢ Ty,3a'gEgꑕ_o[EJcFV\}-Tsf+\ >3V"Z@Ue\Ťʘ:E6&iqG2qL4bֺ+& ^ņ; .-6Uyr` Z׼MW ,x _`. AI&Sbp:zځ @P{V܈)~xBft*}úHg=#KcˊM`ߎd^ Me:ɻ:Sve2b{`? rFPwVx-T5R6%(r* }ې;nWeQבmaMԮ>6%= [(}a}6E]_7cr\j{2)v}lW?7<@'BmJ ><,}"ƿ8CfjZwD<Ϛվlu*8 돉 T7{﷼l+Y֌nAВkV'_ܗif@_,, NX gs{=m}{Y ! {[7 eϦ%!a$fLH Y [aETkWU8N[p9"eY׀e C^V) Ȋ"R\q$`C=|Aq,£9fK#q0A~yQ7ҫ5HhD9Ζi{} U:Sy/!X?<7Mw5q t櫘F8P?&^9P=Ԯ#k%@Qt A3?ٯX)w~ܙF[ Ɔ "5e@xm= .mp#Yb ȸ5k#Bl?"4NF2,J 7[XXlyf1X_|itt=DxNAK$cn1'91@M4qQ1G49}A9!5@~cC,IG} 3y_t,-uv^AWZlH]yVj xYR@}X$$DSD!5,6>Ήp(M0k?@Pkj;[ښI0K|v"h]wZ$z4a\.Γd-;"aBXWxpw)=#ڃ`ͲKL|[jVCCzAIm6h)ؑRб1M8n ,]@fCݙټy3 =({&TiM&gj3u0kC O1K@b3r!|^L/Ȓ>ث1TZfʾ˭<+zBY_fGhH}d OwR4 c. +P,Yu\> 9dL0Q2$o8#nXuY^w$@2!׶AIC#~$n'>'P]OEf[-:۔(+jݚ&wHQ>q؈^UO۹Kq#x`~mo>]63 ?+X b.@bEѦ,Pjle[^UI~ D ژqڳĮqcDQal><Co[_Tup2i\F` WҨp зX+찏? RXv2SB H>6'~p՛3=)4f6r ށ`<6v'$jF mm\^8ZVkW7@/W+|]35USxoEkdr:G{6I~mcHV/ڣ,?‰ UKA 2S}m$W/J}wnmh܀bP7fMeFQ}!7&~%biZi7B`YE,,v/@=Py'rD4*z xueQ3e ~ ^G.>X#t:<`şޘ^Ȳ(+sTiñ'*/PWe2wZBB$MK)m y3U4dɒU_P 3%LHِW.p" O8ù8=!ڜLG%k̀}-:i\ؒW+SqkJɾ[9l\,j6*%7Sf1A=7겴 > C1 7Wa"~8k'$q#> P4Kg^\Υ@yO^=Zg'`gqwϕfuz~TT?SP F3eXTXa5$JW^7iz' +&lg6B}.Km%ٹ*4z^y!^|b2т45{ !Tk Od, H*H $ Ld.t %5PBU}[Gqn#9FG߇"rl%̋[k]muV+w>;kUk:uKLi H1#[LL -N:A8LB.2EUH7U7| O@2܆,D#p\+M\O8=s+v<@([ ebpLJq2WESH`c^?a}~j $L^xg& JM\ 6eZ'^ԿRIȼRbArB|'b?DokeCqB Q1?8Omx1&oI1`-D$2HtO"Hg1%݄띅qXۛ&vĦ.?Ȯc֥ȁ){R>4*CfHخKlxY(i'My"l|bUp߀`+:bghikʌ.WrՏLѯnBuک:&Z(FHſaH=}6.]J(H}Uz6r, 1`P#3 /L\n#w 2W+ӾD=^9'g>fL~OZqŤмs[|vSɲ͚ ڇ4K "1Їc5'Uf\/-'9{CxڲGur'0 S[U{12;uS8w@OvQТu)X0ЅG[Km $1%]iEK0=;>S7T@PgȾSM ^fn)bS:#7;h b[{Py)93YNFb5=7I$v`v.3'ԧl.. &J8"fs~!s"zG|_bU={4ZLn)jM/Oah4{5jQ9q?@%g{)20S sbl3NuaI??J_T5p0*.0p<S_dliD2a̧M~uq*YC,Ag&I@۫H@AQ$ *>*8mk_zJY $޻WD \-taI*TS ྃ&zPяsIΓpk6y?raM1<";迢s.24t C<I|7tH'ڕ(jv"@H3Z(E[0Qh=vo¼ 荮D{I'4eB6v,SӐP#Ix,+xpi`?F"W`*ɍ#ya B֢H53h{Uhяe562Ze>Zx2I~t^lh Ax(.0XJIcq27-<+3 >nF#"ZҨ2P!5 TLtJ";XL앜ń|tԺuoyB@A<+s㍖pBc Oʹ.:聀ŭAnrexn6^  .d47&4ȱrѺ/N FɌ-Ėp aVEoի7Q")śrb )2 ˎBquC0,JB,|6$I6k=H~a o2bPed^$XJSJ&Ieqtuc03(h,i~yfZ~^!Cd+~LMhoݹ>l{<6  PY/yܹ*yH˟'!R-^oi~ozZI O6hhf{%]+LX",8KY4Xy}(K%q0%0 ,V$χ|5L aJicvFn0ޮC_?ěGҜRU$^̻#=[9ԬaTֵ#KϤ㤮辻t_%1xm<@V~IsŴA$gدjL/Mq}|(!hU`<*bB@hr`WBEvh6XfKA O !RO/VȢ-DMFTxu5PƊz~,k^%]S' ({s^sۇ~nmUTUZ%c2vRZLeH@T yHI.U玵$J lb =7K8! „kZ!= Lʌ!Bޙ)0^s1)~vI@Yrcm 0a Lez]?ؓw-S6Y6Hˆm5<Z!ϰ!Xv:ueb~-=|b!wK4sMC@ 6Pm3+E&2oI>D\"ǾGbçvh"PdfYղ'NםJ7o|i3noo,;]uOy7Q L)8s8w*-~,Kp.lv/? 4H悟+%D.c8Y6&w ;;ց0*Jd߿Luۙ$$=_٘hj |ڑDt2 U%MBTK,{P5cؽ&ZM`Fb9B?| FXׯ#M?|_8X^btTVQP@b~#Tˑ:aRF$Yicx 3#!i2~6fCr ]Q Ook1Mqx5\g@sۚ}Y]`r]o1؛hDt0ŀܕc4գ(#ـҢI>q +K.>8~[dʌXf88OЎ~*1ònb236WH {a}/wm(6 `JQ {d0\ 퇚chB#s]@*>(m,<_d4kUJS0o~r-ѵH, :BtE^-K&I΁g_0^4L,^$Mf([bAA>)Fw{`?-sIc!du7Pjġ`_;T!::q.]ڥ*|˂yDa)YWG(P+?l}Ɨ@3ꗷC\u[=>rjmx Ԑhe|1;^28l~ X>3QfKDžO6Rk> 3*ɺAfY}gzxYfCH/ZpzK2|_{.GmUX]]ܥ&}ВFzYfi njxk{黆ar J#tjzĸÀQbҵ^3# 2fSLTGב`>搙S8 `A)I&S"`G#ձ+ WC5-Ǯ¨^Dg;c'ɘ Rx|X}騁1zk(Eӓv"syfd0vȥLn%r/@:Uuv.a+*8j2 (g$p!Gy ^7|a1qB㨚5B2 EkQºA#whu7BqGtZΪ2^J!XdJ؉Yl:Yhm3ԁעϋ{_TDU Cg5V!c?YD7fL-%XC_6QG$&-0:XJIlOިvA" !Z"kzš_.#g!*DDÄ-/D>!4k]$*fcKq·|Q*vڙ.脽Σ/0>aKA> @V|F슓6%_福`+r7M.z+skE<"]nI (o-]a.=OaL?c `R]9 V|8DŅC@0jֵl4&F '%^'F#37K h˲Z(O򗱞ԉ1X<0X/qVf$'| pQNpsV8xq DtM 2Ր:NuC&\ED]H:ӢY3:bzmbQid{%x)h q&o kr.`֨BȣJH >HC4r@YYx-qΞ)N$6}}A?HFfֺV]1DrbY4'+Gl%' ̒=ڶn_/}Kfbzql5:f֖șijK)}F~$2|*R[t-2\Y:J یҸ2.b᧮]$si-Uqԡ?]w/W`{z B kMMK ~?V05it#̱\6~EzI.<͇%^%[@( #t\^P[ԗ"Y,_\w oOc6 wkAj{t%Lkdx "jPKԊᑮζ-VX@}@h!1$6e]JEhQ߾ptaaz e@}ɶ8`{s7q'm* ֹ[/Q^1Bd$ u?~_%J$ ?bjRHә+s:fTXFEg~؉Rj~qо ~V*f ʯGN&M<ѥ/fUGԨ&zݮiKʻNs}E;#5AѦI.Z3s>o`&Bp2sJw%x΄o޲/gweBO+]YTPڬ#lSae,MEk oO3 UfWv*V,f֏zȾHPI>!UG垷tsRm7rieYVżLI߮χ' 8QA-] +Ǖļn @ z'q7zkoUd.[bPz O'3&!=K W6EkW7M=z4rz/#?80).fZƳ Ej gp|17g@bL'KE,PSV-'vIR( g̭L6Hin1'\&[  pv%*LA:H"^Lj^EVgk1oSd|QïXRg2FǼǗ ҶXc^*9Ilڂ.XE؃ң<,4<AEOd{WрTlKyME$ƈ95C!%oF[f d)utl bKUEI79rgo(Ug*, `9ɂe"z6Z G/:^qqBuIsڷ`"i?ɖ Q2s<4\Xuaz?Wj%Kɦq:j -=D=8N4Se?H"70fG5gPj.uW Tv7CNUO@%`"~T4W2baף1S>+Dlf,p罣$v,y8f3Oa2Ff] .MwF ^2P[!^Cڮ$6z [EGA."9 6%hya |mHgPmӴϖh^Rz#KQfv5*{̀rE> ѝC*}h`A|V.Llʡj!-\TSMڸAKZ5L*T44bο,,t@E|u 5")w^J;]gMyUܚLz*O⒋WMqmh+D۪$wzV'=^'~0 ^ˍ;ߛU"tYuGvJ>V@_1OW0v |HQ:Xwbݓ95xd,QȍS'!aъ=}RK:Pf3<5QufBTo"@K"xjx=F0ݘ:\ q'΂)OsP0C))>ܡN .ɠTӮ#>M4͘T9e]\x 9!fl? ^#KH1V805wXA+! 9i2,Ce]?d+1uw"cW<%X90{~3*`[@o7KwÞo &w/J9܇6 }>"^yxHQC JwmNdt,.}Bm smmJvo# &0q=ߟ֠IKS{!YJOy[ m !8F"ܝ›ZdrKVxJ/s%*jya2 \JsFH5a#]')Ys)IEIF!D8wݯYUͦV&mmmeI1SmYebM;LIlgD}8CB/S,=opQu"cώ Nyikru'|0>z< #FWF3Z< m*EjCVC?ҙB!/ONMmO嵰c|5PDSϩhb3f)w%dQ(tPO9o(9{TYvBuظָָ3gM(H" +1URE<2#։?uN eb* ]&3#Hƃĉ% *c! YbA鄐G) U؝E`&GSU/)9ZUb ӳ֑UjՔ69y<@+ԞP&+6 e =/mPU"F; V~&߿xw,׻i6±_n=~aR{T J\A|QT6 nZ,,hu{뻍ͅ xK*-˜m8췼X:ICPdHQH \]l.OrSXK@j6oVoS@S]2u#{OD#968,K~ےq|XdWDaN5쾾F)$R$n(ҷL`sZqL .i)ДT4>B]LG& BZƀe~3'XՒ/JVy(EP6w4,7?qN@%ldk+tSx@狔e|)F`e,vl1QTMG@Q!?toDh_" 9ut_Rm)GtusX.tw!Y#8dK*D">mPDqw2,ag\Gm]7vnpp|;O*Qgos#w˂~Ѥ*{ve.~u,e|'eh\5 bş/'s_ \STjq|sToRmi9zMxxz&ۅwwNQT͓PCjܜW=p#*mGI'yg$/f2{?7h& ɐYL<]s/͢ǟyc@CJJ%F4N65V94#yj%ܾDv4g:8|8Zs(u u)&3S,yƬfl JųNιIlbEw>c$rj!ZօKb@!mä rr 1wsL.`k ..V F[-MnTߜ$ U̎Qz3i^7doz%*Ʒ}O 5?o#6]&))4>9- ⲵLJɂpcP=| 1b:/ .!Ϲ0Ҁ[%Kt]A(..s.PkoUaR$掘h36%Q0zנ"aq"p!< pIЇiB8 O Nczhv&kp6L&%FǮ퇫 v>qs4;5G#g"6&.C{ BeB5Dty@]Z@:a/'[{`yrc x?_TSRNM~V=wn9Xha: /5Ҫ=CBM2aNR XoGh]|{R4H|6(8FoJf?uCJW\xT\Sw%a$1o:1HQ!c8FLdMvu݈aΊSckBBUreO2O*qH 5 ,wMi޸-lQ,Ce\]rɋy/mhh٩v?LѱS*7.`T"̕`lfN(`-gEq~=i#b_ aWXsE_| Ti/]eZ4_~w/X"<͈SL][_3v/.BA] /@z]M[írčǃ~:׵-wS1Ul&ꊇ:.BkNR[YE9vǸE `?r'>"r8z6jBTejGJ)ѫvbi)e?S_m,V`Ѻ.e@CLOv'9 (73nҳRpm2֙]JF4>7_6UEG JfE{bv+k]K\cl;Zoo5 Itmx1n~ (a$YRV  Tq;JHs[ָf݀k8M4^_VVT ^E 0w4񯝕B)Ԉ@ؕI%]5Q]AQ#GDBw'iMn! iKTkYKh][S*cS3Ss($3߰U_?4Hu,ƐS جA۶K)gBKVĤxݙHsu:n5l[);apr1@iQ!*Mjc3lDל:DMBFgQ,eXr0KlG67Xmp+ʯu1/ǠK<w% %9z <m4b{2[NKJͲonTe˯e%ctRXl+7!V?ksø S,_ǛC'~}@nĢ8V،.=mP\.+eE2\32{mYOk_DqTDX̀kXkf*DP8aT0 ߞ7{3vÉ\Q \يG($GbB}RbT|pI,`'`n`1Zl]jɍc*z h- l7n"kHYūFG()NkyA^} s I"0%.dŪEv3|׹:]G47n*Յgfqʫp)U8n]2DP~ 5$Qsxw/۞ ŪߐRX5듯G{E6hHϩhҒt$ys*nTsЛnj|wC@+r3fTc1I/n7҅NG;%U~DE}8m(dy$B wb\hז}tp20>ع[!079nZ΅@?+fZyv2Àj/kM$z}"!$ʗeIj(A#V}i63~яw|)Ħ~b @eF}Ԩ6GX#Ӻ4IppNlnմh:5"#d= =rsu`́55K5+v4 %-E@^6GgOOIAǬ \[I7*K>\w9Mryu'ŐFF.db>$Zx&)J)_s.:h-2rˆ1Wi%1I#Zr1z;-.{3}JP+n5ME*-l`̦PR D3զn6'M mu:dV B,ׯћQ'|s쑰JB9T n6yxR6_dY#H4ϙ[}=EjQB~dYvN P(:4Vݴ]2`;u :g|N?3hQTL7 inUExPԃ5ڏN>7ēl)ﳎT[3_5zU`qf$K+kXKOcż~6 KSo8 RVFyߤP*@T:}$ޜTcW!(]?D끄Se Nh#;d9&] P׎9afR~u}spdM6y[,%Dn`dG/yQ <0]ߡ*OMx#J"n@9:^p⯍R>h$#RjsElY_gJ>O/Wi.j^e%mLʼv*8&V>!:͖ja+A-ְ3KgwU8wbvA*m~`\Ut.T7K^Ë<7+!{Ziƍ_|rpo]SG]V+ѝ~[R:}Lq[+|M  WxWdC6@\<[hi;'~GЎaAfz+* eTAr֧9m˪C]{iS2e(~҃o9,v0XM=n}c6O+{m^/r3v/Zd[͙*H(>wƮzdGre/1Ե@'MKMCSwW :aRG:-`9q \|-g| N) e_^k.^?'8=F;"âAP" 'Hk^ί!]Ր((5ŝY/1Sb_ڈW[LyrRx"H&= 5PJGϫf,ñ/eqj?#$|!C=!eLޒ;0دpvY9U4Mi AbE)"EC#DJy#?DLjl+FY؀, 53 9 gEvY1R'9GwwӻlBhsT)&/3gЀ [1y&AQ)vr ZCOABV|$ =GC|\5, U㋹5Pe/&kWl8c@C?#)s܋i>>^{%ꖊT=<3ʀr?c^Izq⥒hy(xFa- 0@uMϥm4[kUW Sn\ʗyҷRMYOBb22R/kxNs.~3=60V"jhJ{MYv%&`bKU|@Htv{9[x%hWHw>_3 jIԌP*(e(B?g'/;;4FLDu=nfL2!^/ljEW$Jyj_~ 7B [ ^P!)[ ᝢ4:4ReoapPlR}͐ٓdW޺yE YxL6[NuK- CF^΁1%c+fư!۔A+tk oeͅ4\,K> ZFU]D`_HA.B_%YlrѨp}R#;S#)ƃ>pgn78|ayQfK 6"SaOdpLTӥzQE7wg{{Z ,;g+J3Mb^"zl#Ũ]3Ec"(;oWdC$dUEm8E$T"؇Ψu}2p GLDWҙSo؈G69; (f!ʟ{0mփs|. Fk>yKq& -A g :S5T䥾8SfmԸ* ݆iIw h;PŽa)V\;ɬu-4s@Am g }lg|Ɠ KBjԨx_5!^Xw;m`v7̋M ؒn' AoY |(^!#ƛtt9BW'tsP- aFd&P؍Kn-^2!;U|#ӻfyYwK ϬC!`6S}HƤoQOq 4b6GjQvWrbuul|z>چdcBUG٬8/eǖڜ#p jEqr|"eܒL#~8HصqK}F-,-G|"rZ/٬ ܒPnSUȂ+v\%rK 0H;zpj@Qyօ: #SEeY%wr4"qD' 1ƨUh(L’Yw_-1%8$x zdi ]Th_ӱZ[ Ti|`ҧfF(3D0%n@3.7Ery%  hk ݗY8Wܾ!lz-5 ;>ٹR8~R\8O!'9&yZ'4tDbQSKv#׺)C|^0:Zӵ)T e@JLB԰'CcV+@!8(6U )\RP>'i*C nz{7ęCɑ^N]\~`wrl: J%{nP [+uf#@5w~Dm "#R(U\[X\5 ȗrMN̼oIUVUV$'O,^%np‰xESTl;IftCc_Uix䀅&׻)^U䪝jwA2ZCT:X1>KZŊ!P2iP2e=DAeWfhWAqi5vpXc0w3 E} LQy+nA߆4a͓h=a<)e&b}-G z)`@*ٜ(~ۥC `Җ@~"9Ʊ ܳ/tMȪ-Bs{c )aj֓8ˉǥs$8,D?h ALbA+DtcqFF]WF (>ҁ59cbg[8U%ry:|YR v#tDf8(u;KJq\SNL' r6sKeoLÏsB-kq>3z k.A8L~J,8'` 4 j"_9nӉ^& tKx,;g^ '@e-PˌN%Ǩlʸ$j=EPw>.&PSu@6+qLQBD>ucf/ ޑsӠY .~a4RJ߃oj٥(Rɳ<=g㎄[N}v8&k"$Ԡ'1j*6chv>4EOa j)$l3[$YbqB[}_Qd{,SJͯdrM =aߓ3[{_/ l*7m_7S!~*Ƴ}A~R ӪIϾN|>k" PIhdzOf{PIVƛ<>ZE`mJtm`I֩ nxWjE8e%7a@!M> o,oTRKp?5dZb=Y[y$"0\fPғ'}"7ګ@(z8k\AĹDE+EgĿai}d}QQ$x.%h$_L!u[ v:Fq`cHH|2 t9w=>. =$K vc['-pȣH @/qWNƯ`)փ1EE1 ReDWZ7{< 2k \0w ^3s0 L5λajnu&wØ}kntoؚ%;zF!5e>\nl9쑢s%7s1 S]*RaVX!m~/vř{ʲYŮx[mXp`{]lA^L[yž9 &!/2[|Z0aАIkqk7dx+0e'zS1Q>'_EqPoMu$rHU::Ú_ >#C2nɝ]Hn{Ԡ@%p^2^bXbkɼl-Lqϣaܷߔ -pVyYk>z3_@5!#K .t}VdH1Ԥ{T.ihdM17@NK== ȕ)V3[$ce4F!#i>CN*2F,]L>w'kX7eq<(0ъʍ 1+Ml 9$1ƈm )2ET įb)kLh w?l~DZm D;N?}k͎jUc"=*7ǭNNZ#yX5.w_af'B r03ɿEHsWjǁ8v2A#CpaȾD>ކl>ϘB^"ӥg*1-nk"721=x&" *9KEW~}~ UCc6 o](*O`,|,5Aj=ÊVw5X%js௳WSwL0skԧ ;e嗱.UE5ZSx>d?t&,+/aŔ[i.|r3Wqwo!\N%:<5n_>f,@!%L-h\wys}pI_aWdq%4! ]}מVʾܭeO ,oھn?_9BPz`pCғZo;ͲIOjn ֧фZ> u n"E1`":]vXǃA̰ID4rdW7wfH>[6,IRJCCU&ডmwII%+ }=J ;5p.>AS9.ƣ(X)Y=g_7=7lMJZ$9xR~CY g Kм@K.n8?/,Ɔ_hݼv8w&Ek*W‡owd1MƇsT FU)d`j!eCrCH|bZi%dfJ)3GO}r@BoN9**Wd7S٤T;իEH,,!}(*om-4xtNB)l`߻Ҝ21۬/0[V\j_^J~eV-9'Eg,l5,dK‰P"g_]pHdy3;rP-ѕp;"12=,pQ[Bv!5j-ފXb4Ow㮳XRV0͌A+~?rǵ[Q^4<-n#m&ⷷl%&]{od 塱NC"g&?P>9Pu%.m.՛GCt6"P9{HݿtRHudٯ 4pտS{xN̜eb`QBp/rkݖGO_cXcDFB"D}aŝ ժ`Ljv i~WdyojDHuu/dGZ k>4^(_(8 ͐92 -%~6޼:|lw*'vujZጓX[ }Y{ƘPwŜ w tkwY "">0bYg^8!Ń D۶kfSx[+% ~A~*^'o@j`>BM ϰgZ{.U ^?ŏӵ-"47\1 g6ʸzF8Lp¼iW@d $^T-_94yݚ X] i}6)|*Φl+fXRr` ȡ/ulIT4c\BYZDI^u2~`;*$0Kzp "&D. 㪆;0pAxN+VBՎqC7ih%BU̯%m0tM["g^SլNz1nI1 kxVe0?/u:~*EOZjx;@D'IƉuo3 ed@vz@sn E`vwpfD.(8*U&,KagPR7W piC- wM}VY.-A St}mo/HİN1jm kJI\.wEl)nȨT9/D:rS7!7I`C]BiG@-+&Njx[zlQkStA=]tBͰ1g\?uWhbUAxZez*RxaP3XZqo_7#00UՈ=Q}+4>7=N @h쇰|fSk YXp)ԁi\ { }=p5&gsL1"cH/g?H#W~NK# Պu@}t0p5L#z<ݢUʙjyMT`!R$w#+hCߪ?1!ryԏgپ̳Wzܱa!!@C9fijFZ6Kz3X5dB!)sg^@@A+v! T0vƧ9y2ኴIabc/Z{tyI^b}`p&톶eE6fDždF|ȰNNF!E:ypKdxC/T Į AI&S8@?uՊZ/wxgIOkD}δ}&ė:нQ@dEM0H}}n2`u8wsjcJp05LPjŤw)+'+/%aRvVbӌ8C? '88N[U OLh4ё#s ]ox䉃׵)њìaDqopG;%l\jȝdH?_* ѱC|szpf-g?H'= O D~~n:oFxPF5DHybbB4TA'X&?=5| `<" 2 Jӿ|Q5p`Ö\+£⧏m[)rG0[‘/9%=P4Č+`KP>MJ`Hh ;*:HT ceb̫[H{Ϻ|J Z5aYc! 2LjM=]fp+Izݵ}"nUԩQj\%pVnˉ^ I r= >vitE k&&xF%ԥHj{ِ D~ CZ[S;C. 8mdB,p#5BB/(swj`A@W:y9<9*-&455b/vý-O_;C/XlN K<^`C=nFr;N+bEH͢]| 65*F6.0o#Akw(F=Qy>'Zf/*9O1PoYԋrc*y]ݛnyXd9K3}uXD]X:_GLG5o툱0~ a5 ]_x{ |c6¬7GGGs= \<.H@欤I \7[-ΧR',i`c.A ΜNz@Fp&8KTyJjԋs$!뫞i9 ֻ֒@e (7s'2Vn;5˩&o-/{FSP6,R̾1 3 Dr=UܘL*'AӪ $v؞uֶ=zi{ӣCUǜ,3X8x J?HK~RJ?_vDhHIư^"gە06"8_-q&iw瑳Y4?yDqsr5(g6ך_qT́ ͢bxǴ'#/O,]R9!$~#خUCe"_<SBToΐ$DIh-G !s(v5Z_ )O^TE bh2]F؆N?26 Q\?)z]G!v6)0><9_>=IoqJ6ׄCS|knk`vjV@y=ڇ ~ry*_"d? ,$4SD> ~*=W@@,~C^WkC c&DdgzWRQ*z+E+Q|7BrG:4 |u3{ZҀd S^ĺ=Cu\ Zд\[\8Qj Z/L ( UYQ ]@nwUʿrfQkچIUӠ9Pl߲ H"ZzauyAlƞ.}Q|\K* EqE7Tf_A}eA)(;yIW?0D&"REۦb'7ܠLv1·H?gi?=PIѿcDV_<6JBY+M@L,'nhQ Y![CÜt(W &3MJ0>21cOB@ E #nבv 6:!9:Sڣ f‡QMRmspgT.a>AL@?"^t=))_!DmƄyygu-t 04%N)~[a"Bk3G[Rgy`>|ȊglN}պb]* V/L`+XY8Saɸ@oP0%^]-kEˍRcu0Sg 2gM=-fߞ*SW3.ٻ9w.!'!e'͍ 8ڬ~dY$IÆGإlNu)`1 I~*.+'b@^S{h8o)ā;;BV0&Lwnw\O[_f_[ѱ<2N mY|x_SM¹7 )*SEɖ07?hFUW:S>-N`>Ѽ۫vA.󦫂8C'.NQ(_x WjւwVS0&Hz 7ݜ)oյlJ-LnRBBm*ȖS)<Z* |[W2۶msސn+Sٍ* BWozE\'}I O2s Ã^%Ռ*^J2s5Hl!"!E+5L Bcy%Hތtxηxnٳ 8@b'*-^hzl]Ô&eB7ST[I -!)yUk,@>!p(K06؈gβc_ 2UZ#u^ XILfC;i#?i%:|0?1@77\E)+_$qrV< p|u#1 >*x+#6͛lrALGTJRz?5+uC,5=o+Yۉgt#)ji]ݵT D2Sf:.&_=C{b.ǰм$mLm)x}!XӋII뺤3ZMg[sgn{ۢ),ð3| '͒ncw$^YY΀7򇏭ޑT:}Ky$y8yK~`bk?g,c뉖TPZVkYd56#уgdYGe Tz+.;XKN&_v W(m6vJNG6:x59JK@ ,e#R?L<>tamg{Wсʌh̒;Cg]X?TvR=H0IǦ'*1TguB]8*W{shJ-.A{ .'e(A:cOIhC%-~~>;\aŎ.&.C.8GD0uM 5ynؕ ݊PaldyUqlfT֭Ue>B:wXu4+s4x&VTHGG a,àVw"G+u90d1r=a&'N;A'VDշ 戼]+%wH]Elۦb2ڦ[+}_|W tMtd|{F}N̍s4v}ov. +|%40 <۷sGKP 999 r7 Yq쥬b 'ڽ _G:Fxnd[րd~y@"$˛aև[R_-cQyJ7,_MF\yFa*ZGd3wiU,G: ]UܹG:4NwrLGI+Jn+k[+kYs;>QG:! "V2UCET`jq&-!Jgs/J8KLD!s;ԗ|Grl##T0,JM$Dh/n_4PHHC!B<!<2X*0 &QVW.\[r?|̇Vq f#Gz_rj7<:T!%3 Ǫ6D9K а)1-V6jGv: v*cgSj{:0ɲbwМ3`M@B,ClUCX؈HwBP?n^2k 2:ׂ'HB A'nwuU,qpAo?ce؝<.Ą"vlz'|隖HsqǨ5H-XhU&.<1ߗXOmљJY[m(ZɆ1DX?lz=oA1 Xr*i\a֗9ډOm?`;,FQ`iܿW){FgLLRa@L6?SsG,R^%8UFzIx B5u.+(I":84?gWԉlJP!5ܒ8k}o]뛒*uX M-NsH2#̑|o!9 u]5[V֥lWSttUPf(Aϸ7qhܳƷ{:ڨG6ST]NT^WA:nbȧg.U8ךnK~DGƒ+o:lPpJjHPB3t_If(T,Xc5L T|3Hx7xܵV K-id|C!c){#鹶j|6ǯL4kY 4,C=>\Ia<!4mi[pΈG'qihM .p\/ԅ!fH(qh y`t8ԠLCvu'*xv☙]8֩ @Y{ +$@O6d)sFRYq=ViIkD]O$7]MF.bNa5elT.ZB7]k"B|aQv4/N[j[O;I2o c$`܏ stPJӹzšCż1}zsPy=Y~jQ@n"E4:Xp^_N X?ɭ"E_LxW˫iq%Ę&s:Po Saj2}^#uNߟ:#>͞@I`yR[A8)o~vJ <"H攒ѭ=c0/@ʈi˔:P}Ifdmp>4Je/إy'x&ib#%tA=ʗ* #8?0Sw )8C5xgai{{GD Qusȫ kb,+ dJk~ 5n Ћ|h=y%ki;upIji z>/pE8݇1B23 zw৕('d*х* m@|jA3SlfmsTU-w#Vz>ګOV7;_tRr0PCT;r bBNAO@\FoCOO}ws32&W.R/\~ʬD@\=IƱСyPB^PR(}={_+-SC!?v؅#fkgqB2aj1Mcn!a[+ugyfByn!.ɀͅq#RLĕU#`x< Tocڇ5:MZ0?-4T,+5B3S7؞l6-^fѪ>;奬`U>:HB2N6x֬K2}ɗ-jAA~tv5 \^#V{9` K0gY 'F bA0'(?3 > P >2K8}$f7˿=?{gBm)ŁYmHUnY\%m:J㢝Nb۰C~0aE/voN5pTGA]qViaUVlDiSAU^]R%"A(2S-eܫS (,%H3za w~}{0:MU$2 dHWiT}I.7O6ik_5xAZmUQWNO_VK'C[ +4_V5O\Cd")kW'cn3lI]Ղn~xIqmG# E wj:jt,G^1 ~5jyXS=V0mB#(K&g.kRqv^Pk:'+:'4ڃ 6F~Gt@n=e԰Z+@q|dOJ: 5pi+}c4♇m?E' ٓwI G{{=0zstRk!)!" ˼%B% /no9k3lbLI's:%`߲4m'/X@PDL0!yvV2,ʽPh3̳r͡޾PJ,F!@Dd 5SaqV*PDzt{xYbK%:s!9ȄP/0?D,Ρ-O&Պx{|L2[+ rf:rRAI%@!%Yru Q:X :lf|ɁPXiNlf*gǜ]fMEux~0¹>"T-l?>`"n.fnq cwBnA48 8#P|Iɐ{f:Y}C'{--}ЖD5i[-?WCQ >HU[2Qxퟹs]= k|VQC6sЮ‡JO!^٢ɏ ЌJA"pp7,nzB)r R@g1IUMgr@ܧׂt8:;F뽡=1p N}j@˷uyJ n ?br< | 3/HZ) F&,+i ldD|G-==:b% N%c13ai3W(,B͐q/Sƍ=O*Pw^ރo')!' gf6:.q;xD;ȶlL{p%K۱bxIp)fz~χe0`LjNSOjI% 4]F2)[-|-"*&`H Y<]sd"|Zk75\_N:>.D?b˨:DF?ga-;bxL8T$K_wUF0Tx 0 #uPwһߑI 'W}iskyפ~+oXIt}0%ɚ !tQmSGh Q21aPE5ԺW U'7)|с带lDO~U ~1w2urafm0`И!0HEaZOu3?xWzq9|^o$Άrt"fBڙ%p\V,&nqǵ 9 'J:DbZVYTC\oWxrnjpCYܓ 5zF*5sK+zlK.ASI&SQ=JyBo8Bp69y#ޡm3(@8j#a5ˋ`VϷ)$ 御VC/;ɯ\`6l1:J8 bsDe tl`nnm2uB5+C} zl^t?\]d_JQ_VUK:1.'!ͳ4 gNC}Ё 7䄻"g5gq HdH6cGAIa!`dUI݌[3>JZL-ʵSv|ÛNFy}+b%e멚?(wG^3ԁעϏUs52{փ%,VɄC[ Wkr&K4db|igbԇr^T1WBD*qmI@CUK'5˯#0/$6O7C?G)6O,t}9W|̒S-WPc;vtYm>LSy2{ 3a'ζ¹8.5k^#y z-" [,#hN+<+(i*4 ,ܕ^40TtxzOӦTȒ%|B>?.Тߑ264)#"Ӷ [>x$jK]ipx!LcC j%] ^t!e.P!=H2eS{m퐛KXϛ\k!b!q)E@); t@cLaaXDwYUԾrlNWO nb@>P%k&PG(1LuXl ٷ W(r G"bZ5"=g^uVu/toq6Y0jT)|_ʧٓ¸%-co|#/4!)%LxNTT6J 51q5'Ibt9Hx}O\ϊ ' M2R7ls#.O̿č=}ƓEj5i&EECU6S+@( 9>FFU_)[@ApEjB̍Vj5bSѬoӅt>Y&"ػ0f~;<ũ K\yjd"NJiba,myG{>rY,`^j79oJĕgx-+Bs8wk&7л~R >([>{5@¹k] މyCܮlld|ԋ&C`Ov ƠK?sƆ.& Ʃ5 Z_@0Yg @Zs[] vr+TԤd/1yR;ܼQKl,N+i0ms,+\:yd?%@ ~+<&`rݲ"Q?h)7E:ĕ^-2"V`Jq&'ȧTd|˩hFn:^.VXNs=ּ@S%iDom0 .@coLѷ4^0D3 PLZeTMJ_^_/  Z鮕%q#a)#kk@Lq8Y|wGK;O&}%#3)E{"Z 'gsEnnԡŐwu)o۝,O_0n&`źLn6R/KOw"$Gdߍ}|5f2ɉ(ݤ TEyd:13hl,q?sTpyrB* ౛ZdZyjV=Kitvyv,e7Q ]'*QW'c3D%f}ѢbW)Գnn.<"ΆE"tqY;{ݲL1SME l&"R *ҨWiY]bXf톟pt(|eY,b?w~F´Z%ZUvk'z@-\qv>nѯaCFm>HחH晓|dtTd/ ++jW/|)XQik9(_O3aDʙSC m߁7v`{wݿQw(Z u!ԩgLzBxh{M4XwC" +(du85xzj|IQ{cͤ be௑+`HEۧgpj^ݪ44˂lF *%K<"6lj eCe S:@l`t,Ae]^UWIDhH>f$(OIk^39Ys̺l`0b!(Go.Uxͥ ;Ρ"C((i3@1XM(zzIU*P'|0ZB:2$9EAmqTt+'?1QC/dJY_ʟf{pg6!!Ü#QJk&_gX 'Z.D8m 5<4uk,4*( i [Kynw\5)'ҽ $3xK`Pq'q"ѲM>#~2ׅ\@8o橮˳EUO`76>]pX۪(mSvPȃ):z.ݔ'9uj4ojTµd-0;$fӭKM>siO$95)+g&{2s)J J(-,9 kjS%gD 2oX]vuqe=MeEp8[<V{iNG/]krBNVplJ88BSS7Zߧ^ [e >Da5L"=LGYsþ#stEMjNưd8Cdg2%aamWPRX'}PdSkcGȻDŽm& R+q{_wWܤ:Tc5/{-XTon/M nJO~,m<)1JPDzf_4b.7!U1[2,rn. 3dJAuI&Sf(Iw<q1YᮟJ|!ڼ"9;`Ll ,i ܮLM:<0c>Kه7qO/&DrұMP7Mh ,iRIV#60H^DLrbƩF#Sg4 &S#%Ea_P:3䓔Ԁx b)ۙk/p\޾ap0(VLW pzcƻ@6GB&+=ța^N'4*1('E()(fZބO=z )4 k{74EdAP ="eH!4ÞͳG=Pm 2ZCJöS>Z{F^rHU`9e2]Rn&c3b5<7UmgwnsɝHO)yV{='į\-x5뙦ך Tj9߭jejdT3SP8RoG<쮍b͂~{"A;p]ON;sSB/7ϊq!s/EJ{YJ@^[|8; &߀@ x|-6TպTSXI+ó^`DyFӪӝnO"kf;'#!EwSJ&iQPIT\fv u-[GE~Lf#Lt"c>7_d3a: c$KAa/%]8ޘL z _.Y;BdddcX؀0@9C&fF:> d3Th46@]M\;lDG`EhH)x1(do4HߠGr-]T޷6I0@:[hSRd`@$qoC\y#EYո$hm{li]K oO'Z\Z=b" ]&W˗2-&՟:a&M\SOVB:, muzi ig|{DI|ѻ&A'noMX=Zh8 m\I K/$YO<ڄ`wܺ*1J+ iiGgWo+mz4TZ{TG $4;J(1r#`S3$֮#BHOͺ hwK^y!YLGLȌU"-A['=XJagBl{I¢"l45A Nɽ-猇.sAUyB#yߞ;nX)O7DsIq`/11>Pȹ ÿ+"FT[)m^=0JU$A &ly茜E0צ[?EQ$6Vj-M=ۿ]=so0/Uވ;׾>$dkpK>2Ϟ.@/f凤7YurC6¹h2/PF)N^5}dЎCxQ(5n9v2H'YyZ2h9-y:s / 0ۮw9):v0Ӂ'F'BRasY~lzlca~4h(%k;9n R`3_!宄/B'=vhWxk/_1u*JI)`?o 1̮D,bMT Þ"vRdX :=W~BnH*GC' аY9,KGD5\٫q/BHJRF$AcgȐ @BTWLzx߃];b7Z:.J:#ĊԈYB>$0{Bҵɵ-'׼# XI ij!T{y1zUxuvq/b1ìzq\Fx,&O3_#=<2Tձ8NۯȏIMSz1Y<χnF`O9[[EFf6~A&w؍#|듐 P)W[ň){X}E 4S/59Xg5գ>|plp{7F8dB>qۥ~rlŷ`@=/v,c2 dk|HxU3iVq`ܒ2C>J9!@8XI!ʩPC,a}Q|#6z2 F`~m-ٖiWZ#֦vϔ 1_D4it2<(܋'v>1E8a1,O L0E2.cJ5T摜% P_pq3vj L:7p*љƒM£hBlq2Ɵniw 3hږuN\]""o22zz56Naj5d8T[(QlZh76361#G('y?y1LsRKfF; ,u/oI$Qbֱux[5jˮ爠%kz=#2\džz=R¡_1D#9Qg ipʉ5P]W%Xz\/rPabaL#A!e~RzZt8Dǻ)]g;ZبIALZ;Lz/sX MZg1 ]3wOcgR)ۖU7 zfRw}O P:T/fP@vТmKY p4,@$qo5 6h<+Cw.`Q/\ԞdO֟G<ϓ$㥬Q_/S,>Z?6b/׷.:Ax}ސ}H22v{5_wAZKm)6DTg$J1(ҺF:653-n6Ԯ K 6jBv8و =O "f(CbO&{2{gV bfgh2a6|srq?e){7 X+ނ#nWJSm2jme6#in%bj 6L?lѷkGᭇk1W>ݭ#SsL{T}]) rw2>7}ḣ$=nu@k33* TWC,vd2 fw!XCl{?d1[u+Ome2ijJw3[c#ݏlu 1CPq4[0v.rI; s h9yZт䟹2 W)$__J2(  b˙t pjpq_]pwKK-m~3/+e Zl4,$7.+^y^U%ʻoT*U  QȎc&cdY&ͳVcnq$D d4ۿZ4ʴf%9WQJX_-[Dp !lzcaw Z`@24>|ֶd!]3.ce~H⌃z4`fuGkeHEΧ,D8ŹBsjO+O` f b%0pΣc _؞+$|vgP S=.,O3Fj`Gdܚ꒍3d->[GuN:%ҙH`50[}u?E^32XtC=}b#ipC7AKK" |'/Dprus yzmnvwt1(*,$_BPSʾ-׉?1㡥wʶ(R},Z |Üe}=ATZNQj5 EmfjGL5]1U ɛ3)!'D}>x1b(ҫA/_vHѥ!1@l6CqAV=ٞPevWNcyԹ𦺣?{t4_V|mFH?X2Fݬ'R,\ q/ \lɋ~àU^d7ۻ%e81H55E&AwR^.g. YÚ/У z-~##z;k0 Ak؅6)/z!A%ɝ2Бх_*O `SYh0UuM@ꗋjSM( "!&_ xRH.A3OsW'D~v`4A|;c~"b;7?zpQ u m$oz:FkP$O4 p4N^! Ҵj*/uo$oE u1JVuUt  Q.HɢmVb=vlU3|kd!\D/ 4A7clӯIOR"!3iSST(JzIPT!^ԳlO\KvBGeAɻ-H\P,LW-2ߩ/+@MNҽ2'pi#Ȇ)J֡? +@ >B/1n)SWɕ(&wbaBWj[bܓgm,$2x5cG%2MA6EuUCk#򔇺LL+GT]jlAC.%NI^{lW Xg -rXGQ-VNK ` #P'{25nųd %DQ(BA\".gAM.HٮJ,aU'3 C?"vW@,{:3Tya݀fs>Ĩ ٷ'n m !+\z ;G\.G#G,u9= ll;+9<}Cw^ؗ(ɕz0[nLk{_iq)Rz85nDD[;b@2]JVAc0 @x0qRy|I#e⟘Ԍk<0}]L=2Lpe"spQ3 nu{UFο2׽$ML^NIlf{EHr`ހɝnhOMȒx'lPwԢJMJ+@&n6~xSqJBrJ/?6O7;S+ea팂P2ŅxÓyOB"ŧ)Hnq ]읅I{J6,oWpL+7{0PnL5-"]pBWl㻄%l^D]Љ iŕGi0){rL\4!RO[ .H}&gSo.މ8%d5B3/XMBS ^xz;b'^A#)E9lXr HVLـSIa<_ϊC mRzb[ɹǷ.ZfEWͦL$nn>:Es(n9E*=&| _whD5"8fg?&8ߏpմ2G'p4Gpc+~1Ԝ3`M,pVu)iTy< E5dJf#rgg^-{00( ub|!>;*L2II9v蛺 d&Ū\cSIҌLnUߤ>zU@uwU%8l SBgJpZّ]˹gVg-fUG9vAgW:3F 7 -AI&S8@|J<0o34. 6Ć;A֡-Bg&+g R~F+>MWG4Y/dy)aRI;}A}28ˡCf*0wQ1΋wT`Vyzu?z9|&tvH~()hIPm{t0-u7ǓOIZW>ʫd|F٠qeC8 9$UmDWq^S@ʛ屚AzVLOƹ4|$/=>yr?Z)&?D𕣾 =KͻqRD%*=;F^`?PhLhX7︎< t]:VKޖb*o-o BDO0{}b6a+c7-WFjP軴GF~&d3R2&{S5cq@P]s# wqxVTՓWeYHF)ԜvA3mm !@.:&P{R eJī[t=E0\Yϟ#`vzNIߎ[Yybw= adz8#C{&Cn}j c,։TL":`N,`*+~,c.MxnDfQg $SճwAJ.&"E4lk嫣1FL.׀? {|lنfN6|C0$U>oF{/llH=erdKO[B۳H#> 'bHD v -mB60Sh!qՈߖp%n #%'Hvyi3{ vz̢ClBzR')_E,24.#(FH|aN&>h'z|Ώ^jg?P,?yZZ!|! V˃_iMˊ! %?WӠ4x F mq9D;˞O XRN%$A"|fb̬5bJQKy>$"۽G^!,%#1 TX"HzN<1 1(Sb"H&FITD0n*9fwk-Y#Cf̂vL5\p/E4~oJK3pK@lxd^ծWeT…2j˪fxL>=Oo-G-, n>=Sd;p.ODb9inSi2Ҍ.օAûnPk|C e)P:8FO5"זq"ZG\cW(Ź{"~odr7 <¬*c?hPF~% )+..#r8n.KK/iA&ΖWPc]Jqgwz'5غ #8}!UuMh\/]m}sþ년 7 ­l߬5r1&'-fFx#0I_ $ C dTvS,6r bГnYGGmz(3ԀPu3G ]{9k&a g',"#9P 2ĂT` 4kxD@+}og4G4H ܦ=8q)Zcr>r?RXҰSřʘ>)hc2R2 W"0LY2eq>sm]VkU򱰌Q$y^1E,`Y>?&sbݵzbȾpʗ40K;H@}R< !({Kܗ1Q۪j!!AFدho%%r|8޵p<0v;jԂN{ ?߯M-SE7kVL(= NZȩt m [Q*q;~;Ogf$^Dk:y!9ݱ"_AI!6Da;Gq(8fk[ 0yO\zƚÚ;p6:HyTQ.sr±4i+d< zĭ,=_ 03Rvi%~]ڴ~*X1׺4Wc⪂OpwlH ZiJt \ٖ1 ZݹVē{WWG[s'D 7M1X'BAK(Btg.|3$bQV/H4ݷ7Lr!!V. q=|2c5m,^e{m!i.S}/m8M'S$ )m0u V$+bϗh@_ChX{R=0}n'x[97#6\j(bublPYMe9*o>XҌnx yӅC b +:[)BѕuRaɷq_ [>?}ޞd_(wocuw㺚Q;ˢG?}FR 1B_>OP8j+&>;ѳ}575x>~ySWuɊƪ܃HO"kxr#A.׷-opFLh5:j}zv@R_og7,.>]< ԃCfBD$eU. i4^y hÇ+WjԘȢk2v< k?T!~H1hh*g<4\׏\ , 1qXwq(d wHC0woZFǢqG9 keumUb4Rj}O1YrI?~"yΑ(YrkQ-mJqJ"Ci!Lld~PT۬RkѳaRm)@D8́ Of%; Y|,c wM>NGK`H>2T0;ӹf ж"h>.h& YwAI/'S4q9ڄZj̢"B vI/D.Mת5SjdxNK~R\/W$kd4/?哼.8VϏ?aқ'!0~Qk[ٲZ,FuS)~Fdoɕx6`"$_d,i6`^.Fb=RFX*D杰زދu'&Wi$35ҠjgQ6&5һv8A,wr\2B($s:$@g&ز096'J݋R1^_We|MAKNMdER l5Tfz :+sLX4L>*%n\ FfJ.zFo ( ڀX6ӳL4mt+ˮ@<1=kww7N 0?OJ`c/8ϖ ^s ^B7#q߳TzI-JH<,3l/ ؛Xfd1³&ݸqMUa.m7Qwy䭫 ?(“r~7ۡCmr GM7LB CLĄD!iUGh`@< +;)cR?7 LlCX]P~jojJUƒ*SU*\^pL^<~1egDZnac M}p g|FI8~.˹^מMn8Q0stn. ~:j!g$~)I`yQ߃`Rmr XDTG e<&4V답!Œ'L70kf 5-ޝ*˱ Uմ"3h;mALC9f($;΋'J7{+lǕVߎl/TKBʫI)WT{@Ụ U6B ~8L_y7#w Hӕ]z,|;ݎc0њgfV)(Vo1-75yD}Bwp = A2"). m6zۺ6*r:WvZ S³"U 6[|t`Wwq1WuR+M$䛨QAXɷFIWNt9bܺej a$md|2ns\#@d-GL38߂$M},S.5OwyGk瘳LtFd (JcW̍ƲNpKH|[/rW]GUcqqᕪp t6a.@rQ1-rH'17ɯZzΉ{ MSF>Z{' C%BmPQ0RE Q܍6m~-vܒOgx*,iG@d5zC_NV`GZv;JhM8Bk5pzG+ ;Ծ݀ U` _N<{+t({QɪԼA=m׉Lr!˪h3DK4P@TX Z˚tùqkҟfX-TȒձ?:VN[x*@+K@5 ۫=7B]?uT1yfQm\e}Uo`RahR\,ieΨѓxe1uTkL<1: lOUӕ^M3(JzU?*6W4bf-mk, o+n^%vLB쏘l`^5gl~k&óekr:*E(ih"+Y,LmRgg/&|9©ceT uM+%[*Jtŏl>Nd~JP9%\_N TLzբBW^taw 1*'v,s ذ*Zn~Ї=D@oB/b{faၥu$AYOLx0i>ЇP#=j"!ǹSRDz"ksč_Lc2wW]V`QJaqn `jGm4mDrL(C̄kǕ~Jȋ9-LQ3ÍܦPUخ IPvh & 6$4|[űON̍bÝ;vMAt6OႨ:GܪfA=Mu G*EemBΖ@݃G-=cs`\sV/ա:Bː 0r3ʱX m@ o }^lNNR&C\gbg~)Wu4>J{rzl\@o:qCuCtn7 /^F Kg>Y)@3)KN{OW)buhzΙ@nH,yESgBT/ dnnmgm (V4U Ј[()74rT` .Thabzܯ2~X-jo*#H{~I\T@\抏zcv9o]{޾zz 1ۄ j~67zFʽdaLj;9S% ]MUdIiiqlv_k drv)r\͑sKȢ 6n*^Ҹ碁Ul|jKʁ l㬨 (ň]AV`bwFz@ku|ڕn^Pjk2?㑡'͸f]O3vU *l$P+{υ4i0oi+DqQ Rh+* x,p_ ՇxrzPog{)7\~O_ kv4߱H[kbWj܉~)ޔH>+azYewW&R"xm7}087ס𲗦$\q<ǿUb7X܎ңENxGY"DSN~kӷ&z.ω 4W;mx-awjHD֓XQ 3EߔT*A>0'*xsPtR`ƾ(fijH>`i3vǨ:]FA_)M:j73 gc-~EPR2, +$Uڡ?oлqU̖yZ̩7P/?} wP]lH@HGOBIEF0W𙣕VUi5< BI; `]Dnxބr;c%`ٗr25bXDW\yǩpi_\0Wi!ﳣ@[Zm{*3ܡ2"hhQg MI#.Oh#Y=܂$3u!a'ÖDF:-&e/OjY~Jdu8Qc)MҜ]|@h!&vldXaU&z1f*)}E6".5wG⃏9F'HM ԯ(}WnGR;&Wd?75euIS4s񜑨 uXK%Q(w( p"'p')`@:t`lhFK[Il0Z{'s+lEv #ɲͬG6Լ9:D$n.& r7HcPd =394^SӿI;Imh ɀSw8 w|il&&vu;ӱLvPaϼ~"׶z93-`/W&Ve_z W΅3M ) nDsmqx+Tߺ9b6uq?Q?)΋4=5r  ~]T7=x+W-%5AP0)ʤP(Nj S3O{a4Fo#"% _o4W Jǜ2e*)͕+x#JWgrHͥR@Dr~fCrUWԋEb7()[a!3Pȼ_{D4Q Lv/eNƬR=g lAW]\YgN]ꬣP`l qٍžQ (w0`{JTfڸP(Ge!LD YW6٤r#ԤgA.Rˁ0D"M`TOs٨>QSS}Q-bfjԒBtU[aQg'g䡣WL&m+C#0JE*&~ix KkFv@ =`mMTY9g*G;<(?{WN#|+@L]}Rtc" * æ VDXlk=f8f.D7Iwm3nrjh1,#/n\F&viDN7 ;x " Pwe83cį4Qx<ÐBaxs>Bq-̀ fn(2oǿ1XV}OLNrܮ(cRY#&vP+|E=P$ZY.NL!geeͯXA8)7D G47~qc^"{,daR@ Ma E X_3a;y4F"f_b2%ǃDy≠z` kac\9&fVDzwSA}#~>hi"{IHRws!h#B})^Ss@TI\M!0ml!O=|= x\ xQsh\Ō/!" Fbv0tq/f+FYnjUزYˮbmمPAĮ"(q;i[]pUyryVch[v-[d>j&DZX6aH4(~wVU#1TOpEK#|NDχ3W~pUѿUP'A-$.> IEf5BzOBdLY0^ŵ͒eqMi^~"1_D۬Q" VR!w=xp| '+zʂ=,<'K*L!"~) ?J/@&mSKXY<8N]I&]ɡC_Q^RU;bS61EV&^"k-Wkp笟e*ǡD=5'ePDhupoQ +/oLcItdydwgV?;e"oLT\^WS} >&ӵгhԐNo* GCH_m߽D~*#>.J}ȩv4?zXR٪2o/Z*%. (TD}l\chN潜`"1^7;`$ @tK}raR~.ɦ0h 6E@Ԏ65[A)'r*Ҩi sKoVAԋM|j[ǩ@X_l^˪w"i90`g9*KkQqo[5$qqH P#@0cxD`X$UK+}.#FHY?3vm}XLM5̴4'ػ'SgBW'<RnwvA5NH>`aaMAY*d6TdZsS|9)qxFgٯP[nXp~XC5JAx,nlEnMNw^c󽌿oۂD{G&X'`\%َ1f\P!юYCzߟ%R6{l?ڝH.>׉@4;$SpFt1P'81NKO:,W0j_ ÞN/2Ts_k6o *<˸w7YǻNe@XF#wW7P݉O~LCf"1L*D+ j&v'~v죋h 6}JZn,YU>3ء:.%L0A].rVEhe adu6㿝%=&F1 z9:t摘}9=Xk>z#>FmnB^!CHǓ!+jP1ato2IW2̹qL}ui╳`Lik}kwh;C'?<ނǹT aJ*??v'ؿ`btKwAQ.c@摇&!2{H}=R)!w"}xU9of1h1HQiE4Yx!JP駏ՉX>]kA'8Hqp& Wq>E} ?N󲗁M8OLJP5|@S½X9+ dX5c. jK^ƵC Oyy1Ltv%B+ºR9T1{.FP.f=YM7Y2?5#s:` 'wzUtB֗(im䪁zxTZҶƐ7Tj:m$b4-|6xFݶKͮ_oT H,*~=3[<=x=n`i""9%rh>i6gfpnCjmzH=Njto!;`vE\, mԆGV%+#v^.鑢M:w)[Aѵ~\0bUD'w9F\-TĭEǤ< 2:{--v`ݦ4e.ŵP,nToy;9ep|#n8P-c,i֌rV|gd03}闝k(^ vWñ Hΰ<-sFe; hyH80H5d}.m Aw3i߼9rۢע5!a`s,3OȗVHd-E&"PŽ! nzE G~u]~R.pU_ZBI0kGjTQfo\2Qco0{"gP#C T ׁ;~F$ߧlo$A%(5:됵es梾&v0P%rZs8&&C˩:mSxP|NABYwQP);M, F ߭jHэwrrN\ >sZTIGHTL59D}OPOu8*>dj⴮fm*ϯO"%{r}VEZ1dCr2y7!v./\lu:֞v0Vae<< X!qMHLu0D ZE.P[ !#BesiA!gq@6䝣 Ȼ Bc4o}{&MOmz2ZQ TøCAΖB&>EBnp㏵@>9!LM4aO-ƌڝP7L,a2I1R3""4l:AYC ʼA]+Z@+Yv8#?~jUgƦ_UA"榚;JTӰ1Ǵӣ/NӍ;1猆v=^j7^0_d7lTtϢ Gt?RqX7S,_{Ҹ mz+XBF4iG_*+>#JiRCiC~B1| 1P4)-Æ1>9* r<{ -cJ"ϙLæ/0g nۺ3,$:ݲ|Bi~*»pQgJBd&3aڃJ)2 hqedwk`aMYl4+mF:``OJ8D^ݹolwI'n3MB%pתqڝSF6/;95aH皑}1O_54GPVhėM]քDy:9=U=M}d䳕x_8NoLЋ0V[\  Yn~~*orӶ-OWA>.? pU8Lls9RQ' akиn7zeײ~D J; (S*5M&ȍNT w .ռͰ\:#r; mEɍ{A^` 3,Fr -y‰zz,W 2̲UśnMoOZ0Gg"ڧFN&(VE^_)1nP`q ">\QK.s<"R \LYp,LR>)x;1\$s4FǀJrB] c[8DH[IiI`lXGjfY >nhaU!eDvb4eOԕt]uLR`:Dp"'7oKTT()ts_Yɍq )8ؼ %XQ?>! `(WxшZ~pאM-Q r@zL"v{0T5 Yb'uJ\vNf&׍E9 (^:)'8ٍSRG8\FlA @5 f$^yi,æ̶XD7xBeu韩Q8wat}I NfGhZX/KŜlp흥kwpEQ&Ud ᝛;+ɬmX^"-'iŤv'A8eڝEywکq-Yީ:|J}Eɠ\ank}s}[Y@zM6k1#lDzIX5-@XTbrbT{YtcObjoי)vRĩF _cC6W1'ZHS~VKp/>Ĩ- =GpY|jž,_JyE'tڪֶxQ|h;)>czgyX7Gm^_DC)]}.㎪0786iroJb8 RЇvҝ-}}}X;OX_Gq  '7ŗGF,)zDζ,(j)ie>"gx@iYCe"FJfV&2oRL46͍%3ȿ yTxʚ>@h0+|L}|tw~Ԟ><5 wΔxp*"(KhɂO{B3\r-MCJ[ǘ?TbvI6i=~ղF\DH0ϫnD qS7^ŅuArnl>aIͱay"raBU0N]4Jv[J蚌+u\- e[}a咣ݛi-}k}ԥfW*e u (ߐo&GޝZCE.D˚2o,(FX83*~#JX66+uxU[۰Cx(:elнHjbY c| еj (0-۱CLؘ}T8,Ϋ7c`Υcŭ+؂>\i}D~Inp!Ҭ_H۔(0ԡMja`㙅W\^`hmz1b685P2~8_TE-OfM.Tl{**&yA.3+DX[U؎CZ껱EK ]7 s`avD j$m5DUAGRQX>Vf J{Nj@Ey,5eהeaLaosá ! zc(qi)I0mPy%H,?R}ZںL\No5e75&t$)m60֕N;C\O*"OׯH 5PmO`Րz8A6U){5J$1b**ioG#h_ca} .b0d Z)9o_gہ>Buˊ?}l1:_T|Ve sF2،AB8:C݁Vb~˩5dBʲn՟nE&ݣX=잍o9j %Ʃ◧"~/pQ0d N+uz]"^HX{fŚHFR[< DߜC:n[&8ms?^lno/]ը@u}]Y\e}A[ "kaCU` aoZ:V~ՋA/Kzm~"$ ~4!s| j-[Y +N z\EHNYDP| ف5 \N뾓;ҷUiVV b7DyTO9Inoon:i&ggGgj&q&;@V,z5v8zǻup\z.ǁ0խOI/rG1ws%bFmFƑ.¹uU K}̽áBQE{QXR{[{Ywq`X[; +ԟ5,*EavTJMMf h͊oD+I"0k,7ΐWDaK +Ą ?Oک2t]}ŀ+e^!wʡl\Y\1tȠE[Pkṅe5鮕8hI I6(UA9 Q}u+٨F(d )_x% RX괋;>&0<Y'GSʖI+pF N:H$Hq۰r=3]q3" u!bѳ(] 6Bu$$40c5eGEƆd/AMjGk߫\ָL%tǫ75,|>kbRѕhNn몤hyj2_X0QB5(]@ުX& K=F&"1 hZ%wDgsl7]hV,SupGG>1<)eRmdr|@D9ƓE&ȗ.DvOF¢\eUI͍i^,:)XIz4y}!iG%hP,zI g ی7Q}+˰WLM%tD KлiğV#e] cv̫>;u*pYe[]aQ'\!e@,[v$#t[SpQ8UmY25]6Zm<( 5 "#׃rHWw! O.ZzvbO *QA4kE\ ӪnΔ;[U֎x8w]qej 47 Ok%1 ݜmjb-ѺUp5Q-+揉r/EoS_*EܭJ2EuC)buπD>ެ#zT,N4ivUC(S$g {TsSq a4Tdo "}}=vN|^FD Н)nWȋɦi/>6^X ,I›ָ(GӮ&)7_h@! 18d, ZhYªySqX0> AB\3a0Hz7ZLKRyו*9w uO3hx[o<:|M0ȲFŁ>Rl&?F1lm&0%1ʆn:VO;a0|,< I+ښo@fk'R~w|?ͨ8wYq(HB h'ݛZLv &9ȃCet]QL=r.67Pěj_kF/63/OCՂhaЏvB mr"v脾gXxr_sjŸU2ݻAF{w ΖcMu?vMTy~%\dJ#"wvd^=@zmaaぺvPh[wUwaxQvRvx3om >,/Lu;wJ<_,3w@{UUo9KC? !JP 5k+ +o`QGZlfu# FuA}_ KN?mNm EnңiFJS?v'?ż9Š? EQy.S:z5wU6E{H8,ZnWHhXWY.WRxq'yɲcbjY7˟CpMaD>!ïu;JIdNYJ|Mlxc@rMB| 0v%B ,AA!|]V_OTHzK gÈ/E1&F[,^esקڂ嫙x--B2)id 2,V+kRppN'Z3w<') -2-gUvvB#]Z(Nah dHMA*ͦoڔK>SfZ|'j]rhmԤo"$ 1S0M&u[5ҮhܛV\dF\Ap.m XE&Ea#f]DIo溮]22w:F]< \liŦ?B>8IO-85gz&m/S}5 ུ!GV¨A5w 4 HT疮a`ՒJi&љ&yH9M(9@PuއpZ^|iYk<JMSE..XESP֒ăvFoKQr%VOZJNUUk,,,C-&wExP\Ϧ/(IէdwE]w):Rj tEf-s&zVKunܱ5!A:(=ZNT!A.digF~4(_FJoSH!i6P6*9ʡ*Eeֻ k6YbʑBF ո02"TcGĚoKKf~V9zUT$HlLe >A>1P^[uB|@W/$ t}ogswcar$~R%.JML8BR5B-gg~?_`+ػ\ȣU8*RAĴd l0ׂZbb.|se8K583븫+2? ϰ{͎ݩ#JylHڗ1<˯e4({{>mO͡[%'?Kr?HM^8n@hw~$h|x3 ltZNJX&cQ&F2mk7CB%IjzA8(9'0(&z!kk¶0݈LKePT9PY#,5Y ={Y'w|UeV bob H? W_vkkB*01;Znnpե~,:R 8-gK7M ;#kURSHWa ƭU1%zj>"OFfcޡ)txP\a{QZ4RuDc{>?e AcI&S ;r4@92q.tFX@[~94KC<䳅ȲS gXԓmN_C*UikuV}M<ȁo~ ']LzφUa6~g(vt/ddj6k8&[T.JMPkWib kv3(ƒEo+l̸Tԋ×摭u1ܬ |V|n8m'4lY,(Den^鑭piH\::,-qsC|@Bд%R`]cr֏}!YZB[x246j~vt"GRb߂e .ļlp֦5H]H~w !5ch"YUhv#c6xD ܍B(_|p7hϖSrq:̪hӳDfy틣 Û|Wy%3d]ߙj DL̒޾vivBm =#_z8=9|ES% f =SU (C%q ^6B:O68fjh9S>EUꑳ~#dعjN 1\< fVoX&I/]ՖH1\[TW*ua#۽OzɲlBy<]I1o BR'{ٍ&)OٻU<1>Jxu<@%/KTXc.︪8g…'`1i<JC o}ۗRoJѪmh_yøcV8<]Ki+`VTq*/.enjZV5OþUd+*o$[:PpRy)i$: a\0mPr-^laL*-70,6PJ &U%(u¿Gǻa:wp* {@[.uwGLdT^t< %֩jגm)k \_&6ZN#RI"ȶ"v׌Oku7Z3F_GBL\D)aR+C*Aoh]ȧј-}I` i]Y՟#-C]: ձ緗\)^2yDycǀүd8w@]1a5'"(ba83y t}ĴEHC+? {,:z,&Bk.#rvdeb}Ty srO@e- V;Ne^m%<ӵ}‹賁hJnlAQ[1E"֫T_2^.G-JX^ui6[3r p~;5D@Fzkd \7&DNaΣ٨ .zjzp#̽P`j[L>ne:]7iSk+m@8-Ķs}΃F]>Q䕒_c%*5h62X)dBUI LfqNruDӒC>.Ud[.Ip4{:c{Yi㈦ˌшBJ}h6l;.8'G1~t?IW tX~\xR `&jU+xc} ݧ{E] >trbT9Z6+3]MpGoNA+ǯ '&TQRdFܫpYcBc'6jer;gC@ooٮ>z@W)] i@|cD78z_&ߜe0<7qVYl!6JHz*PeIx4uf!z!* EV^8RoGm# VP9$!97bC3.IpWZ;0=J |o$9*ƫVѓi1?.,u2K<[eVgJz -_"[rߎD6 z@,xݏRnYGdEޒ=/@_u =]T&Ȟ#$7#=3S2a( s>qӅIIz 4FEiO:R1gЉxMX]F7-k_%dyU.E6ۛb8˽>̋&U>SH@yXڔc\7JߎnHzĥ.5dn!Szg"0P ϽYKIG,S)G$Դ񰙇O1)t8{^+b!'T_vt*"eu^\Ʈ oyn!lMV"Ҁokh2Lg d?+iS"ڱugG2Sn06ّ"`b}_d硌(xez?R `U3//E ͗A7Çij zD4_TFL ltvw{ULd<窬1@ `64l716L?iX'9/ հ3/di1rD0Cp S &/̌V?eWƓ59!DJ"M+|W=o\랻/%YUa3 y8{6d%$G)¤̚ i/, )<>5'%hvkWápccB:ZEWh&o` AI&S-ba }9Ġ0r*8&sfZrR0k9g4v‡}p %RWsm,UBL^IiSQJ .i$a- uw"["Xijes(-Q}v,1E;rxEXP!%u:]~ɲ:XtݯrcI|V PrgmN'@ HY &&'o /6hx vUgdև5,&A*"PKV5 x#"cRAXWCd ~KHUYXR.hNe`ݝV]|O6GLˍGq<}#qXA6r-ѠT^׶c%UBUvͫUqI%*ll*q{cW-0_z&y0A K@O4Z,,Bh pxy^cj֓eii=AA煑Sx1kmkS  k#K(?>pX~>Lp 2W$J@x~vFIAkwP& gN-HGe v [WnGVItɂ2G?#ı@gzͥ㵶;t⭽i?f>}PibaD_ ?M/7;G)p.%TT$_ Ug7!W1E=^ ]iIVF>@YБ:)$jmUV!dI2IiG)!sƕ}K0iL8- ,MuL:WHChtW#(MVrc0d X7A_^dr]x&O7AVI#SSұHҏY}ˊO<!y2\d~PX /xL,(+BDlT.hfV=-f]oBPD*YB%6/D*O969;CKPIy#>HA Wf cdcyj=3kB˖M4qcrMHdRJ뿢A`ܜO&Kl\Z$,atPo P5E3M\ ?TkٕiDA*ǡEi! GPfGx\;Q!BMAYUR ] Kl>|ֹto+lc ɨku'0ĵY`ǩUu6 fYFg T:b(to>yA!QsͲ_=^ׯ_xH=sAkEO`z| ǘ+Y5M]ojb/<PRK/WD`%%]Pw 8S)u~^pɮb),@)݉ m ^{iR^i\iƗۮ ^"Mj{k8ei>.rf"_~6D }+9B̓Cˀo^UpKbP s=+~7󦾧|d2 tw}<=_V9^9%kVR/7-uAE;p.=$#|M5YCSg䳨||/ް1q6VVAEHO)܈qZ/WolM$3; I4,10|@ȊJЬ>ASu71YYi wKzKA9‘MŔTLԓ "6LSCE bKڳ9)h3ZEZjn,[WԞ7!E)5H K[7p\Y%J)1!#_q4>f66>`7_7l{Dۭ~%}G8o2:c{ yg6{{O}ۢ5c˚!un&23X2v]tkA E~Gè~m%Rv`DM]ŜTvDxAI&S"yxh2ߐb[dMq'3(ELW˨~:sLU_bjU^( ie4=:(çH;RW B֋gaD#*^4anl f(՘gI GW)3,;hKEA<_ ;rێ-9$n~cd"as OdfHLJ> C&/fH @Uڻ61TJz_4/Kd1aJƍG_)XJ^/r^@Nee4OWg?_9Y!o9rcHZdd>>3 P^giĮX/Oт܃o; ?)o`n\!YTYNjhL;]|:^T6GtݿCt /b{8r_ZM5+}'trd`N:ZT"^F~JpqLZZ^P׼= z^YK:XR5Z>5U}(:% *9psй]APfQQbK,PZ@- Ыw laXm[s.y,=~AʑqfK۠ lIlsxHJ%}SfRxCN t}֠Zĵľ<҆&&!n]$ YVh' ݘM7"qҕz60nːׄwӘvR51Dk]Ϫwȓ|Jwvͤv=LnKDbp>W`;!p}+g[@+ J2,ɔowK"EslID.`;PPK);MјNdH s~VA3~@TO!myɖvHSv1iw$~?/Yyx-R1(U$Z *S TL"1/nc>68RCo 8.,X#><сAzׯqⷹwp +bhqX v"V``KvZ&L+.,Ng GϼTPpFTh}ڷ+2xmKp̩ҕW.뿌 t!C|eބU-^4>hʸle^ne[nMC  ²U* _/.ae2=%,f68( oc"3@|y#jtRO@d9"2x.VnbqT9N&>a^ ɹaMV( ꫭW!/pGP@ӭVIԯC빧.HOy8'>gDi."<}ub)Ёi`bެc\guRRB*S\%SW9yoٯ?Oi*` n Q+-N@L@ϋN@9U\XR0[}ɾBi3b;9ἀIV#w'L eŝA %LT#IMO$+*+ g yZI9H.OY£ml,,l=AHU ;Jh2 %L֒Q̷"XGɳe(]&XE?j6J%r7WJJk?9]`O E_pϵdB3W051 d=x?ֻV8tzSrNBTS%L LK̤ p ggjؐ*?5;к',q-lqU*(uo*+.wEDez?"_&_E` tUHz9:HJϦі'k2)3,AI&S"`i18[ǤEnl@ѯUDI}Ftm8j-ʝ&C$Ŵ_1N=)TȒ̐$:/pyzL,xyo9%o{zrHظ~@ (M >:J%U+i\'9y4>Uw0JgU?@ d~`NrɭOB `L#_['pRA Z} /ћcz#k^ YҼ} VJebՓ@m(4D[R? ^>.A}QjLG)#f;^B/uс>R/0P KTO')JOGcFTJݶEVl 9K%&+~b#N6Or".s;w]*p.fQHd FމYh~Q03TxL5{IE1rFnq!QkْZ/:Ya!"q˜N(HēE3=^Xibgx ;CP#мV-*bKuΑѐuPo_^teM7]((ۣqcfK7揌uI{ `+e+6#*)G:0/X?~CYNUX/4LcLKr!*'|lsc?a|,Iq`ur<,bđH<A&:p"J>|>T$7g!h".$8ֿT2@`v҂X}ЭeH{s;)[,Jrb7 Y: ࢗ>_;wB#tr/ZSs@r /6?2Kj~,B 2HNNyvԏk`{'85>gDДP liOoVSc9h-SftwWN7dTmtp6udQY|+mۗno=5)˷1͓`Dߞ?YR0`?g]ĤZi:ϼ/6OT!f+5e 'Ivq%Mv֔Ҝ ;7$%%?9ؠQT¤=.)T/ftg\ea2)D8]"0#fz_$R$y^[+&ԇnU_xF,]ʳʸ805BŹp狠71!8 xS$ 0^6%/]?mvk q)ltr4`m H;k4 VX 2:'4sgvcfG<cͤ WhMc!OAZlYG .';}M NTjpB3j.d_bb*~_<4B^F2l^ 5 㖯HBjJ&_.8JB >vߨPV@G<ϓd$7ξpBvdz3@4_E2o,`c<n((Y0¬v%3$ _2E(iA;%fmP>>+[©ǚHza=}N1dZ(u: Qh$\V>uSLj=+imn9Ԕ@>Ӱ@2drX (ȾMP4&DPo 3泱|Ǝ%sZ/bc'>uIh<.@~XuQ3cxy,QQQ.MX4x RJU]V' ' l>-6yNO S.gìwYPݷ.اyxΛ^[9~w$ NH8 }'Zx셗d^{> YЪGˎДֈpҫ`|B)<(FC25Ԩ=[ci9STc@Ma )0y9Ef|ztV2\c,#lL7‚6>l`U,TBqb gty u9yaJGe ] 1! ދg=Y"tN餻stBPFxD| <"V3[{ԽŢy- 0E%W 79kO--p ' rmz ?u_93L<1 } 4 O\>dK@o<ov>|v }1:W! `UVRwWo>l۽^9惙'O)+.];k5D\ =ԂHO9^mƤtwKɌ.,̐|UwxCR} A%GmRɉ.NRēi {,V>J+Hꭍm k˵`uJs<;FkOBHah6F5lٲUP5oRf>r {\ѩf&lpȪlY ,Y,Ob8ü5vˊo2O2Y7?N76h3θ+CQURz͐:w-CI6#WBnB/%7RLevf0#[L##L ? 6ȠpE0 UEr3i MJ-oKVXI9ޟ'{ RJUGSdJ*;UHy1{)Y$*&z%Uen'~1& !"t-J}A56#kEzG6G:TVDz}K ثSJ #%Gۮ:ݑ D.5AJKCǗz;[kzps|K[{ 鳤:TXX@A~ ;R͜(y cԚ3OcWP[/j榝sD]XGv>z12p:.2IYSJei lQWy5TJ KR^m+ol|UeZ&&\Fyd07ғJJHHJ]R!@O(Q"ni)Z_v=qoW@6uWHo09,غ-A h-{yY*1MOєBwH|wWyy85cd)z*2*< ?B*KãiM2**&|\q'冀xVMf&hhe7\6ѱrMH|2@o| DG[ƽ*g]7|?Nd6?96Z5>win490%q_dj4R.T8*B2,Q =:6eoh}MHHw5/Ih=$@sWmhiiA= :"5Y }HMWlK^'*3e]w+KMs@^j7fb)+ 3"=A՟A +ٱ+A]X&w-F|2Z,kFD[ nƵP60)PwCƭ96h R4/c6k%W!<7u]vT譏puӄ@'e,܌F7:?e ^9a2bVokС yd.VėHî#kpXJZ,y1vu/8C /\>\7lny5?ʒK& |Utdlkrd@ٶ 1ߤOwwPnIVeaL]KL-*W|)X.{s]cNRKOoWts-񟠴~[%UzоW]a+:'Vn6ѧu+{dN&Cˬ$уߍp.u`z_TebI-J:7X+dHoepXԠK_HT]Y2n>l~~T"תŌa+ʑ2uLDaݍs^k(د8*31 gQµid=qpID𚝉9Djwdtqu=m%>>\W- '6жreQ!4}/$"讱u9;y /%'FF"m$l&F;Te|^I¸(F |% { ಓsBms:Ϧ#V& K;eULfN}P oVO]?ODaކRfOӝ;~em0- [7lJ_y5cV;LO&hXvUԗR؈~MC]K+@9g7䒂 AZ_slT(Gg'r,O*LU7#:ChaWYU.XK%쥖';Lf mHuk].iA(诜nY{邮r \x3b-B}LsVh:^7*u ٺCU[9#X=yZq(HY'_ofchh,Ǭ#_"_0iT$Cn}woB1yߑzȅ% +-a '-oZRWp¤@r@mݻ+To /0re#PLTDtLąg`圈ܶ\7??L2Ƀ e-<<{U*#d'5*x\&+hr1)k53Pl'Y=eᡩG~MިTSsT~ts1at!>!of 5Wl}!.|~7qikPjhG:k_I]C؝*BCw Y`^ByHOpx]d)k@n "-u"!FFYƆdI^$b:}@@Cn2tZg褣uC`xjXeϪ[I t)_6fuQOM9v ːu(H-v?dl'WИ".i_]Z܇*VWc\t4"B2N@*id{;*Ì5FNCFbBci~ƻ`/Kv*q {PF\DkW/&E,il?_h>W<操oFA=$woi]MFxqL $iv Z9Ӗ t<!r9;ڀ}UP۹Taѯ)}AI4,̕Z,[CǗ+]C N86 &q?zRr t>r96B靠K&dc excQ8N7ZBАx3 nWk"^H{YeD[iCaK fynNMIWzY\CR`зg"$7F;U[-2)OƢ]'2~wf$qnit^qMuR\/3e;PeXuN~>鮪GcÕоmh`}Vq{_[uqД 'cV-Δ,][H#~3kTPrru;E=6F 7AZU;~SwC( yr(rGx-njc (bT!_KZG,qbV 9idu z;Ԅ8u'!}M. d@B(0D5쵛Dd3/[m q0tH.+5J/pK,`\I-S +.w2MOB0ҋ1Z8b,r5y*W*2N`Az xGJb&YC` K'ԃPɝL{(HJ=`jAUw%bãE2$A*=:XiK[rV4n=@> `d-+75SYЬ6.2JN>LP Q&4BCi95Ϊޜ\Lnb\_`TX[NVR⃐.YB7bP8/>=݇aCv4 اvi7 87uqK?o+ I%?Ɗ#TՅ,$ fEMhpgke/殺*,w/nO+2)NIsO1-+<{4VKh<;q0JrR0`MAr Vn&rTOz=ܘe`CW]">LҀe  BGm!Ass0wgvD0GCǍ]:V@~P4 A I&S8@vE`k(-3`<|#Y0 6Eĭ RDʕ=p.*nrZ~7>|;!7E" 356״N`x4=@7+G#JحR8@bQ\OjgvĥY*k9m7.|Npl  (_)u1V i%t6%/3(* A?Sh:c-t".xRpBx^ѩwvڣ{LqM4)ú{sq*}ai9h'XXrBLy#Y|+k[^S%yu|Bp,F(Gy.6|h2RAӇc zL:]K7^9zZH3VRŅpZC ۗl2)T]ȁAzrgӰX[-ZTRAc`cʢNG/y(YEmJj~l{3k`zA)$mam? hw%,ɓuvP#:J э $#r$"rm|N0omHvNFB-w J tVL n!Ί'uӝ=Mb}F47.$g@}W 2Ci]"W"PW0 DAq|Tq9tKT"RL0pr2izr~IT}Y~4$QJk ؊* ZQ4J)ylU+{OBZjڝn/`"9и@ jp{#g D >Zp-~;-]q Zuu-r0#?lFL_q:Sx82Sǵ.0Ύ]n㉤J锈k0"_sl).su!=r}\e =^4A&|RDOp, M-R=Ŭ->HnjRF >\$>zEyEU]P-ٿ°d-ԇO{ 5}lTW6F5ЦƔ'7];ʠj >+1tc)C_YS 2w\4YTXom#.Q@q큅}uUO{w}`= Zaޮl= " |ZIv'-Z>eW } +W2-YGo6$cxFšdO?PX/v)?M_Cf+$}v0>2,8 KfeM]n0QiSI{tp iYh&'qE귨o:MNds-^<,dNQ?BU<KjNsm5BWt ̞OZy🀢X6G:S֜%cOތ AaAE Tu/GѮN|BB1XF{=et.0ubr贩0L{rA^L8҉Rde0㣦.@hb硫8-][` N|N ߄S0ZP"y]2z|2&p-VCcÀ̫{2P2kzAX,uԦyIZEj"˕¡ gƂ2~ ˍN5,{}夻W~kS]+KSKSIn.‘^!҄8 C4,a kC[jFpcA03&M;h~ō ww/(M /IfldVݘ]8_i#> kvBRB˜}LB憸٣iCCF\#VתIdoA*I&SF%W3caUY1 P[(c_#YH(w*Iaʹe={BQܿl$eJp7E4Wm5f*Ehy6sK ȰWl::̭kC>̫L<:щ M` aZam8&J=D{j]Ri^; \DB6YȫW߅bQvv^t*P2>La8cO"S%o:1CG݆?buկ1&V]lvKIC޸ٓ7?CmIMve(Έ_P^Ҿv,)7/-u݀yߏ uj'OUӺ ~njkf{t-3X!!vI+ѭtUSMdZ{s yWz~9.7ig/:x|S?P)\d5 6KHGC|O9iZ_ -RedAzFV* oaB! F[p6|[b7a\i~vcp(,D^15f8k8l@#Vk$QeN"딼֎hk^`Sv/L&(|e؜YN{ŴAȵ"#Ӏ媑Dv㯨b4B H麱{iSk]1?#81l3L8dk\WZ:q@>Zhn HkW> y37N1A`V$gT;9~̖mItڎ(f3hKdtyLf#Or LѧK,P|1g_7DٺZSG~/(3ӹ/l,Fݸ2 oF:bI\ɪnlj*Ȃ3gELhAun5x2Y)+roļO,X^k7M/8XtƑzK-ePʖ&YJU _-$0۞>5](0| ߃Ma-îf%;LLl/8kYQ=y']|_3 HVR3AN J0++XV<,-YW(=p;YsDs7A5>*츢Zn6mߨGPr`cv{55  KԾW vrd B&Ӹ|ȖpeOwg\ ,d9!KB9) h/Ed fX5]q҅P >05o?X<dxE`p@PGwgL6sB4mRtMcSlLp 7,by諝`QDqQXaЏeDk0s&( Ǖ.cr/1ZK~KhߢrxAk\F! #s-+q\ x wKh^?FW/RY)i JZ p1B#~A`e8[^04ޭn 45lGȸXe$z} !-oLx?V Lvgb|X%fJnNp"3y:ځb}(8Pgz9]M]>*uւx>:w c+uM3 a0Aa}$¯De:&gGL1&]ؿT 49#C^$O5Aފ*?$u!çujmEԀY3XX1Ly6_xbȇBFF]18=$]Onw6ǓNjл\}ÄN +'r"r@?Q Բ1 W^ a)˦7%i S u?rnvr?@2l"cNlGkY4NJ(ݖB= M5cīSKKey?`ZL ٦=S 4*Fiޟ yY ]R;9oT@E^b>|>YDռBޏId9:v\vlw$B6@>)lRpFQ ̄b4əl迨rOhЪiX^ꁹ +)%eLG`wŋ@Kмw;<&&S;PCPp@ pnMSB): 5.qBNfc ϱ`Zc`WKo;1ikHڞ 9i=fByTt=I!?|:]}:%8 ~ ZiUMChm SڑppQ],;h )2@8I 20R9Nb% PrBqG<9c\tn􅔗si&l#}00-kcэ*dÞ"ao.x53,5h?wM#տxO;[5|xNYN j.(Ƭm|^1Jef#]f0N3>kN}J-Qn+)T킩@A%)6#y n ux4Iٓ+FenͅQₔe# uD*wstK z, "l<2ۍ~hBMyr&Kl&> SW38M7g r,&cA%/x^NdiߏSvLyj*U 9Yb>.e^($cvL2!`IT׀r َahiW*X¨OT20 +gVi 9UvѠ#{_M]x`dOj~*N*G-_Hkj#5 GĹ'N|z.-!7BC#ejG`Y:uK,:•VU5)`X,j%} a`z}dd3УKHdи"N 5'cKzك,wg,o[DLđ=ɋT|@_5/7s 3ɘ_K☫ςQ5I2!\R֜g]wZێu9\2V&; 2K+߀hp‰7ѵ060z5g2̨fKhstҾ׋R%[|r?pLQUFؓlRl40)75*(A`C%&PJBa"ba^G3cQP pAKI&S8@ jy޺46Th@U`?w[6 >05zY P- ,S ֤|7JT.ޫ*? B|E4yG`_KizV!vԢi\VR6 {x0x1č'^YD~OŪ] heKxCrM LC!*D#EC%OSx$$*[~C MyEVU|!~2Wf=>/60!\$[h96G(,8'vlMkBcXirJkwBd4S ~79Fijms7a{&yj쮂.4x0M*6Ȣzu73+ONtIOZuT2ix, Ym>%Ȱz+ Ao:NNf,KxU.!2l6 Z U|}/|[X 'tci= 1{rk=Ëi<@dp搯>-DŽ:]iqgC^4ZKҍ HC=+fGcjYWP=3jHD1v!Vܘf pu9U~Hm'+PHGኤMCgv[w3{>\2]]~MlV4ܻ2P#J]8fI^jp1,eA%*pxМE>X_C{R4c!Q"[vQCrOVV9&"YrbũkvKי61Ou>Az|LfkZ(sFD~xhPHV8baTLә 5҇:V9A1SD=9 t2ړoJ3;g\hY~d:~$YDQ5@l.{dsY!^EIsd{K1~M(D&gEBRlb.''Sl2֢0DXߙ; wT !:@4o Ypd%٪+QvB )YzM5 B=:4S/\ɻw$N ̢>:.0 2 ݮ뾳v\|dl HuDִSƁC,$@aJG!Uxb ͓ &van N-W&E wt>ãȻKEtͷ URȚv۶=ְ ,S :*hZ5p˙=AXU߀AlI&S8@ YSlj0BKz/uxuvd\x] q\=G(fP!f$';SHpPeT䓕o)V6ƴr\Qr"֛mKF y~fvyE@bdV6[?,ږU*l;r=6N(;%rH&` $4`W0kXVWw6(M ٻٽǘ==ljX.?t3^6+*Ghr/*a11E9ݘe4p[t^{:pqaN-svMڹ4xHC1- W/&v' ڲUuB故p)ӹh~@!&~d9 ewMS'MV m(dТy53hp-RYI1d)954u@Ѵ.e܃X}t"3n+IC̒,x:mwJߘ-BD%n ^&#1fUc`2tY ܴkzΑKF) 5!$^zP2 qÉOmxVL~L9]N1j)p:?_($9墱L⋿72!͞Ҧ槅cr&LS2gkZP8=s&gqwfPYB&)9|۔ha\|5(h2FʤZO.ZJ bTwunHnE[މ87&3SHRfSuJͳQֶ4%z<ͬ{|:r*D%F&!͚X?zHA4ð͜s."ŽL;,cK@Äыsؓ"*o^m"ٓIIgO:]iKzZ|@A_])2g1A.na!H!O'09V݆A|$/ywW"j1Kި33֣gsnBYs>" f#=%A4yD|3Y]t!^NH:X@5d挍Z2sXDYr!;}HۇnVԹ{.6-fB9Z?]㷶˪_Cv,o?żQXO2ƆbUfsZy]*/&Ϥ/]ReXRk5<}6"+~ L)EKXCeasͽےOM1oVnqpªYP1C ں|xC-)p! 9(ob;41N4pɘ0-5%5ido= m¿AUZ$: =?OWnN)M7$ߌZN1?.Anym(E:`v74emeEݙ:f0JNp'u9wLke(;^rR4iXm+œ#IC }{*qOIh 0%ά }&"hI\ Wx oʦV鑈'.tXvd S<jQdLӡ z$f$P _RX4P(SlǾkÆ|41)MHlDc.nh=l=torNδcAVNˤf*rZ|hOv2 9dJetC ;(#~HtЉcW%tZ4i{6;QejjEd9?w2j#seԱZ5l-VNX2:͂[*#؆1@ 5^;\50tz|4u͹lt;*_4@^˓} zKLC\DJITmVN}KpaO.* Cs5 &&{wJ}Bqz/{nL'&;ݦOTtzǑ hVm_7/poFmx>rVJH PO"v ;m@BK\,^uTыsw\ z=:s&T6`Ћ>Ygxd_nfǃ6CJ^bE#V&-%X9屁ҹ7;1|}RGy`qb|E6ư)RT 3v偞0/e":E*KBU4bɅݝoRϢ{E&ڳL<,$xO_H! .x"ȹҢ¯eh~KY&NjyN \n'mNxy$0>yغgTWLLYØD&fKsJӣ"3(vyjЭGYus?&^r?{8[Z(;XU{ In(5:SZ^N[3JU>Ȱw\<ĸDգV&撑Y،:ВNH^b_kDtFpʜBai I`L8II&v- wc3PF 6ϸ%aijH=6j;8u1\p 5wnUS3Ʃl5* d|=Yŏs]|I jCKϕw9 do۾]eddphI0kU{1ҁsIV%%Js}mkytz87}1s;ۮZ^7U# tpukks AI&SEQ\cJM J*v%B?/4Ӆ͢'_5%xK{1]ݲ0.x)6*Hsɬ|!u˲(B_:iAkF73 v/ ${]2Ojf TI@ŝA 4-_q3;7żhˍ4P|OL}G+!}_t"8.t'C!ɣCb* ߧZXUUُAb8r$aKwCD&0w^u4)c*p ćA}~SNj;ᔜ/F5:߰y5IF^|#~-RpnvXܔAx3iZgiLCG׍/1uRgnO`M*uá$`)J F5"GsزDs΍:[`\{QC<>;kQW$ =vE8{06~㬶c $ع!.^6NCA "1a [/XR6i_/]{R FáI势b@VcV,R?~5Lvv! 3JᣖQ*ֽSoE#z(ZK3sEHw MIE.q/zJ5GMN^ w7u4RLŨ͹ڏ7Q*Rd1@;[{\qj(FLbEL$ٲ?H y\Yq.e{ե5ÕrL˗I,:؝2M2"Pa-*bc<J3\eߙMloc0M*Hn\zj`\(> wjG*!Kb>=qC_.X gEtt<:֎!h KHүk2tN9KN_OUjiy>V-X;ܳ7v4M<[rB. 8 ʎ.xD|ʜ3dr-[mlt+څpq v&[1"OnzOku9U@jBgӠδXxB*Ad{]2j>3! e%xԔjP,~&:큰'' ++y|3+׍zށ~05f}wm ?;կfȜYI-M}P_Oq*46 X$@oɸ]LN=u9'@p^"\ lG3$YƗ}l 3HkS[o79I[\e \Btkkl57j c 9l ,v#85OH_݁|gMr^Ut"B:}(Z1Ae^mkΐvFh#fgGVj“# Ƚ&($/?xV7UҀk8 D=!c*mDmxըε-wqy< (UfbơvjL~l9i톳 2n}bc*jʇ@[ <=* itp`Oր*J12n̚N 1JUū2&iF?`CswtZ&\6~H,j"/ sk{f9-Sl~` f $M12>(73N2'0(ʹI9o2lr{2e$> Y 'ᒇY7f}p}$۲0 &BRAE8D}@f92#(sq09+U"Sۅ<*GCRѷɑFfV^Wnz ZhG{%벢K\Lw@S?];PNG IΜmY#Ro*? 8=N_-x˅q!df{صkE)!鐮farЇɬ,@X(9nʨh:-狞lTqziΨX=NGl-pA&v'NBSpl!'Ģ+÷÷1IF&vv؟J8K֢t=">KZ"ٞпZ-(Q8}TI v-%'rm=u(לlGȼ6I_RKb^uVۋW J?(3Wcp MAI&S=ba ;mk78TVECvLY.CFZMbkQyytq!1Q]-g$T a 2g%LuE(L+-&*Tf8"Oȃҋ`Q0 QQ%;m/W3a,@ ~*uT+9c83Xl4s3DX`5:q[5D^ZY*b4}\46*UGچ`=ݷ#d8Ly`9pȝ/2)ѐSd~'ԥH0 t۱Vq22Ȱ sA:vv6hcUuƪ9X AsD08N۔n^U[܃a]3=WajY&5$O Q-pU. q:dX:{`O$7J٤tPIkllX &ޚF"KEΐ_l4(hc}>V t3ֿp +1  (ާgle|wϙɏ_13_=@3a:-C |g[0[y ~,Ξ#l V=i׬OIo W &h&3zp5g!GX NiK mѱJs.dXE~o}DS8!+=8M&n )h5A0=<.\+h5%F^KN^0t {4WwQ৵/- *b4_uCsi?j6HQYL-bb{ fS8imM}yYELb0>ȑ셂/Ӹfʳ[x(KoqpC n$7R|y,--ގC-inbxM-YW%+n0Z8,)FlCKe+甐 W "T؈E[ QgO ¶{o2(%WE76=Mh3*c GѵJ=ެjOaSFyHf3/P9A|)r~VXu R$fNȗՎ[|*֕Ty&vۉo|S#ްNՑ QVoBj:Hz]`7iNUa!gqg ztL`%lEefвH:1C f:P D*Xi,a2׎8eC& 3xlnmE%(j7S`GWٗ u]Ǹnو6hdbέi%MS>ߚ#VmGg?{Oj^:@'S58}LW|i`٧+!>tylE"'(%s<ԄWy΍iQZ}U9߱./ћE2%NFp  jz6FYÓXw$U:ֽ|n'j~V}x;=/r;)}K'C^g%n,cZNOXa')NαoZ=*˛N[+L</Le4Q"_JUX7l+L$l04 ; ^9`DZ: p5 i\;znsrT N:DI$ ~x ֆeK qz JE@9 8 So(;W%?$5V}9~^+¼?mM?y Xu~OY{_}T܋d> ? ~OCqW@=ue=]P5Q#ːz2UF]dL-M&[$p~!'G pƺ^ؙz\gbJ 9MR~9rK҄ԀTr .->+Ƭm \7/{ʛ *{!}Ru9$ tY^ SXOf2Cj׶=fhkn.AI&S"yxh2ߐbh-q'3(EMZ2=E ܶn٩ CX'DhHPu്TSfYNÃJ<;튡DOYprrk+ScNZl$̲,d-iځқ%7wZ6"3'*# qd[6355[Wujd,bN ̻!BoAVXY4篖(U ߈UtVRv"|+=г(s6XVR˪:-_'4?nVR¿( oa^iƮ|R+; ?oSG]G ALR);P)Lbҏ@_ŝe|5d'is 7pJz7%y ![;->m#trO#/b?%88KϩQ]qaֽzB?^pwn2S Ynlc"nI+F~'o7t8/\=jNh̉8VIZ!^ `"BjbMUlEE =yUWCʇ3QsvSCn;L><2eQ@gۜYot/^&i0"s]1\rq[|y:FW'OqmH[HלY ؋î+;B Mʹ&;%'a7|VLp..Lctyհ]\7ٹp׸NQnX;*#WP2}ʈ7H#Z}대|̓Zb+A],nyxf %.ʄY Ucy]r=It `iO>Bnj T&amE"S;S$FcL/yY(r 0EkHeU8Fa^'f_\3W@_wUֱ2G09 +5"q_o R&ioxPc~m*$v64=,8?gFP2p 30Gti*@6KQ&ش>`L٫^j1&\D9 jǾN;*[lRdb ֢R~R "-ΒqHЀ$7#kabPMB*`'H5upǭ+X3 }Ce>uY5-%~bK51IUH 1恱ݞyLүƓVQ[d肝od*$txHp)ueBqIML t\FqVVm51 "NB-㤤іMF fxlE&xIr&M{Rxa R]}}b4g^q=&¸.}yk 9Ju58"lj۳\LudYupc!-7n+]ɄVijHKQdPk'tp%Ko (,|7_p)x3M=Iix%GܚzoL:Ja# 8!rч FdK\5ҭ (Q3w5G]?q/?cOEreߖ8csNn v8 4'I{ʫM^2aҷ4mZگZ=#yMaFq.ImI#m0K:6zI%Lr,d4Ba~ҜbiDF)2.K݅N]c;C6 }<!=ksosOsϮA:jhqD_ K_9^\%`'yA1v}gm?kJus6 ~͊ħ'yN(>ˣH 9e?{AIwɂR4=xwějg; 1I*ZhT;UReړt>k'Á:WR׈V!m?khnzI 2ZrOX< udgi<|]yX{:-‰01gwK{`1Y2yhWd4M] =`a΄瀴a.gY.ȩ6o5u# 1twmp Gq_=Ec~dW$ky[-sIѡJ]F$^ΪBp_; g" O>QVmBc ʸjڷzԭ6i D;$W텩)_g.YF=qCOXT>)xmŵ0-v^uSEΗm&fv 8bNuTNaId'[ .c67s0|+v$I5Ԏ5Zm"׍AdA9OC8ˆpa]`:v eR"~Xȸ epਤ쁦Hz2ϊ @$vQKzk?Hi1wA/H7L* 7!r$eDyݶ13nṂ݀*?E E,WNfXSnd8R&dF!kIW&8Ǟ6mhhXSmJ<9Z~zQz3gaR֣pPG޾/- L*TrKF J,–\EQ^S9YZoהke Zfh[vI̚TƛT.n*LFS5H9ɹ=srC0Ks` /(6ՇжRQa|yPThB4UREP7 5OƱ%BG.wK:fg)``4l\bTxvyɒكW.ƌ]lE.ok$a qY_";JՒHmᒓ 1WEi}w{Cϥ+:!uXHsLT @?8M);GrTD/b$#M@j~TԹ j!Ach_r93;^,{p1]6"BֲP!%).*Q?_9kڤL]mzYzFh:Ӄ\5.|Ɔ* nAr?(+޼x $ A@,W#&?ԭ}K*m˭x::QuR,<1!FI $yCߓ+ɔk T2I6A ]/HaQMqkUc͞;pښB'9o0/끬SSt_ç\hzۢeYxB[8"bw{h[*27e6ޗDu6b.>̈́b^ks&^1^n2a%+M_Gck,ޜX:̌C2o}7'`=xzЋ75Y0$pm5&<5hnx3 O4YࢇsKSi'xQ#겖n90#cI纩| >(ym&Dkt |N]79 d r7+FJfʽ3kTs5aaE_hKm&q𝉩--m.#|Yu 0{ b_E*&1t {խ[=el v[n[x_x ww J[%} SD,;l~`W>p\>ϙ=gd+X"Hz9_6uxƔ`C@gJ+ϼw!t -5R –Z93?P[G[^KAIGPnJ=nI|xC$̜\;+\!?W zBif~^K)~\sJ&\5*$'\̮;zÙ&).U >.脹xWYoVZ5hi&+gGgY撀fZFf5iY-V""7xp"Vz=kʭe|jGdžcS6Nzi#X4p>>`X{8`b>[5K]e yb^;9$pAI&S -ԏ5 ~Frz;*MУ珞[pP^g#)1im##gib7~P_$0{InJK0.~ZO*v}\[RHTd8$ie2Q.RE!2Wk'Ƽd20UyOT ܔ =/B_'Zf0m_Ch{OmH&fʠ;d=s3%gbgP}{eZ2TAKB~_`9ګxZ$6O)[raaU߀IXD](Yd6dANtDBmqU$E>J̚56IZGcY^h<@{go;UI BVհZ5гXQ9P_ {R3B~>J M lP5Fm԰;)*ܧʰUns6{)hphhv GwM]C *pQe?FN{S66u,9opmP68k*=>l$ahސ7>-_ц KHy9˵y6Q0l?2d2z)& s|&m:U(zbvd2S|9#KK 4 PqqRU92kV=]j!$s>}+$!#7JXToXz1ݒWwxgEo5tlĸpCP˘ЩB|']Jշ;Dņ4ˉ={ iMU*|0S7ZB)[χIӺkIq49a1B{wRD~Y4z Ao0p-1Ѷsf@3. yשSf_>ZĴ:7+eCs$f_ݕ4G,Tqx֎;tpS}]1 `q\B{Mu[Jה’sMuV /&57l YvY=e؞Y I=[zBH0*;S2w ~ r[&і5>%3bmN+=]Nx_umUkct֋Z]ќl;Br3 ̍YGx'1 8Z Rl wK!wW]m~">W^tb^HUqJ~#nS48UZX2 >k *BT҃%}2q\85de:v0fRR>;HV<ނs/M;e*Zݗt({M&%)B>&\+}=A^36 Z$2И~ iugq~LJ<)6E bہSzai`tFfVLơ0ÿ%?n{jj㚱;47 V|gY@B}Auk %|Bt=*7>p7Z y qߝݗқNʊ4-q}谄l?Ql Um;~'9Zm܀/傅κ ->Cӷ~q 5=-)#{?V?g$侦=_N#s{u]_Aݩhe *1asQgg(o<',g vƥb! zU fr%kOB*R5g/tžp§!`.?Pip+/Su8u1hNӓ[kw "?PT$ z3m?ZĂ'/AP`,0&dft; hS_;Z>;dvame=^`X H!S33.0TĬ/J t7n͕PZFK]m CW37:TLNs- rUۥ].cr̢uÅxX2J2vS ۦ3q%Bfe3-%򎟫H8x2 I_RXOpKS&IK> T<";G:]{r*8kT4Y@Nb韲z&C;,4`n:V<"43rjΕUAc};Ym{Jq?[353T,E~hvupXA|^Z6p4ZgK8nAiZ,5HƝ6'0=N|7vTMJyGZ a{s+)1WleM⺒R6S$ڷzKP.0fmTVRJQ ImM6uֶDwVu`#.{ U2:_/q, E-,0@5&IsH. ,t$ al˜vzez_,__ ,jL̺ B'dfޡm-$ YOf`J=yѪܕ|<8>XPsuG\Wz.5dZ2 7GͷHR RDy1Fm!B4#mbwMyhFD)VUJ!#Rbc]-fhh\LG Ũ, =#_Z$ X 3"nU?!v\z`ec1og0 ZfxtT)33}lA]$䂩f M 59|=wms2UUN6&y؉3z/c nrEz=n,mb[Z!kaRhB 9~>;^xVlݰ`e=ڗn 7rԛd.#[&O(A~//Ay7sv:E @0-~Xm!v7jo M+nUIP)Tp͘5(1@{}䖒^ۻ޹󼑗Iu9 ,,hN!8Q%,,tN kmx$7[wLu̙f.&[_8eJ@LdHý[~=J:ap)3HIZ{@Y'>҈%bXXs[o*OzطI -1jBv8 ϸj9 *&f'Û| 矼q&jjl^Td \V.M[FxqL!Q$R{:(il2Fi{øGH|ƈ/VTc QzM5]4+kK9GaTq6 Pr a|6ͬU]IJq,%wX('$<`4ICۙ²wTR>-Ò侂,d5HK=2_jXDܤE^{:}K:2o¤'Bkׁ;BhSe]n^,?+/t?Fڞ;}-s;SA2kP?!^vN`A qC^N%x`4}Er5pga٠A3%OR܆Baud|OGeEӂ^ۜ4{gsӔ[ ;#+#M+ӂ w=p>3r|Hi- i{V"4% ,$r<l5VZ?A]iNaԍ^X|N-1bbj[ZO`8L8n0gܿ܊/P&%ؑC'㌶}S.IoRsD fүHQ"D׏kL%гL~*+€%LC ]mS BtBIvcre&G;[` 1FR61d'Gn{FAMtP4>oI'1-Mc vYzG#>bP5me+ܼ W +Pme,!_~?k|NqUUh6gyE& R!c% KqBNTJnMDeo(4KqB%V>XYL6זĵx,ذP\QGG`f䘨wer@5xr]C[-KyԃH(ЏuG+Y,v.V[6~$2( 6*Qjnct zA9-2 |wx u$T{J]p ?J 9cIs*VwX8\a|zԶq'aj)5Ys v^o8zg[bָ׹zX!@'G ٨4Dv J[}o^񽛷 ׼{$2/ײhFRp M\ G.*!Fݦ)eֵ\6#v@Dky]F+Ic@/~H{0=X4TMOҟH.Au0;ˮ`p]mP `A +/V^o@ypf>Bے? jYZ{-.'*Y'IQxOr+yurßk>`|`ڧN.#;ub TQlAfu)`Ϋunl[фHq/G^%sw] #'M$,3U?3'(0AN `@[veItd@,Jfu)y4L>v3;vN쁾n# [wUE~E9̑d1vvB*)iJy:lLH7tnDX> %rCM?3sxypɜo͌(H^qlM37}c-d9nj`~1 rհOT;@Re&Ns`߆+\lHE*x ;>ae8YơWy`~go,IoRŴ)^*Y 3$d)NH'nWd̒QlQ*Qm#o`X> ~6q[ǭ$~KW48@˂Ԅ7*T:Ps^=*A":yZXQ C}9=yS4h2ɠTL;3Ov6 f3LӖiӉ7N@b@[reª#KLiJ!;gI )Mr]Ly+*sطC;%:K !0HxЪG֠. ײQxK=Q>kdx}[ 5hhuDHaˆ$40/2o.o,`qiʤ\@Tan`OHE*L"'løQ/OmA=%-\zkB X0\+B?DcW+FkX; f |UjO )Ơ,5֩W]2gFhϾ+L}r@CU%s2œQDf8ϟW%<N 䭋I2ۀsK]ܷ}~; %TZkX#`gI5% tMMHf5P|אɵ]raX8̞3p&`IB8a_bqމ,޳ۢv5*ǻR95FHL5x?WY?C˟[)q P:90\ZoE'`qo&'|W_<E󆡅b2$: bM3ǔ ܆ 5ϱ( ϟ+wXqS{*Z=Q0DQ|o./SB,3E VxxbVWlۂb oja&aehhT {;vIK@zФcޤyqx;KnFXS-݅8;xpP.+E>[igK2̜mQO}{|u#rYl\K$pS犵y@#Jٖ$U_ b.lMXSqLm_.~ﯩBD/~MgbZ3vH \i}bK2|e.ji2x28 /Y[ 'z2l}–LƖ^Gf]7ZPW5]=wQ ލk8ogmbIRZ%?7s$T"i/2~#<]~0d\ś'؎A7];Υ6rڰ6.^kf9vjCySWE݄:VJl9hF[eo~엃UL@GY[@H>Fwn{8 ^Gql@ m5ٰ>*Wɸ>``]xfjcxC 3{鑒4띀jQm-0(H?m!}A+ vTVFOrA WZjW1fʾA=ٯ/Pn"MN {UI?V xDUT`A϶qh@k&e"0 A.Vg!LB~wUK"%д;m91A$ o)ڎ͘T&pYK9fռ_*ĠLh_ŋ̯?1 @ڪw-. @7R\;ݯڔP@\яgJ@*%%18#?Q5)1#@]ܞz}xe5~rP /{8q6WLg<ʕ CH\ 7RjUWӞ#UwUHq8t"Qu+m1a'j541|45c˙G&Lq̐fi<s+(׵]jļR=hnPX y}Q|>ܮL?r+mBeu ±W)w\ B,F d4 PPKBg)f‰-:ru⇔p)|&s4 t$@>/.CV໨@ReEiOtKVfއJNX^B vɍY3p~ɡBxԇN}s_@~OkԈeIav,Fyᴥkq,ׯSai[ul0&Ұv뜛]!HZZdž~ az6?0Vp?Y?C0kB!6?LX+7MqCVCBuΈLw,?gr}}釆]k򵞷f_et7tʎ;HbtCwH'Ν9߹ñb*MUFSL[mЭf UqBQf0:pe% R>LӘ)i-Uсv.f^"@2 [L^ۙ~GMOk|yRȌZt {tL@:JV#CA#jTna@d\ڤ/}aaQ`[Dpm(ϿBJaSʽTr~X 9n'P %>uƽTj:vTzL;"|ֱhD)knQk^b0Bj"BS79\/ɩti9?c&͌ܟm@YtW7>d:bS_36.PBNPaj e '*C3dEqv=]˩?h7+8. 8,?S޾-ΫYm=%|D, ?'I$Ή7S~:r7gP%4֨dPd7Z`+(Yvno_j:)4^pZG-a2v?DK`jH8se%ٔT2DMZ7\ri2lY2s6YRDVLD%2¥(k#oC^<0zWǾl[L="$&L͆~g$mAfۜACVM'絎מh:n34oVL9@]@" "[KK"GV WRAefh/9X#y=,nt8wDJ-nܕl04t0ކI]wnKZ^boZpsK%{%:\>U3! Do5GjbMJV ~X23A\k{2tͥ` ~3w$"|h{_CL^Q@I Nd,Oiɤ 4 "dM= vm$5EOug(K+rO,c/oTrZ;z,3 7_ul'|sST^ 4ۉ 陶 Drظ?H?/ͥQE\K~&p!P!ՋJWgQיjyV .V٢*Klym+"Og܈"cd@91bD^fW; 4VCmVAg=}u5nO9l `,O-o"EGLQ;L泂 j˧UMG:8}JXFVy/|z3w\q3&]ri3  w1g7YR\JcUj7P ߭M}vڎ6]UnU8PTbM%ǡoitkqUD0)7&(_H?%u-ݎݲVsԾX*kanvO={Լ1 Ha- !)@IoO*1+Հ`SE {>[8tӧm&-o{ۆeWڥ5CˋVdHHB>tx<ݜE|;:E4d]KL (0k YVM 06@cH%W6TH3=7d!(&r.E'7J|o ZY֏ayܡPmrʂHhzHD"8CyyY']iuĢJK4#:W# y*wV̘c >.Ch7xT2Lw~pY׺pfIe { }`ZBzmO҄@ hiTolW|tz6SjomIQpAK⏄Z d3k2ʾɵMø+D8HgoOv*䒼^cny2rB ci'R(H4Fj)7j .FIUDƙ3V:0nvf܏GGеS6S?V@x~ d n?o]R ϸݔ*H `͔eb e;ǰ>5 x}~Q++[ܫN8 h}d!2U *|QEHPP(?!R iWwH'sH՗+F* XWRsp@l"^oِZ!2P{02?bE9JX] ֛/~.ze/׎^f([$3h9mEsdr 67Ƚ/k))>>#?S}BLVXvcA~2A ݎ.3˧ !Rj 9\i-(r2i̪23Pv  KOwm1~t x1Z43$GW$+ ov,@çg:VHUz H>lt4743PDkB+;Fk)?p5"YSbw2E; 屍9@17&{J8k*ai g6MJ8VU䴾 :o5q7NJS؂8- M wNZGد9= $g=rk<!?ƺd K怾`BsYNҙtcE[A};pӛ@/daZАw>;.piROzƤFZh9œ1ծ 9-W݁-Mǿ6!&4r9KA,}46MU"{TSWNn_",s5# FB $RL} v8[*Ͱz(%'+GI2zKwe#(vb6+S?oK5Y\EW$շLFPG:`E0p*q1XFtd2O,GEcd +(f(rvftZԺX $ gvlێyGr>-[Srf~  1(Jp`/٫ۀwsh$u\Лx=#WjoT|IJ Y2Œ#Tq}>? [gk/@72EH¶'Hi gK#ZЫ$iU!͖ld:4y-o. +qK9sS u#^Wp{y\ȧ*< $,/U;*}DҠ!v>dٴWzzB7lbyKyʦ'den>QvpQ.Q,꤀٥ꐫ6rl'^x"ƢةtPNwgu2=9jyri V൤Z0w6,Ny HZhYe6$lN2YӀ&UP"|&-Zz”0B!I}pS$m G$Ng_z|7b~EO&fq)`Ùqi&܄䂌r<;Yw:īG)Qy(0Hh`>˕;-V)'NTt:qbpT'dӿ~7}&Fr. H|Z{:$u|~Tϧso;. iLf(L* c E_)BLZވ YyK[> <T ִ*Ʌ m+SPf:Zr8JEeB}@)W̆.QF3cxͫ~i9èП޿*2S 0 ].!YQw|rDk{$@ ? KlVG(12,WH3}YAc\NQvA|wGwnNlS~Cά._i03#o@`eu ?01xcFv1-jP0慝b~^v7S//n͑Θ4Rs'帰ȃQ60œ`tv^a E8ߜ'znޜT s{ >LA@+>.,y*7 t30ۆC.-D X}FjBmk)S/!hkPnc}Ď JL#_iP ěq;voi !bpUjF^7l k(bƦܻa()/8p3``Z֢_K_|za8q)BJ0vJAD(鴐x(ongTR4CfM>![SWa¢p,0كUDl&A7r[\葆1uR*Je4߷rn%VĚ-|+V ,o$Jx45Awđ05iȠ%IL!h`H3# 1' `&3(G׫9/l h1}~$|yZfeJ+Qvo4>I"†%Q oL`9:ҩsΚ~D*+(*9*Jo`v%&=_\ 3W4>Een UKs8$%Yŋ(Jr&-p%ZXeQ6*`Tݝ%Ȫ{ڒר_S5=C0qtI sIn0Cl7<'~$ J5ţpQٕ=bln+],{rz,7ZnrH3qy/q<[}o[2͊ Xʲ&D'^l ee~ pf <-_.|J:H}xЄz(59-ɂ QDQF QՠYbHl5ix *Ktwgz uf:Vø}2]©Deu9dǑJ":uwF}o0[ۑ}lW<Ѡ.;"]+ a)g2QQn2÷(DՉkvnT⋤>={^gCnL3ukO93jvk6'1d,'H0׶)hnvCkfe ~Ǝ`)q ux3 dž9hq}UŇ;Dde7&sr-afHa(E ,Flu۲:|C7(ßyse2qܞ`ٖ+MA0.Tyo42U5|JuŸ+=hO{ R] -Lxa3C)æl~E-E|D;>t"LuX]aÇO5k%b.ffi/rFEk禕6D2Dn?df64Q/JV1x|J<*\+3-vsv:' _Qn NƖЎrߌ(<-7&8p SW!rF/Wpc?_t8;riÑxwF \V-uU1dVc% Ś<̱xwKewLd |.&?\ӿ,V]EB0,<(K?cM B77ݕh hHZK,S^S5nk$#L{]@zO妬H U@W_;k\&VdS 96u1E]f?Waf<~uny1 />_mIIu^I9mGovY&U |DU~괿ǧifD.5]PFI K˹[W^$NBJW1}'0 Bz em҃)q-Sq zT4ơnx=GVA:ɣژ4OJ)fyT"mрar[KU*3%H@|`ހc.!0˘vve#_@XJXz&I/S( ~U@0Ò(HzČV%U@+ Iѩ(R,)wAs\!"v /Vmpt0ur|zݦQTh-N!X:x¥21!48,͒y_9R-({τ"t5BT4X_L{EI s+<:,;r~@FD-0Afi0 i)FA*9s &H ]xXF2Pa_s7-YuC'7M@NNݮ)6FT" Vme7N1:[ӆ|m Oe`q[#-($@fJo+vs^M`;dD#YbINiݧ!{Kte qd $l\TZS%`8HLzNXER<@F& @ohq(.͚ 7̧6"ujB1-|WoxG2]WN-Sf&`iT+[ R44476%:i"C!\]l*A.ƑFD~㰕#Rϊ$ѳ[MY,~ ޷򯵿B8g s;V4佻!;YA@gtC|&2V6u͚Cd&n /¾/?hҐt+_t,G㍐n7oPFd0 w#^A~7G-vظBuU{ 2Ub#XSbe?e,. { VDc}WVMWd 1mMU,V6W0&Q/(2!lK8 hjN;9 ]RP: 2{ZޗkX2gJ+>}D^:]%IH»7qjy(lkȻYFf3O1M#Y=_+k{4PVnPvq>< 4?,js&gEQ SyC]h;. $|hL |I< #CZ7V*4gVcs(Ų{XMB8jȫ!~QG*4=A+l;ls@iDpRiIwot2VHۜF< H[vnNdQXY<VDIE= IYr27rQPW lFH*͗z!w}i=’[7UK%'!MqDN7NDƯ28ۉBWmeq8Gw 8P?~װ gzv¸3y\erQ6!ODA!lT1uۑhjm\Z%Hy P.#.E)0At_8NJiA ;!bc}U6L#51~}s'LmfVMs@q|ɉPǶx c3LPXb](A{շp=,"j>0&P*zjMmFܨ,MѬ,,bQ!cLE.bZ=Y4*Q8W߻JrG]Ir^ǟ\7by>n'g8Jҏ2fh\W̸r0tlS˿_n(ƸF\Zq$g4O%A4!ۊքD\Iy/ŤSf$X SӴ3Bt;n^e=G+N^-k˶~[7+IV;sSB$ugĵp$ɪ~Ln[}RZSXP!%2B9=BSǎym.2",lFO$o|k#s)wS/{[mn1clۏǰW#o9ȋ/*w#+P2_ytq.}[  %ÒbZK˺p?v]hwDf`c{ d*HPvcN PGhNLy:mFfjPø(Tֱ9TZ9&9"|`f$c,߀F}GdWX琺(_X|K@krоԬ8t衢}2{CQƆ8L% 6z&'[Á@J$"x1缋7BO}2}y,׼b O۷=@{~7#pSlBղGZH1ޙnP \R!\;y,ؚkm{ʮ;uu:2 \O@|Gc9yH4D8A#4fήE$ Sn^pC*^%oo+h#>2чzc~x`S}ainc۷._X -M"Ȟ9$YM5_ wfXHv+2':(JDyz ?J~k \zΔ†ӸNKk53h(wsGSrHʶiݫtcZTٳ;^4d0Yue v\2\r<]{}M 5Bm)tjs?bЅM~Y؍LD?& 夾`!D-D$š+Xm"N "Yq\05 jv $F(XޡMr$E0M42@h\&yIcEutIahbVP;/ȶ5+΄ېQf4whc't$ZzӬZ@S̈wxT:EO\r a?@ *';/&rDƚB#ʒMhP[~l.M <ft0P5bTkpffd,rLGƟAx>X7, &tejY_sH: S2i  b\YQED"Wue盏8Gf뎌s,hâe9L 0EF[XmzOUoǚ'*;(0吵5(ǥl8{lЧ) m/get'!E~w&Qo{x@/aѝ)oWNBmm6|He<uԘssⲳ\'3DQ ُ (3䫣}_I Nv  5o^{{ I HG w S؁hqHf6Biީ^եIf*A= I{z{$(m">7oԸBqߥ>O/jiAӈ4`֣\#C!%QKJU?NvAӦq~GqՁzrs*Wa4cy!W-muUiוnk@LJ+ՓΑO`NԢc79%IX:^SwNC11/W;^#a9pJ(%E'!Mk7ĚH4I=<³PyE3+0'N?_>Tiy78eXYQWнzjH5neS3 U5?6~Z'qVL"Q'LmwAѴj\xM4N8H gl"SW~Ω'0NEY#Vz#C͎*h륱́~֞m.*En>fQeڍDPcV[?vOxq7"3jh0 Lf~B=V!}Sg#֎ꦭ3EN |KEN悦Gmmaj/]W#7؊k^g!D>-Wo$CC!qm#lUD-*D Kw@.wGwH?ϦR4`Di0u[)ߟЂҚ4~sV4%YzK-w_$Ͼ(ٯ\ UW!'s?҂n'B1'/PB\co/LBIQ ;&Z]fEΣ Ƶ9ٕk(-fISt$[%BB<%wǚi)h325|> E+OhU~֓'\Kxeb0ԅ?ضsԸbS pw׾ab[jXhA.W(L385Us2*TJ*S# VL_| 23l&z=|m;O+ʙ&zkhNNh'>t^Oo'^969%3Sbp~G p4=ו%RQX[O sŠQ֠<]> ހ+p-Mqv=`53 PS$u[ߜwþJuYxVVV~lE?ʟ uݺ;|5hi&i&gaF3&imz*A4 g{HR B0* nC?:NL!߉ܺj\_XZycbNcP,s4s,BNkwn_XpP.>eKHํ rAByAx7U`n'O|%'kVbN@ Xj|,bk'_8s5,yTK3ps+~c3е Z> dT'*!ëo,N쏓;tԱN (&jW=6=uzڒ%1z=:oI%Bjy ib_-TJܘw4}waʆQĝ}HAacvSBk]zY]K/d,]Īb,dϿeB[2'/W~=gye{!0LQ{!ȗ'H;LU*xUP>uޮZV굇O׸tVJ B+ Q?L '^hl޹& 9].[*4QQ!g1&>WT =oxrťO;͚{(jHcy"{a>uDˀ6?mrxȷ 6E;5,? Aa1|pb$Lͻ MWM Q J!ϴ!(pBZ9L>1.CnX`W \߮׻<8@SDDf,'kx: BKm]C2?|G:*3a%aF H^^4vj&B}31VcQ̨:``LQPiNmj4ں'uI $譝PCVK ]<㘷t"?V`뀇-6̘AjÞ 5 YqP#qY.E֬s6K!ƜvoQ@n7 cY(䱁;@xk anQ:/$,sCuSZMN`<&?]%֕X~+VkqxX C.Yf_X`9֌%< z'ݓd7n%6tP>ȿeTLq5jdP7>] L1C^x(p\oQ\y([u0ꩵ5g<5]#^i)jp_ $Aʿ|85ΜC24`ޘ 5V3% z/5ef (TG)lu3(l: FbtOesO}.`a~!@TSTqZ:0.Igg"h.Ic ֳ1zBZfa1kUQ8 cTK(iu9IpHJBwqvʡ.cRi^zQ2)O/lhʫ\ΞO (_x:pP&,YaGZgp`/p(C~{\Nhg[kDz!mܞ0At}ykjAXoPj0 17LITpW.&]_[IwSerZᆫ-4ZǹrAx`ƜezaEqD<3SDH J5+)lEȥ{Zam!N|磹WfLl_(sxX'cEL,3u;`fTPEe 358<NP{FՂE JeX _Ӆ6-aёr?d~ Q2X V&[b1B=ӵmHN-(AT4OV-VJŐ1{ϼu0&}ÒHAxۣhXܹ:"E:3PÒ.tG±?j}ە=bY֝[V w# 0Ϙuo1{n2RxV KUF%@GqM{2*7 _?LO5Y,e{^/jk͹W֚]ЉHޢR~- *V`''+f9NZ\i?b”+P04}'-zK uCD)M@Em@B1JU3r1m?.vߓS:ީAŇi ) sK{+A9إLQ,yo_ީH-#_jxEnRߗw6>'bA6R[O 35Y0|}]Q˙\wdmfM9Rkϓ|%A./ggg~wYu, :| s##I F{}M 8^t/eJIIp=kZ+,ZWr<66Z5-՜͗\!yEdq@[[M"ig z1b5 Gωr'YY8x_=\؛'!2V]d`eHϊ>{&B0v <0 ^ggy;XFt<9)v(od(]8?ta VE3TDvMA>*;,I[ڔ |(ߞYXgʞ -l^vQA`l?!Uov7qrb( AcI&S;=hk eP1V2`F]%R x=0ر?ZL`LrRb?eQS1u疎h鍚 s$I}Y 1"D+7yBo ,0T3[?WH/j e'm>ͧ]e Թ.>[L =:7%Ԁe۩uRۢw_"lH0Q'^j2(Zb Fv.E5ňj3I蚢hrWl33ܖ"<ݩu ځ.j{ϋ_QrhAk] < %/.49FmYgK)Q\2> 6zT A4>h^/qԑl~3WQOqLm5r\EΣFJy@Y&j%<7$h(/x]V>-gKBԼW9\"xH<1òpZ?&)*y-mBkeSftqN#?n鏼y* X:Q㳳缚a55CI\PͿ(@q_eI;/QL.JV]$}ӿ-j#:꿩()3Bp3r$lMUp@Α +*M/dIG`Zl_#Yg6P&^G\0C4lF.Re$4剀 :[T9 [jMz-aMe['zDK.h$N{(\e @`mkV}uIg8mW$_WňQTK&İ9g5(pΉe) cJN&bDD.W(_JDAbr+\S+Pk+%}~7^ E$Ö0AZX\ag P]R'rsbwp%Sf[c+tbو:?hU*׉RU+|;d<7}a&Z10=\,.>CψڴJXbf]1Í ^=aOo=n$;F!XIUnVr8hC`_5v۸Ć2b@>l=Ș71$ˈO #/Qb[ م}(Ǒ ַG' tX^s;ЫLZk]c4Mrs#k- .}&QM'~K F),pLJAIi쳃ଢ/`lx4!C ƽŭe/:Wx z-b[cUrVq_"8}^%C+ҋM]%أ^|IMYE;J~eI-Bū3/k\tyr}G+~f7jT&!3Wpy=-G6gT9vYynzdFrUҢܫ0A@Ȃ K8fWNR,, AР, ]= @|ߵِ ޸|3C's''3|_vY0p`u@YB`<^'_XS89]RQk7߲&Vonm~1R?>{Fft !AI&S?w")L劯ҔqJs3Ҵ01O֐<-AGodU3i/xYh.PrX_hml7[3cNDHY!0Æe/spܦcA+_jCzJd5To͍-[>=ondFTS X4zϠ6$j'w-a)4*^7w| u}ӀؤA ͑}Dd K77չ5ችGn\pei|x'Nu2pHY;e'π2-1WoZaǪcA,tgXZj XhͨAUyzDƲPzmgj8b8yFtzi#lwqA~KjtN.[,гEr31!Y=#^m6/r9۴Tj*&UMND0VHHoF;c68JϔmHӇ S*&싓w)( :"{6sC?.vև`Z= SWwUMkj5fjcׅT8Y:Qqg#k11詮}&]KW{Ɯq:͖Hς0y {!u%(\u4* _LT,zz/:HĚȤ6围x dC&L)8cQu綆KVcfgF-u+\P_ \5?a>' Dl^YL=hDOX`f~x]-y,WWat}$w۵wExejU.1ffB)4=Iuâ{[{ |5} 1E"BE\`O|T/,﹌cWB1%ؑy"i? }0+똋 #WDwR.Rn]4+vS"m d'{FF>]jYQ% >X{ 9[ۼ7:Ab{"=yNGc Q x4Vm^N}0d{L刮O>ЛO,,)0# 1$N ԑu5 !(!.}%]ͷ3\]Ze"2gpHN= *pN p:~$ PM 91; DHI0S(N,UJVޭ?ln Nxu>{į_WT-0BF)vEhQvI:<q,r@96&)[,]x+4A(gp䅚s6lyZeG2Rߙ8j,0DRÊX~$?(_`}/2DMyY_ tH4f*/1% VͬyVyːD18F9;@?DŽl.b<@>ЀG<97CpQ^vVSBKQIOʧIr0%v!8#u;lIv8=ZM ?j*8Ϣ-W2ʈP-[ Ѥ..j**䤝\w{MI!;Dei+fd O@ W0@6`~vW S-{E $ U7bpJRXY^C}y ޸EwtqfQfRI̖wGE-Qn˛L 'Ar,!qcBET6@]w|biD %O})`Ʀsȡ)ou g~mX2$Ę( R5>b0НǻYgY'ȄB1̫FNUّEí1[9)frXԥl*JƓ㛗<;O -fT@Iϕ+1}eդqnj ՉdlO-^,gEi*kJX}Cc4!p BM7HAL>z_ţĖp {|[׃vb"ĺF&f2ǪdC@(Fv#)dpQXnnu*[^i9Cj 0"'Ѐ"0I5ݍxD" ?f mfWEU>gV3Ƚnfz% EkdOuyko:X y7dS 2%ˢdK9}ͯrmXSXs"xN{š鮗geagjؘo&Qx+Skcyoy֏LzTj&i*w_JQq[*c?#/@(>*) u*/AdD')$C&`C7z*z8sƙKs9Gɹ{o[\hc0K\*]gi{8&nU/|t$oQ8?.vq&`$j NJ~`\TxC *_N AI&S]xh-M޴a.v-s6aV?S1_.nTcJ(X8W-RR{{>C!7-AF{ƥ,i2;G_5ı=xrai!U@<sZ=}@(״]gC*J ~YmY2T[ԣYcEZ%^6z^g{fZ%{Ә l&ya7*j8: l/$АV-, PMC牺F.bTKʓ' DuđNgȁNs4]I;S(.|v˳)HB/a*bEL*A B a}/L7~l50X%T;zeOo8"Mc(BLRnT 1-YôzG>zZcS?X*j:>˳yF X R c'V+1:'Z$mvi,&k,vߥy\ tσPUq8桸|'D7䙫eXOn @҄J_t}H7䙑>,=B3>-ܲ,>絹/`>&)Hק1y1NE[{1BzGȘAc \kfQR E.y/4gYbD?5} To6 "ٶmsO.!am{9pPZE2& &Kv&B9!ވޙdB~8rMn ;`D+L%)ǣJ ,=ub7v8ePZ=*O7`g:vK =.;]y> i'wI%tϭKx44å:TN,OڄA?(j8߀t8%ȷ$$.XDbX:Ӧ4f1򕗒 rtqN^g () Q-յLeWXAKZ~0i, r(Q/пn!LJHOd:PFJ@}!,aD²}Qր4_#{5~:Bp⽎1uI5TZQĭNa叶H0$viʑ &zX˨bt;% e~(Agc:(~n]~)\ Μ/Lx#,61gHC`4tF` 346`a14#>~no0py`!?=ISzW%1~ĞkQ.1kPp(Z1D9j|kE%߀~.K%Xo{ȳ=̽>Fږ2N=M ¸ʱUq'VxLI̿1&{Q6KSG3&B"VntX4BO)h<,=-JI95u6,&KPzW 뱻{ zӭzKuyL@ԩx{3qz*"廙$38w\DEP5)\=z_\w}ezbJL[+{1hקJWpyV9n{oRDg ft-al0V˭[1dzm[B@캞rp[Zszݟ9ߍqW$j[L5!a# JrIw#Ǘ=޷w[X&YZYZY-FBNYpm`rl6. Pad4Lݏ*ht{f2&0O1'["*ޖ+ˇ4|-rǠ%RڒYу ȑuqѩ Rm5VNxƲHkI+2$`f`ֈrCn2kQ#o`!?'T9{0cKBz7v.$* -澖 BYd:R J5,#ؙL5!WiGŤ8\Q =KʼnQ:Z O/x;hɩ̪ Zۇ MOdrg`U70u! ͔A2\J1ГUgL6b~:a+BW4dQ@T1d*` ΐu?w5e^7߫Yf ﰿ9¸Q*>j+?+X-mc4kjTӚ(azZ=92K )Z_uK3?\e !P薃w{ϸzrqS{W59lwN]o"qKy CvxF6ẌTY 5{<g4 ;B xG!Ө 6в㇔^:('VPu|x%RMs},4jE5 ae>@uuM04li[FSKi&ѴϼhPp<_W2C fZ^57wֿ`v ) N=gm"+*]5Ӟ.eHEwA&& ncXeT8!!c$:KYmexH +_P_{WgG3@;NpGV8`L#\IZ~xC! ckG0j Gh52`݌wNat9s\=ًT G0(wl@Q(,/Emw}"9n"(OHRx) iCX?*m`EwQ53VxY2F⿾SN'ҊJbx:sv7HյKM~?hG W[Irf_qX[᠆BΘ>OşL5Rs=gr&5ƏUFr0o#UÒȆw$޺]oңH<)9ՒY"Hܑ#h$i|{ާ,3`<e8Jb]O ]T$`zh 3V{8aknV{FNjA4~mqCs J cߩD#|g#jS@"~7UǕ\y{ӪmCȼ j?n=aS/z gC/t;k*3q< IO#1e3.( KwHY{i&UJۿˋBLgeLS([ nn读P 9ٔm[ ,vWGYևGHbgmwc^4E> X#̑ %isJ4DЦ->Lx <&^l!hPsA"t[zb|Dɘ f+PD3cףmЍmD'}L`XOg_{Fl0־* 3/A"#,o!_x1ڮ̈Y ,T A@%_R֟SmG`>J_O'3QQ0)*dټ:k?-ZernB\ĵ1?ߔzA9׼:eRq~*S:AXʚ$yU%rH< ሂ-m*Xؕ~ȇ g@:}hlvI'&cﶬn|w]H/1wc\qrS0 :6܄`ű46i΋AvnfP-Φ /p<ӧ7My‰a0BAsL; a-R am2U SLSzT˳ddP"F28sV#ЙX#]yw"qb+.SBS-~\jDi{1"7)nAN> F2JE٬N”n'mxttlrJyFz#xU2-;2<;~q ЧCM"]9vVf*;>c*0M9*zԓߣ1$9#5H1>'2N?6;'u@@$|Ñ 7NB* գµܼf)܇ 6Ĕ. :3%cKT?)`$&0z?ϒ!Rca]?5򹞦B{K`ϫ U- ՛!.H4u,+GꥷMAt<ِGJCZ/o7n!+=ZL<*6: )*r'}&>#. N>_Ugʦq%g\5@8&+wgZkGOAd +Go{48ngFY`G1pr(|VܡnJnuG[ev_*k&26r]ʒ w;: ]*ja^ yHQHTk)f- Ҭ:dZ) ]@YC { zZC`!_7fCG;wkde{&6-1NE~2< ͹dJjwb(xVI!|b>+c!ÝPgdBݚPn^zVMdVڰ@"&aqF1TX& DYOz1fN_ҀuYgI!^Cx(j!vpElW$ );1\tuBIS9^C 1KȪPЙB_J~cI\^YxxvA:[ J> 2Uд뼜P{z 7Wza"\W$hjcHi; O)a{AnoaL,諺PX1x1^:&gl*n>q,$ON9+rۓX75h["4qcMpvF` N (:5AHI䎃+|o\B&BMAd  xfYP>ϟg}ˬ#ޘgD[ Ht>P2tpj>J* 00 (9^Ȁ#2$LZYPo܍4e,ȋݤ:W&_чYuZ'kҔcǻ Sb?ՑĨ:5 Y,yr'u=q\ns&LSYtYjiZ1IW m=BO 6iZ pFvtvvvaY' ծIOxS875"jxgWM0Le^qtMH*beVBs2SVdH8T!~O7:w)K.q@ApKx\gR=S Eg㩄r+SAI&S<3Z]#LC;w'J? MAv@xuq_53zĈy'ݪD(hWTF?ϏYڳ:zoD.F'>~ݤ4hv;Ϙ9Ș홤J'3@4,;w'lCO,_ήkk~n"]x\)n#nft ؛"2 9"^f"b6E6͌tqΗX.蚡.*H2SUYȒ&uq<+Vsf2 Ope~DFI5f[=¼DkXw"R_am ?dZTu,:37qX= fX~3æ9h)اjۮ+s.D_'͑qmn'pN۴^к 9[,wv=jk96`L+dAHꦠvr/amcq?@ C鹡( VW(zE)fnOyu<#_{_ؗ'6^ oGZ,k*򞿝T\+/#SO31砸%_>RρPE,oAAqL֩=*01֊V1BY7fE{ۉ#$mvֹ'2)|h=CWY IІpE١b1~0+vczv^)PUyvA 裾X'L,'BvƨŰU9Nzg2C褡BJc0Ļh;CASԞ_A|/%˪K(689 K ]S~:EU#mE5Cү1ZE<]):kuXj^9QY D14.J疕߱'Br,&Q젼 i{"#r(P(X-B_,GtmJzcuCpHdRryHaky has¢) [u Oƒ{XtZ`XF-o6 %S5Pd1 En9L#⌒:t$*pD4֑-NzDv+a=+O[L_-v>\ zƂ's;zPb&0[AH`BdGF<ƙ!%#*03]meɸEp kُKzXs}{!XzΒ_G.FaG#C _NKϦ]wHj1u'1*S!reR_x $[a$HFk2̋x,>NEQ0zV/8@ARJاd[W5Zk@*>~ rZNnxV$1Cx(s*Tf$>x:ݷ7Ur Aג/m`ն) ]EuzDC{ԈRw<4);!eh}!k:naU*+I_ca`>Y86}{2#1E5jAf*R.H Qj<'N7*gRާ*@i'j-Ժ4dCI7tDJIRAAEB@^őჱ8)m]AQLP-󣔹T$.  *ZsdH/ B 3P|.H'.9DkCA}&Sh.wH}4"Χ?gݳ*I~bݡ4YR`peCyn˸oE~ҖTO)aJ!CTGc3o+Do=A@=H0X8E)wD()'>&gFRP&nCЫr]oOKxs=̟wWZfӎi׉_As ycsl[(H%xf`ƭ[cSN)&9LQMg3lr4q-@X}̋X&v"if7}:Xy !f)1'#lS̅('џZR;bݚlK~q/RљQ7I?ۃEb^T#N-{mOU!VkJx+9:-.Ms:fTGni.ds6~!xTqo6mȟVђ5\bw4Y B(^WU3rtA*1*4vD0"fW]b"8* }Nի"d8mP9׮6D| jM?'cdxsMFES۽KkN.ܵY(}f+4u-'BXw2qrnW+DZ IMEl*WX7o6,UU#ĞuW<2-m]yM1 :uNVS4qf0h&%D b'-3b nm/YpJoh$uB<K7e.-hQ8;/CO{R! Y 4Jp-uVEu >6 h RsN=)G }!hmYb+6or#N%0uz|7K [Mg?Y'"jm8-w/Bvܕh,Sz )gH3wOs}Ӯn"Zeˡӓ@ 1w^[( ,3ìoMdXh }b-ʞw\W8`/ŷF鎸Zydp{Dohٻr{D.(t Bmr%L+v['a,wa#f"nܱk%Vuthmx#2KWM96^oG9 [<Obn:V#Mk|v" # yԬ'-5V.A^C@%<_χ\ (Dq,ih#w/ILU wva)ѱ)RtBWYÄ39mKtEj䛉^ۧ h{ /co|)C)nG6(& ?.fWE΁Dӥ:BrZ((e?SM~V$:b{( #jKǍ7|8J%紈|&jfwF܉="43瀓DWX_~_gy[2&WAWIaJ0>][GSPљUTGNC1%ތ\?:ZPho r~{''-_/ʐwnpݴpdOY6 ey$k;Lag0p 9'R+U¼#B5Q%Dm#ȶih)pͺeFvִIT?i9ƒǔ׽2#hVk'PL@TҸ2P#+M Ir?w ς>䘆<ܩd\R;p:1@ɰm A:-' iWtۓu`d[ap 6 1h;=P ?oƩ(U|q#+غr뎙b0leω)987wN,ӂcV.Ih~҅P弲^^wͺ`Iٚ ;ʦZL INKy#׋AZ7$R89 ,A0d1RF- +aQ8y-/ qP:F'Y8<2%*!`bcA׽]-' fd%#BJٸY%q ěO55\uէԼcCMYוcLe/tx[K" Jsjulc㷀 3}K^m<5c )/%s`CpJ뽵^Tq\.ڙUGWn֤CoJ#X8AMx4x^VֹoNx7ha- ѫN\% xii ˤ~7|h[=uwi0q/<h@I)^UgLMK>Ł7xxE`co'3>hfi~+Jb% onrށ2%]/*_+ F&>z-Pwibn@䨇ˑKVdw]q]o@PTdj {\kVM|v{l}=ZXqW)vGa,hyωPCd R:zhnY|k-PrtДYX-QĪxA}qoX2h"f8%O^x2vli 8/wu }L8l>H-1O&ϘX8m}d\Lۅ#Ɋ`qЉOOԮSLdTCGkf'RN^ʮT|V[|׌jp g49VbԐ$w  ҭ0w'a!A@ pU$X̷ ' a Ǝh9F28(BAo/0 i.Db&K4R )2 mKԟX{OP`R,Rs}ĸ FL |AwW' ɾuŏlێ|7YbQrLMv?jc>DsLJyf0VTx!? z;NgJۜKCXrfR#2߶ z'/"j}H\]}f_Ug册]9ě2(L8Z^4ҢBgW^!IC| QI/g2ѡ`o GI8U+Mjyv^Ѹo(J/})`*1ʠ m<%[\RUa@[2lAʤ~A8IA4 }c4҉_w(~ۚ{z0gv<ʷbwbInKN2xheLf |T'.(=9t3F+'Y>/(;5֪~}lmt5--JKQSДءMck1<pD5f{5ZJl\ݚr_Q1icvEoj풨G wH'^:9׮)/ޒ"+}^ۦrwA]@֙;#UU{:ol *MVήM/G.seeO {(Eq;0\GC+t=IT?VC2*}_sHҲ&ĝ׆-Hq¬m*@NJl ?X8/6| #ȸpu[__nTĔ:j8ՈAo^`)NPу9,)P1O{z@j \)Lܸxͬ3e#rw_vR| D rZ-2gC3SW3~g|q2}p4SړDr%t.w@sURdK<{+/]Vx6ו|U΋VSm}^}]MBػbۮ*ȤѢ?sbzQk( A I&Su<4v"`PgVAp7<UfdrphY=DRi ue=27^.}4m^s^,LJvrq2 $3]~ o_JH\;js^&+ER.E|WKqpT2s],m?I7X8SEvy_6¹]p/υQ(1iq֣GOt4U#B?# T1ǞJ]ґlc07zOExBѠ?l×NSMSԂR0t>kiS[@-Qլ<*U'U31=.ulO( Ye x=S+hq BT X\(BN;"x\z.XjA<![ 3ت˗Ğ奖,qԇ#VY~{[2S~C9uvm'h|VF}E3narrTz+:GPőoĠZt >GYZsyٙn$JЬ@ wudճ0ݮ$wDĿefgk)Wݦvٵt?G*T4z1w+J2R}z6 ;&PKnWH+ Nvp/Ӄ+?W'IK*:On4o =]C"K&,H#KbqL0}I%dBZ9n0+(!@PF,@ﯱoJxk{@п!HbTGJj'Ljٿ/JqdSU|(Yn4<0l 5G-=Lvǭ`L(KUdM2%^+JYt# r㒂sZ].( ~Fϐ.;r"%4ծj*OV?W4FߡlBN4+1O|y@b'ˬF4/X8%pPVX$(&}'MO޲+Luj9*QO?J]ocuZ=sE)\#{3ْ&rwq-kZA.Kd QTsm\D7(?i7*9a7]8p8 ;>B<*$D|ܜy)yǧ]AG1KZv/^ H%QZWU1yjwBdbZW؈鿐nrCm B Z,i$jڽI~kW9CAg]M}!k⌈2ozOB^LRX r۩8;O`CDBvk3z/c$5ީpG9浨^ȓrQg5S)׺q]UX\^cxJ'=˩&7SUvghHB7ijж[Kp~q+DV{t"hL^7/@Qi G)j0-Ey#gb#S\0+bv++3 ϱf؍.zrKadĬI7l,Cż%E{ÿUpVKWzD%AlHN襨z`3Ouz  iɢalmaF v6wuP9yfRI>ZEDi`hntO0t6 E6oιu,|$|-BW`,uj.ؽiNI _Q{oVTcOxZ?}5X'\C56;3j_l»JSXR VEZ ܂Թ-bu`JK,WB~/C@ee2\OUx;}wU =]q#, kx+^};a 2߭Z@!+F0 ]Ԕ Ӏ42> v.r+G/pJ<ŭS.¡%pe H*{zTH6n9MhPg'?`puvfϾi}@R? .\߽K+ŇIM0 AkW t xF$|q+i0Y{ƯIVh4˞G#hXךX28%aC ɤTJչ2,;pę'}3h)kMOh™kp#U9M׸P":u>8qls%7zl,;1%%Z z#*!M7DSCdw?!+wb rwBR;Wx)O+@!HclD$*aC#WDZ%'* 72&{ICJA+w(5"AKwfWCʼnFS ޭ6ETTΩzx-RIZBfwBHq@LWtȥQG<̓y!sQ(DrO0&E"1%5H! ڧ HY9x랻u2S<QDdRd/d{zQdćxpa7"u@p 0KBײx6|O#F.5I)1O}W$s::+s*`D3.UVUVU=X/P'$ }=Ub:.,Aڬ,^jDF%=@5\miB;A\^q(`=zsKxHr#tzM+@=IBӚem"! cA*I&S-ˣ#`[x ,23Fd"83[u6Nbvrgkk,N3՚a6b3Osi v.$FgMU=%(:{' r֑}`I}/0@6Xʞ]YKv[Deܶ ͌k{btsFK+mȐguIk'Ce eN]&o+%cseSk̴2~LAƤ|8ՃQvIa#q;WA(\DO׌$(n2u4>l1/88W-)ij7X ɾtP/QDB=k _#GNW'砌#9x^&9Ģй.E``y3nZ)[7Uu6 @G5Ae &9z5l+8* `lh=(Y:,&G6O8gZrDEGS=EBٱ#_YSq{uMXi Bֿ2иs9ː)A:2,gJJiC48ˆ< ù.jś{a9ac`C,1-2ma4tĒ@Poa4 #Iajp__H(/AcCybonmY{3]E$ iM0g\_ s~4 #ל+A]R@g8P:"3Q^ۓNs~:ņ/by 1JcxryOh6}P `/dnAߨOhYYX5W~kj@l0׌fg[H /ڽs0 0t?\wٝ r2/ b]C{iS[e391EOg-=3im^/g,iL%/ CN% |j&lhǢ~΋ϜKkOt("GJN^S~(qwb桽pjr6N;@Ij6W>6aS <&| /~זACJm# *+cZiޏA^=^V#Kե/Z<%p)]}5QVJu -OMdÔ!$C8RI!Gk ԅ)s>$bN}T #uaa ۋn edy>ENR4=d`Cg;x¹)<{Jc̳Ii>ޯRh1[pË_%1r9`77߹.o|0 ‹ϾAО𡟹Wc`AU`$թ[~sQ:N\}nr9q9 m:UmDL!JˡOAm빬1 s0 .$MHԮaiAJ izM_> {֦?[ }^Q,K6ȣY~22'TӁ=(CEEi+SV{5vTIB!ЈtB]#ϟUJ+nt<O~ ~n:0%PG27nmܚܕ?'<97>'CijN-}" ,P\ \ *@5pU)C1hţql${,koȇC[isŰՄ컗j USUSJuMT֞E DO~ L "6!,A))&Md"_@ʏ3 K%HjƉHi0bA4jgOiLRw -` TTZ4Q*<Ձ X(܉SPu]&%yY;}'CVi>iyR,6l=s AKI&Sx4U;s(̱eTG!lu`~_džM!Ir81toaNUcM[<,NDH vi}5 ߡD߯#X65MLuR2㬤 IK6${) mzܺlx}36'FS㟼roϏfflKqNO(7RՙXqR!O*`pYP Y:܍#j}`'(e7 Y+\DP]P,>N&`eA'*qC_ZQ#l&*'Ix|=t+jA ;;1B-T fOYJuƌj vlc!g81d.8\'_P0șbȑUdV.ni$(|V؁Fv/*F'ڳ\}zz51ue۶H `_20VC9qoM~'5.+#_stFy) 5 \ i'/VnBXrӱZ+P cOLZ߾'D/+ Zgtt|9~y.GۚhwJp֧$[e6G5%k |z?@ZedUaPM3X~j6JbM\g&؝`x|vɒޫSeh 8I<:b>㡑OuY.{ἲ<]B["v h`+Zy5٠w{vDj~@FAXX[&3uAQzJaSRAVfG=㝀?W-H se{ jeи|@޷"~Z'n~v~ʕF>.O[*F;< }B>_#w4pkݒZfN8j!=;2"z.gmL[ں2¦Xr͋D( Ckv.C U8o)O%JWm' bNjARC~,+KqH==Mg \М`e=dOW7`|̣yujNHWVQIHFD! :O5 OZ(qcSZy,^fM'ũ#&$UvJ~ Yj}7MO +20 qWno=.GNvwve"l+v؝4=+b6f 2P\?$YBbð> #{"0y0sh;ܬfkl W,zXy@ߤ+Og8C D//7 \9nW,kzjT R,*(A-cu'wwfLW[mpo#&Y#Z>S2[k&Vlvn9a ޕb&OhEC-s0jsdpQ+{S.&'!py{reNA^5ܪV4*SBؼh&#[*vAywE¼Alȉ&z*p'B"'Ne6dqnksX)!"$PAN uݍv*mN{@p2Js}rFN\6f@r4*[.X v-9= @$!Эfw" ֓:WBZkc4q/o!{ZXAyPۭjwΒpwi(MCŀ~<=h/AܸESLk!i׽®t&0Te7P)t4A';z2Ɋ+vD1*dsslEgV)fJ?qo&bU0I2 #5$64hoQt.YVyt3$*Bm! <&!*|VB / S4<$f Xztf]<,x%=Q遃Ymsb~U<N=ӯg}NNjФ*#hت&NM}MFeA =7H5 l9+¨m<HM5 -OG.@PU关m[8BfP$V[p.%r6QF ջl)I%3Mws\gB0gFtj*(^ĝݢcURu TRFdHRwk#Ԡ4ND f``ť;^($x}3*g𵾧~3M>JfHƠeNKxM}K5< ܓdKޓ] B)7k[XN ˈnIu0g_qzayX,K{BWi1! 6H`*WQƇxn5Y4 NadWNطrV>+(oI/IIcq_6mᬄ1r&#g"~؛'SsZ8xݥ z]sT(H6w 7t<V7xgrcӚ^,-yLzصp6ڀ3oH\*[o+>lu:FuaVcE^EeGȷV T챖O*>T:aOGJoX#ZNc8 z\(>0XOW͕>E8hc Cg"vQpN4닑#8x,VoF \yJW HɃ3zO+i0&B{d84BSd@q)X'^Dϯ3Ҟg߾$ -{N^.^0 뀇HwrNIت\ωfy1mgo"8\κ Ṁn (#KcgsxrܶP5hxcB~- 5ڶlW=>#IKIƋc',o Jbepy[vKWO)BΪ,ق"1d =nOU)C|DL)Pxz/FM):?Jt# 3eCӽHp{%\3"&ПT,aÒ@{}oBqF9\_~#OѾeuH H|Uew>;%y:=5 mcf|ȺK'wF4-^6 taߒHZP^/6иS ?6V;Ñqx/[$=l t;.D5䭃{D58T[4@7LE_tn86^|ŒC 1 d0Ut=ˎHK@ĸf#<0N`eMEfD'./ ﳪ}r:ne:}\/$wgm/z>-#|E9BZEzSJD)q)swLp$c|~)F-)M?uP]rVt^㑂{ ^_r|L3" PTYfEb"oFqnێ$67ek7?)7}՜iz&mz[<Ǽ}lV'k7}^Z Qw;sH f[FFPDGUF;UN~]oN)0fbdzg CCe'8. '҈k5 Ǒ-r z mz$M}}^莢 |9qDoرVp8~^f3J1.,O=E: o77G+ ; <:Y](Bxb mfxj@@t""h9PV\XXIe*0HP' sD a%>u.T4bYg,E pfA%uAU}qFqΙ [>iT* !8˫dLr8}=u}UE_ Ƣ1 N} 2Άs42MPCH{LN8R>V>KHkx599NE˖jN\fZ=nYeҩy,}HD>VC-LjDIDI0)[:=5` ! nAaIҴC$7杛$~7m>!+@yK+M[@s& !qgZ ĩ=k|-FOIOc BQМ'=L=F8 AI&S8@Fo@8 gF< ZC}NN~|Jrz07մh},:K_uV0r9 픖T[;#d8A %]8IXҬб;Ԫ&p -DX8?"a2b~7.d;z|#[b ZVϛ m(;cscXz wQ^5\nOӧK͒B(k]U~CGج(NsPB&>( DCzu2+\FעO% G[k+( (]9Rf/Z2p稟Z.3c.N#rbJs8"X'FF .ig&'Ј)~Q~fe7<\Hm?f2[ `@V GY6fxZ=&3} _E9r;"jzI x#JxfKR54 ͗6K~SCɕ'N=et3(Oq,{rpKj#pvWL34[=s )i2X4%փW<ǾﷅkF4Iaiv1sDS9fGMǹ C=S V-؀sEa|q./~2K5RZg ~z` BZe'eTJz[u5Bp+NR]֣] Api/Rhut;P[\QJ#Ȇ_m彏_*ՠ4G #`}.)4YkM7\bshKN73B2:®m}_ q^WH 1~4*MX) %D 9Bz5xNDߴc]׹͗?EKKlw @ <2aUtњ' '˳=E 2;U.=ª]G< H%&R3R7'yM$m@m阐j$pV*dUQY?'8o1G5WԜ'qۖ@hCǖB 7 ΍Ⱥ^\:.[ʄ]PU]"z* O2Qj E!|(WH\Tڎ|V{l~@- egY[YC4ǟ!5hXOVu=`Sp 04w(މBPʅ6jMp_~|fVa!7Vi҃YϨ˔t|)ZM ;9.f~q[dɡNX}dT\R^2|i MPB{ULpwPX?u&G-=esԸ`ww j o=`;uGX1j!'w_AWAhf2hZu#BD: WmG@/6*_c? qsr/faU@:$8_\ cla˃)n ~:$Iz\acoգPgPrsفBk(0 | Qka,4-oH:Os(0"M\MȄưj(S`|_9ú^ΰ<(A+& 7G , obg81qLov }RW&{xE{@s[*0׏(ۨXkuACw;G6־ԭT 1Xit]hSh cU}N+{~#Dg lq pݧYI))QQ} *E%.|)4=Kl@ogs-s+)SďK;oO8$<=S+lP84v.1{%d MU> Cyy 槪m^#o=R(7oG%!"4Jg4_d2E)`[\If W s8MK&h9!2}%@X ֽ`؈uީMddʵV7g,F՚rɝA76j ,p~.?V{>eE=3S>6AA S.,ߘ%í(hQcdzQ ;* #W cAaws'3KDWgig߀G=CĤ€UP1嫳zXc AI&S8@/xMS]u.zk -KZ0A\.܌%j蜟bQ:~JǍiAkj %z 'RH*u|j/VK)Rwm9 ;1/fOwL0!ʛp >^4/JNҍo9`)Ule:Eǹ'm;ګPZL)G Y"pD(̆l "Upl?r~Zv4_8NWM(?]a솈"LD.>4;e⋵}|)wMF ʏS!AF wĐ+-8ٿLC6fe3e쵩b7 &o XC צGb?] +КR}z fU1(IQkjNY m/<h3+U!JaK}Z4nҐBqXkhOE8$5o*#KCLw>P{Lp?zQR|&|Q [ea-š\f;"7n\oN܍nNP(3MnyCk^WGBfvQH ? R)x7hӫqSW+(۠=b ;: %l_jfG!u1:t_-GiP^D (-7&g=t}"/)GRJJ:וQڞս3r%=DUH \lna uԣ1֬(Y}%am(.@ش G; N&/pԵ))/GظPǃ2nj] cJaF5"N&M#ʧp(~km3$a +bPe ~DA8 5y(l0 ϶Pڛ,n>0w4|lⳚu~IlWM1>Hy{iALods㙩o[qݷ-FXwV ͨV+YUj]*赳H*V.>0>լX.nk9ODN%=تmtI<`7+av@Z1v>鮤L@3,cHr-׃t(au^ROD$!jݭO]f# MHw\SsUEU6>X_EzycqfbX9v;SI?w5m7/@A.X BOXb>E'acw fZޚiG[ j{reCc/#/Ie?R weֱ埼 k1s#]mGnY ~5〙2 b8aXkq_ׇm'qʌDMS$Mb>d8еI${PepL2 Gf}{}gJ#f~=?3?S$\d 4[?avyQN[ȲHBlF1d%8+XkH%Am/{G@QHʀvAjS #8t`$xNVb>9fw'*v ơWG6 +x`W7㜽L-O6M7ztW%JzxԷ,קEg.up+zN sѸ5aM6# 2.ԙLi穛.mBtD cGD4MXn۠Lc??1IǛpv9P#Zp2T%\@(; VIf5!q?m:lj·ofQ %Y Bk-`:deXh,ܲ3 BKڳ\y#mc:Ro>iFD9>Y<ͦGyͯ b7|9fµ<ժ۵LV~8 {@OkvXIo했?LBBpjUi?t`q"T? B $eWi&bz2d ?>/5C2$ud6ߴU/N ’"9{TSٌs+1 x^o2r\87Ĥh4V5f'G$tX-jQGYkP&[Z/37S?%A5 {}8nSKSK)J5\d>]'$W:\_oov%86`0S[MS:[3d 5iy;l )ˡ ɴtf%tv 3EI y<̘F9<1x ~6>n%dq&~ "z6:ڒPPF!'Ĕ8hr a? _cVMTc^=GD'9ӝ|5 g"y`4$r <U6 PבbtA]6JL 骒rfv*SPgh;I[tAɶ&#õꍽH:O1}EIQ]m+SVYtElhHDZ1] }>vDMf yeU[; BO(2;f*IuA'n(K}J\$״N] y@+?qcg ӳ'EQ`cޝw"% Tnoh`݈PmkpvV]sYB~ 8'bzW$cEQwUt6k[Hѽ|#MqjlLqV5CUNd2ony088+@Qlh<&}]]F0/:/Ex&lÜ\DmԎO*"ųVsGemPM?olgw|:uH ëMI}Խ.1](*>tOV͋+^e6]|>pkP8y'qⴂdu䐺օ60 UZk Ж-eXG7f`Lp pᆲ~f\#Kڼ|s˩s:޾_9QYִ*GjqCpў.s6FG_  >/y눳L+X.^| E; 9Yq(>.R y&b :lắ A,uΚ:hug6YV.עQhq2[N: MV ă[Ѱ)Q{/ pLiPFa\p\៖B*/ѣ/rKR,|QTX$`6Y좙:sڊ#B6b7lE, GO+pI\1%#w{-y0 G6|nYr5qW~ftY'u 2+"3OM|g[Ii]Lx(I'):qǁcL.GNa\Pm4ΟW?V+!,~#4bzy {!oMO\3{Mgi=E߾O#)#EB./OF:,bGM1^ n Yc[6We !%;~C#`JK)adP[Bq^־炵ocI}H)Y@̕qշEB_ZEX/1vfW&_Th7{FW.l' ċR I >v _pڰA=|5|xx\ _(4 z)$φڣ7qcKQMB+C|6ql$I `rF~]-޻ d5%g4O|F|M03+f^\%W`<"bcKhv?40]\;n>aJ&oc!D+,I(01pdL2R94ftn11:Kšv,?`}9[#bncy˾+\ 7 `)'B+4: !t* LJld%-H 4Xfojs:`ꔹN8ĊG(#m [ ":i@V" <.(*(s3I-%͗m/[HA>&NFxs>D[sUl*dWPgөEf1 aR]{hWY 6%)J@?pyVCPDu$n!/F20 v* }~^|aqKb+-{:]ݝR~H\MRȇ]W7  MئH3 [%DPm)S2?:+״,BEm1+s24ç?P&@&o.Nfu{(>g܋E8qA#n88=j|J |N!͐9<@9~P@HRprF y1[Ⱥq{@uopWeؐݩ;Ry, .MG'n0Klu=+oŲPIᆏd1'BRfN^:[f%iN_}Z'gz,  9n).T`.$<4s@c p[6]m_E`rսw}߉O,fڒQ9%2 vn0'Y:w tkYm ;Y%bHH{Ih#_@(G;u6=.W6qHʺCmmʂz2~WfwlHSB[V5ؒ((̴٪#ŝh, nfMDkiq+$zX×p^tAݢ'Pc@=X}6, ?t9D&@xĽ]CCS Sٖ6ks|>G:ET'rP?AWb\DF?s/LDO)n\Le3Lb<#Wn7e{T){iCpl'5 mQ3 }1,gĮ`̳Did#xtEr TTPP Pa҂p8X\OP6䥎eTz+$޺rQU >{/sC$QɫDp\KǶ%^-a8ɏ _ )aw-jCq:> x ^6:)\!9%=?[k?GP:O%n柀Q0˹58J^SϠu+<>s1~>A`-\EPRTED[XRs̭\Yk]yf ]1T%\9! k1_d NIh gwzؾ%\4_WjR+ܘ1T4y|~N%l "̹ro[;UW -հ!#u[KNS&IWhP)`7!5nYkjY[>ul'%}=ᙛPLr͐}n7Wb4?w+c%2/#E8h/'f9M+]> QBgw)|9$ӫ8PXm8Tl!9}3s舜uΌqCc8 3"x9F瑓R%_כIbJSadLs [/xkGrfTjޣ`!q.kW]q`>x]{pۧF .3N#M2vv P@ qЭ>d!cbܗ$ #dEXg'ͥsp"HJܝJ $`Ś3H2ݎEl XP/֞^Uꋣy/YqOCfч.wza374m\W^C˞RoDo- G/]ψ,5 u&ڝ~c-,|ǂ&0X9D=RH%ـOe=M'V5CiVl6"MlQ*4Ӽ'C"N d'jLC^>%lgW9rؚrHRdoh~Fc#In\~BEDOQW7sŊj-6ootC:KX*;qO&5^<{NNxޱ7{VAd+gI[Og׻)#g7gpr^L+]T6Ok/^=ϨύZ ֌AN$ W2nv\h̠>9­Ux8*](W.(XPuu9;W+9 uT&U:4QQ 6/_UM4ر/G5H!EC+ޏk!Nnbے%LLylM۸BaH  v:@([0 9:wx, V&Y7Mv+zA)h?9G>e 1jBNgӇ$! 5QQs]Yނf/Eu[τL5}:\8{L)0ʜ@,#I wkI Uy_/ڒRqTr$tzCũ,aO3+yDz"f%J7WGi)c`BRb A5 "X,C)^e*kymi;}dc " 54G7_R<.pG;RysK14^H~VkFM4TwH VQ~S 7tZmag6\_S~!Bt˼] }{[I6PglHmT.s@րH 4d6bvJTh]c%DJgq@EF-|9ij{7g7ӁB(TZo/#A'*믿$/]ebTPi"]A,p;_>+r*Ct}9\1j1ዹô6p;HKZl*@Hn0 PB׉_ỳ@B;Sո l+NB2b_a)p5bcaK"R\} r5k( Ho˃*VmK} :jD۰{w5ҦIq_:7#f+ۘ>j8"ⲇ<8`Gш1 7C~!Um$xyfe5I0>h(*,z'2$Ao4%˘GJ]I%g_#4_4S[Ei7-U{i_ taLJ?T~MV7XkKG2K3qBR b$ͼ*׿1=.$r.{=!ɑFT{(bJ.$¥fIųkodrG?RK9Α#x`K+Q3%_Lp b4[ o$`$u5Y`V%}鍆f?<S82dz6ӂ*9 +V^kиʸޫ1Y}V A~Ros'HG';?.'ƿJ kΦIo"Ϣ{Hg&;n!VVQ ٗ'zE{4=YvLZ9Hi)3E 1ZezV͵* r;ٌ.Q:յRI\z_}B,(,vчGlFv" Аt/,TTQ8 &lfG zlB "{mxpah|\X>@#PICǧx ѥ- a'ßtUy_. o夰 ,ļ{B&֤9 ^Z7=D.K-ns(l}t$Q[;CT XE%49if;s^NʚI]Xup6F̓?RN[g-[i/?WpK#D\-iHbئ!8Of:NtJMmR? %KwU$}VLm ?pn!"]YY#d߂1N`6ݽПSOSloA}.~ 3.,aVhթZFjӿlQjmilIxo{Ў=Y̙ c㗽R݉#~@<2 WLU> B>K&bq"7ɒmw4$'k$G-CIwV;Ss|\%~_(/X*8X,7*Y.sUD(jņ0X^DMp"1TjEmlӢǃB1ߣj0X'hؖaAȪ|0)rIpߦ*zThDC Y(]gv;YLs\L c/_ ,>RߏA,Ec09eGϧ/Î=0~mJ0k|dЃKDr /_6 |.ZR{f,D5 '1תfFXz0f^#FxKNyzvRoj lY.A:N0oJ7 [7㗆 >LzaP45oѡ1mI|'hAOjW8Jz'~=7[W2ŝCğL}Dxm:1}C·:?2Pu)&^VxbS՜~@%uU<*@*^&J|';ڔa#뭺!ejͨ2}_CF%21.U[U!lO}ڧeT e/T}9=1A.!Q#." bU%ptiųv, kcf5VȜ1{ѓ5w4,,%*39CuJ #|bTJt6Ebc /@Ouy /t7`4;\͋ >7i*0yOաtXRzH/f&W{&ju@k=2~NZ15GWrjý^st'wǮx07[#I~= |o|_g/.|o5GZ]bS7Z&t0gg뎽5{_Vg1kaTjO\34%{{f*)5ӎݶu`"uk.A2^>X5Zr"$LBOܱ%^< 3LpkÅ[ ;A3I&Sj@s)A B7bym]ύɞǙFZVhyگHT| Zڜ %[efѶ&G^/헛@[s\9=d.灑*ӝ\ϊ%$~,K7(eE8RIa]D3 mh'- CPRJ="eI ]shC aqj ssNE`>MPM Ģ&ƾG:,=\a,'*5G[_8p ח@8`ZMo@ZK}MX26ݱ#m,+Z'?*|Ȣh:3[M<쐗jj*r*x}gQ5f~\jPBr9'[k:#"~ yEW;nj=^FOG`ϏY`r=UΣa[N"uRAPe!huN.6u7g 2} AU0յWI/i;j6D;3M|*Y&1Ka;mx#|2sYAHt %khNb(oh4 XTyz~mtk "ד˚n~ضcLԲ:( Gl.قm9m2ya~:wQҒۛO&̲'沉e@,i̩fcSeflN>Αs{ptIK i-TxWh#l`ŗ/i6]kd6pEfԷX;iM C4 TuiO%%yIM]@9R+f۔E {`y{1 |~!SMy1ˢӪܥْ, a ƟQ]1N\ݫW`P>aBB935)Iau5g)>d{b\ANmd|lav~C8f}hB&M53>)O>ު3n2P[(""4F%W6` Fgܖ;e#[i)YV"l Y8%%QED_^\?c=;S)^gz/SD[HajR%TuA>ӟqp?T~+ama}| :1@5rx,!vk$u;B_j{ȹoB :uz'a$vL l0^[ryELښs+BLXTۢ@^x|%VO|wQzN/2 =擠uqD3-m`5aiZvn l/e& h@"]RoMj Lw;ͥȼe7|CS#v|1\ozgfQ4Qs6[T͖-:;Ptg_%;/$g'S:=TP~>w-v,y4=Uo#܇._ 엯JIPA][' +?lJdz!j~ķH(yq(U,,0ҽ[ӎų_c Ocfc)w^CbMo3YO'0oCB@:گc@{͌R.,۸;%hG= kQT)\GEٷ$c7V1+SχګsH#ˏ!glMQvY^&@-SC(M0IVW˜n֭¾58fH't \{ADNEșB*;k s30s%իQ;b'_"JWwaqGȧfd!|ֿ~Z&nYW?teǝ0=抳$OvXB$$lj[֛ C+OZW;L-NJ^̆sкCr8oGΰhE5ךNl  px"cD0?mǭwm@,_UNB,=`3+9!i.m]\SDƶuAٸ7+bc@dqTEi;RNέӕџY:u]7&e69I|!pú=EU|UK^d ;0M9_}]'q+ڇ!(fq^)'y_nJ҈6Y#hй%V1vkZ*pg@xɪC5c8^*D`8`8,Iu )+B4 59҂Ҭ *q%g4)KN@ 𸁤Q2'\%Ն a@Zv(﹘˨֞|4?jF*5lȦ4H}{-W$ISBY[GCaa2 cJ6|0^[ߘܛh 8V3gF"+wF </2wYQ}FJA$ЎoV"ǀuY,PNpP ßWho$Q}b b0C,N- t}0Sx);z. ШiNV(emh^NvlYcn9A]5: uUo#iv/iX7OuGApg"dS+,b;Om4ďt"qA .CKG$ɀoxɬ`@D/*BcDu.Dyyf*TlCP/8E%JPa$EH,qd?0{+Y5m>Cl r\ @N3[^Z#h eՌ h5vbAJ!Ј us>OD1& ^ eM?ZHS̶VF'yQwa%mTtRꀠATI&S8@!_tC{F }~n7m)7J3c.>?̽v*4%|hXz|ixD װ:[u6 Ǥ6xA<w.5š Nf-7](rk"|7b3|3i%O@:͚Q͞Pj L@ER3%+ YU,Kv?=A'Wti.yb@ެagJEJ΍fI7{儘EnFbXudnN3/G$"3=nEX)u|]E\30$B *I;jhsL!)p a',{o$PPUpI]VE]Ch=2$ZzifSb@õʏg*34$Ϸs+HmHf ?+\٧ib)'w  )х3QZyx7X%,'} 3 GȸJ8~:FF16zݨ)$scyH񖧹h0dZrtuy yèC_zzm&FE#%2+?l]UT*WNC;zdRGDVj"+h&*`>8+]iw9RPG$zÔT4ok(:ek`DFS C$tC'l"ϸkBBXef4DUi;{?񾥢^JYA Q#ҐAR{u h*7ҟw|;\tI7uѭۦl%r0ձaf4P<4pЦMb["YX+WZsb/`ubD(/8N yz)!2(R oN1 _+bZ<74^~DQC+g$P%[N{+aJĂFQO!v w͙c—,)I䑟S1_p'K הTsboa198娴Nҷ|G0S}st. tBɑ.lQӴR)0u\wwnMϴLѷ/7zÜ?&m:$wT 5qtSlRC ||~rz ?#!5]WcbDyIoH ăEѕnh{_ ZoI!]y.ʼnK$dqٟ#?3+_e!^@W"hIhAZDwr[2-8m`H*;'5XUg.Z$"[\gฉj"A#Ms= =Qkh yU'T~+Kx}'99'ΪV:èMcG0Ը~bX .C+/hk:M6,Ж%;S0l--9Eo :h7-Tʂ/LV4:~*/?3itKDt;MsL;19 h:d8EMBO-VZR˺ /{5+_ a<&# .w:]N{T>פ!`l}2Q|MMy٩UQDZH.A\o r?q5a%6AkT{V;[2N]3B,boXk'Y, ^<?*R'j@OCw *IxFMOT]hޚhQ̖d; c|> %Lca}iu;ILJ#=ꭲ&*zy'Ĭ|ZotT缼}+Hn&+HJ:knVH=7aHBx{~k%`r#~>A'cERVs^'5S.@X DzD #, ;XcuZCgbλSLTk%FO}x4pTů\\6v eM%@y`p¸jQ -R4RYd6!"I1kUPz_ɃJ Ͽ1NIAVֿW{ Kcxhln՞aZ[^rS&O2\oO@Ʃx%E@˜݇Γ QD? p\X7 o8A{," šU<->*.\+~7IlRXTSŰ( .3?6,8iVa,^ 8`e uWv\rp&-#Lp>s9Qf&)E|Aiw6$u3QnC~O[ٔ'g.dGnP98֜RVf927ʍOcYC9)\ݖ ]RX!bQc[EJK?rsБmQNרL #lvwXMVAMr#!N.& p'{8:i7ބNe*-tZvωX)a' L*C#=*[~}mx%4/C]~O;\avз6>(C xy0~| t3?E *ap# z7JM@sBoBͪS͝347>Ue|/ %iPOiݘPasapvx7uʑ]/҄iLq [2xfDmr92q@.1ݤ^884 r c ȃ(ڢBE ~jFʳ!cEȴ{/VlCSP'M;k`35(tJ S&d])c*5eړoVrןl+FK|`os1}5cbjrC-(yeJW']pfИrA\]U{%W|]iǑ2#݄F.78fo5l/qEAJ~|Ѧu ΎFbA\!S_'ݘz#_{THX] 񶻰90⪖. 478yĩuoy-`#3 X4BNr$FP_1DvRGտ_?y.qAȉCQg=1"#4#Hli /( d5"@֐3<=ZqۓK 95uCCbƆg4pUaCFyZMiތ!SXDXY^jީ'>~}kLU|!?,@^~%(bNEra?C sr{F>P K+KPTMt9J]Z2]g"UQ-˷ss?ïF[i;z㦇%IESB&|/ G1hmki`b0ybAΈ2F(?GM'ڹ@\gx6M龧 k74S;# .pt3`\~k~;.U* `b Sgewy1?/rX% =sozjX\9\/vҨ^lQN;qG%6Õf^ X[Ymf7T#hNFu;=<y"tW,__Vv\oRoIWM6`h~xHV/y#;MHҸ^ V>v=;bT-1AG7pLvb +2}yunt~ᢶO)0Np9hws R>vtо$Z:9=[N@uI8ZQ]I|o,\\ u}~SPf;m R@Fo<+ _OQ *gf|T2X0gRz;d >a5Vpi>YH|@$;9tW5EF+ޞ2ǸQ} Wg^N_3,'1dG!$'ialwM_VɳkhYLNoqn?|C#Ѐp,L,7Qr$Z^IŀLU96 ( rwo>YŧkGJMpL#j飥d޺k;

'b ͐E회4mg-2쥀̿N`\Y2u“9qLT8(|ŮrҒęnc9fJ5 X 'rV^QkJ= ;V`5\1F:]?UA3y=3/~!VBrluJE<{FΞwaRF6N RwM`~dދĆ\tdC.]Ofo\`.}.`PΚ$U`P#!=^eLN=O7Iw ))dz%;D0iipy_h<[9sr:V9UMtҫJZYH-WzDw/gpࢶ)<12X)s\ɺN"=pRg e9l x06U(!zZCPHNj/8se~dB:k=d<2VS9a3abZx\j.D$.c<=`<"t1!qg0UpӴk.n2#w?S5PR-O ci%qa1BYKlOԜ:W,f[qm}kTTV2-HKRoR)<%ts 19:Ƹ)l$"d!G 1 K݈ d2zk@AX (`JȰ sFx dJl]y?meퟆr~]V| {go/cPj,[sd>.kD8AƧK >"Y6i'bT#n4=dHfJ}c_%TNj.)Pfwf 4,E4P 筗L|6#I _a6K ^ 7Tǧ׳3m<kmV}T~]+g1JA|W d^X fNBlMފ"#J$q4͆rC%{ʹ&pĉHӥK kJZmӘ a&p:?CNMB{o̎N7I ؀bF"!@|P{V"kŜ T%hH]xlgA)"D^Fٟi'mǪ>t;TAB>oetYVVPMC50WicjudVǿ!Zl&ĸ .G o">B+4kY00 VZ7 )BqT\U(23Gmk*⥣6ݠuPHAz)8Tl;91[<]φ%sf^4;A)wVah} ǰ"'-8a+X~ Rq~T~># ʡqD9 LxM."帩MbʲrwyJȔxxEb3Q;F۽ðu pv9!fv Gc^_0rW_l7" o!@ki ꆨ{<\򃥣&0|h*`%+To_}$ͩq< -JޕEz4W8ܮU2u~Aǰƺ/,2ϋ&[>ĎdPYy%AλxjEJQ0dGiOwhQ`B,تtZ61p6!.߿ގi 0|oՅ@h~a"HI߀c2wG{X3X~ѯZ43[\ɦK֨+ A/B\c=Q,sW"BV'gy2Znpgt]D^ja9 7mDh^\IO-%@nf@yKB J=VTERQŇ˝:uAYk|Ye䝑nrwkžp&%^Yw0{t7W㉡*bCnaPW`ebx묓`lw9u<r&Va"UbvNnTR\w,֣!w*5Pz,{߯Ιs Z}rsX?dreTFZe)"F Is&'U T_hjBȎIrʐNC k:!- ,LH srP [:% b-TMbRF{Ra*ji,: ÀH3 C]&A Leıh=?,u(566 +Il5E&6$nmNkLFvyf@lg.cFރhұ6M ,˯4e7=v4Z\6^|gV hHp[$%N;μ<A c.83HCw{9al[ \F XyUs!3h*,31&Zذ*(f۔:M*}bQg<\:1DPf"_R704aCqZ 9 SM!TR w-ݼF6?b5zmKwGu?`>D-"glu}=᫄8V^_q.9 }SL3 D';4 [czǨH؟^WJB&܈j&>M4z iNA$Ϯ}]mq~{1g2ܧH'-&{N/}()@qtB_/pQu6vhrv-5y&'.SI~,aLe%(j?.9< { ;SRIG@$diZL'2!锛-0˄7z-]_*/K<GԔfS_mnƸ+ $B7"sӴ{}.!L\ ]) ^;%ٌs:Mu~wp|o ]0$֦1|cV 6/Ƶo85`SZ:>X0ʀK!6wBc _ pykq[zJU#\bIDv{k=\iϧwa| ɓ 8jȂM>U[wpAQ{TdMdCF?WY*JS0J>иb݂ҘM~{Ny$W<:e%8چ.&fTGɑ9Gp}-"RΓjR=7Cm|V`xm&>8OޮHHs>1UGVc#yD hL;\^iTRc杆o= =#;j4]Tcy{;Lgseu*x 8bneF(bۊUq&Yb~B7(<:Pf%VҚ2|RTGFk޾m!n:q$"'"~YWkn<]0qe6XWO׍)$!XF!j݄E;]M^IxX1Z>Np[3_KMMqaT'nW )m>yky(1kt<:Ql:gʰ}s`w%VUۡZ/ͣ ]Njg65|~?tL.P͖1=Z'g{J.JaUIΦ {WJ-3S 䀛NӞW/p VX9Uov}M[egW3̠{8L*(ft<"Ym_Xˑ{#g@R7J. fզ iAGb3i@BG_ټm~Be.E ɣ|Ǧ'P1'T&١2J7 Xq< H%t ޼HW/nP.Ƕ|'PO4K OKao\CX^sySZ\\`o]ig䓀@46 3Y\QluGxcE*INyl'O>t'O~osae|%1?s=E~} 9gHsCBa~Efr#m05PƂ+gOR svqE4riK?(Zԛj|s3k'D.kc<3>fs-N)n 0sb[ XQmPQI *u"J3H^(eViP5T;VE@24bl{lxb:UHr V h° “~f`.PՀ8)>Lg64 IQ Z\һFk@wj;=br%iO[Q5z$z\ ]@ ( uJ\V烈I蔍sQ|?WUi|[FRm&-2"eHp^V$?zd% j(\kk=$nf'8w˛_:aڲ|I~pW{aѽ(8|ږb[&f0wʔx5;2BI=A]~6}S odk$)Klpw0T. 矴VSP=Wi7I8:\4J'VDgu'Ev͹p͆vv 3<7C~4^H~|JK@&( Ӟ<>EXZ\z4mHtT&]5 0VJ I@:։*$l#Ѐ-Da6n>1 "^w1RSB>{a<n/6F$_N\ =bP-e jwiI3=5҆5).!IjO5 :aKf?%b(l5.;;.Wml,k P vf'EC lP\p.Գ7d9mo<`ϰ^Og+NMԤKn;(>~-Tm@Wglo}۝\w>\f.yJBnu2<-ȑ'#Zjc.:#xk_  &x>)gf `>9ٷ=K,,4U^O$BBg0DA7R56l S4u؏| P¢Z&FAifc{{b qg[s'vhʯc"2аkedv!_/#þϕx?JL-؝\RV-'tjF H0كՎqɪ\Y;=8osV>L(5_I]);swyFʌ]1VL(B8kGKKyST3FN~S#U@"JALO:ОZn}. CF HbEA Ѷ(= ~&πAOZftEBHg;Qun_6.3b;e#`(ˎ4 +Y0ÑdhTJ7ɐXV NÕq02ʐ޶z3wTo峓S:{laFcEZo6!XvRL Z6\{"1p}WƐb5j.2 N3-D'Н(r͜IkCq ({\"X-*:m(ZҦ "{ U6X0͞J)"}}Kzhz5A}TvH2>1l-+)+G|&XҰ)F9N JE{(ӲMP)ri`F`+J)jDyTѝ_.KrhpMslV@Ӂgxj*CD>In^^g[' T 9INM0H{7wSP,sJ!8]zt@Xhm `rY~5$Ad'޵KޏtG27\J}-w9JJ 1++,ښL,cG8^jvJYˋ ʨq9OMXQ|umiVQyܷwl]IG<\tF "w-|a?'Lgo,pi?dyaI.KJNO%/ vY⏆{9N(Ddr%j[XܑQ6e%el [HL{ W ]d v"0U& +E$;lf_vgqc:9FE>d|5 pV~xPW4jʰH/}#/vݏ5[lܟҁ{ZVLe/EHݑQ6K;>f)mSELޅ :=yvYh57<ZqDX9TxRBFK|ӯA,pan^!:N' ݪ9ϳ.jل3uMOӓʑ]h]Px!\f[yO qF蹋ePJߏ8~_][#H ڙIۆL}%1A"e,%wK9׏RnM׽idwDe*_,9A5VKWr5PoA ˙`uCaT*Iv-!x!ZfffZEjlJ7j>1l&XYؾ w1",4S8OL A g<~E 8nLkf-2݄ 5uLSϑC8GapT}YAuV&(P> ]=JZ/)Gsg~5 =|K]MzU*f!qLĜ(O~}K饦9]7u^J_`sz™3,k=aqscT/ hx8Qv麴Y1\9_xdNS3VV$rqfn*/dxBIΎNEl5 5t0V zxѡڇ`.km[,ce7McChUˑL]q`q=&` "Cw|@@8E>X2JK> eIjWw6?&Ӫ# ]0Ӣr%X3{M–LGLѕ ^ i4M~ +(~W| t,S-G3jPVt i E}~:g]xnQAu/h#uY 6o?JǓ*rHJKjI)G9i4, n~flX+7uS\bT!XFyd^f򒾚`Huy+XZh>F=saۏ~U" Cʎ*aB(drkF0ލyGr !'&5hezdv em'GK6Kaz 3z G?Qy: Re. $%'- .`/5u@׮(#R֍z^!n/3U˩>mX]œzs wUzDTcSCu/}RīDtOln|sq4r1 =~k =؋ y%j6X: /~eW c(ԶG4`]9^bgd%.Ic3kAX ]׹.\`-GO2ő9A6E _MG󣱬х [1rDTˈJ:f>Jh= ݆ђ(!DWe_\^F0O}k_K (2*]Bt f{*"+PP(hQRl'1o!ۻWtk6T5-")iBj*mo#=It]Z^j΂; j((+iC)1O=Xۃ;t)&.LFDKSP&@2FE_7GcM;B TkZ HME˚m<&Yzd c}X;> tS ,CG4Е s'M]Pf4 S?8{xNAY,H~܇ye6% s}5'ZhNiI21f1PXD7gZ2OjleFJ"mQ(@~nP;1yiܮ~Ik[ׅ>{{m6 ],فdv">ZC6.(%+~̈({ X|y*VEv$9XwXyTDFT.sR&27Ąaa2}e;f˿PM |f>Eӓze8|igvOgd_J"{"($abBdZ(/m@d&NHbGj*|qY`-5##֊Z9q Vx!m[ "a|R̘ _ } ?)E֙샕\y1"`eC%< }@ UsЛ&.2(y 2H6}1sWS[|m>ԐM(ҙUyJ?yA75Ja?6ȠyA)wK<ǒ[)eÑUfn*2.;0\Κ(]M.kmDf :ALOகt:c4=j 4Bxkdwo=*i*s M+ʁ`Ђ܈y8f7i᡹=a螟<=Ka61f3FNSY0惉tZ~&kMI[%KaLbarh޵dǑg*y 5wWr2Rb(kMr-Enn{$fF3AŖٓm4O8(BB)4@gE`n<`u&]el~"gK.ΘBQz (eͬy.`>B%51 }2IF)pz tdxج VZ0[9f#qS8>KHhݡ}/+ElU\Y5s){Yci3onHSAU ׆ގJY=^*1+tS؜ %?JiFځ!v/2K LG"\S?jHޮijʊґՂAO/'K~ɘZthuZH8\0U$NM?:e?%!_BeSv!Mn1V&{M(dZe.Ƿ*a&ot˽a-*-)s7"u ȘM*ܗRF4y|^bˍu[&l1A}_ȵ,Ӡ&&64MX&qa˨3^4*թ< *c 0[ ?&Xf;1*Dˉtt]ÌQ;[%(P[b/`qy-:%R\DJjNϥE:ڇk ŖOZT43uNoa~PR1JA]B[ ,g;FAZc39D~j_v6O5s`*?P4Hbm޴yۻڸE.mhj`\Jfudt#ܯ $t$.jc"6/4Q'نX⮹}5)}c@f` }nR xݭ%k4T#lwn̠μZ9X 3e j6<;] N&h`W?ϧʗ&%>C?mupŸ0v7 MXjF~aWG\˛)(6sfήں_SF9W7S#ʸVNumO,_ k9e:ha 5gX&'iAK}X@o(FfؼhYه=,vaz_3%L`B4D7$gH8d0 X.79.B%`i]r_%Zzwq.&_F35\29#*abndboJeM(;Gdlbx͍# .w{=oڑj|x kf宐?4(ih$2-F鲉6dǖ@wI~}iRJ65?7\'_opD 'n=$0ڒj /ɠe '_Eo UO?x$P/q/2a8o!PJ3 5G}ֺBq>W7 s*य~/m/<H-+;D.:2` ŕ.ҭϧyuX eo4/yF<9J4 f_}%DgJIU?aΪ }.%>cL~e"HG|AmtIV|77@0\dԣE@w#Ŀuu"m>j@תLx/*o4_;1.HhQçՂ0v´MӼҢ%D XR w⧚# 7N$r#'= ݈bbeI|@ ԸyڱãHw$h)aZc1G,/~SɴKe?OPY) 0-pl[o?.޼!?J;%DNzn$-43ÿ8Jy[[Z?̣Nc^0pnc>E‹1rs aL]9}Q&!:΋+]̫-{6R˴(6Uzf T݃AF*p QЙlv5bXvr8uE\zSnw7ԔHدVaf.\R=%h}Ge-? V4L*QF| 7S ){FRJd',$Dpf`sPpbr}h\= _`2iD/ ;d-k>\]5k~:,A&6h3ã$4j#BnCZR;AKSo;XiV /aqcӐ%; ̿nOعVMI~n_xG­ 0y:~sR3<3 -$k]<>,:ȷQ|\z!=MWim_i Ga2yűJ < џbh~.ڞ/ׂ*30b䬂X0j#`6ʢ6>g*pz2M֙`9R'8^-*~ޗ?;i )[̩IO>W+UgV[(R0-]1ʼ,$eCnOFV()#Ւ"R樵Lj jJ*w@.wӃqS/{^~$9㥭7hNB@ŅZ#rރ[liH70dL(H9# 6has7He*)h CPzWt7# n%{] PtL.hxoK{FG>0pi]uMd2Z?@d4t&hW2,6Ul@uVUŠ_Y);i>1-O.A0*^]VOe ׮s?OTң%%& j`ys$;E {S3 hz+[Tb=oqeO`iz0{Sr]LϜy.M/@?Qs6`}IޯK=lXv+X[Aח.= =:89[.:9͢BI+֠ 4CtİGPzAHT!Bt0 7 ne\_QBEJ_ʞ a֐EbG?ˀ?]j.Y0i=@E=̹B_EJ(t4)7OP<9Ǵ0p-SR_W`y& d;6Ѽ%w!H"T>~ 5=~l4Y +F駕@aGcD;:U-*+~[7? ̽׹ @^@n˨jT@[]@A•>Tw]-Ӓq9o5_^53baڞ<r6K_$H->Ra2s|kK1Z!FSX z`HEhS6LbXI*8q#Qюm jhYGZ%c<{oYA-F'Ԅ1 h)GeEKp:]e!pDe3­{?tp^.c0!6Wg~X<()pLh ʰQ?2eެoM8r&j>W'񦍿 *HܑM7!,7AFlY#F5wЇ62kxx#&C0Ը|SxG2g@cL>¢k9؅*t7C {|N]d"xC%̧J)+Doԝ=I*IL,:2ybFǕI`mRz$<%{jRf;2^Z`+cNeFwwNxȎJug=^[A~]|Ia}+_@VյOE܏ r!A[T: BYz 03,v8/op/?FNtz/ Rs&c&v5-ߘofHtEcKγ/qg|%&65#ONܯe4v`]a}beijF *~qe#\{SFџbTtAcc%?4í,X.Ce> iu4;|01Ex6WHhrrñ@i7rA$9mt}[ҥkOQN2~@q fg)a3%!ph81uǷ1Eļz܈ks* oȴ@OQj̅ncgw2*HMt&*ح%v& "梦K[A%CK5B@ި&dVt1s7&#Hyíu lͳH^_i>e [q9CgY/c"8Gb>c 6>&gMEn{3[X0&Ѱu% ϟfr5(rUO[L}B!mrb$T0MZүP6Rlc:%,IF!An#{<7 ?n'@6u]a;(|:Q4aPX[#F0Em|+xMk޺ d6ndnpic:},Z $"3ې]zqR 8nډmf2:o ܐ 2-;9 J{<)) {?AU\UkW _Yl * cQ_ϗS^7  TL2L>\Fl7wjCTaL8 +npL]1>g!xtpeپTQy:zx G-TM|r $Y;z~Ef2$d3[ 9oEJas(8 qXmWilҺ VJ吜g-guhB|>r8jӊUt>^գ̳}07w5ͩtWs;*wf#LCco{7%h=Җu# dy*wBg%{R5@Z!䞔l< H_i1oS~į,_4fA`9ᥪޠT.0#B6_RB9I_ `z13ɯ#;9տ]T>b4Eo#tU0I?q(f|aF8)|V,N:Px &gAijzS ,A5J!:ύX_v"q 7Xz}h4|AH˚T@b.2ҧgw@1{-1NGb\ei$Au/~61E^WtJH'@Pwle꼻ZCdrKM|Fw, 6d\ Rzm~va[ZSk4L/;*xW |ey6jT$XzKtJ39]Za-]Qkf*c6V;04"Bw=Vj%ΩS&wO1?"O76;QxLd)-8πCۙ[LMXd^%)yT LKaDdN kcy$t'B܆T^wi! mA~j2HΖ"M: HR(cJ^(/4a$/{qH몗bsBOn_ݥȆ,rqw0..y uB$vW4*񜳤Jc|/]Y2kW&h ϰ:`:yQiMG:Yyd@qf;88H#YoCS@^jnMkb|?wm-8*&ohfb#)2 cY1p)HxE¨5m҄F\#] 4a}]3CAc-2rS6K~XӖočݛCZcŔ4riaĥg+|ᣴ7+N]jw NexaY=i<+**j,h8tsL^eݹ:MT n@e]XmG$L?TaOh| U[? b~,/2ؤw%[U06ҷ7bM#kJ7BPI`XYbf0AիW\la|{w\rCPs0Pƅʷm+9˛I UUzb TPo "9;xuOA HqoXk 377juw:XB=h?$&ɳ_/H$*(YDr'5]jkR T Br_,3(KӯlqR1g3B`k"QC٪Yx+{~F7*?CzVR3hJVs}tH.$_/f-M{w #Z0u/"2cפHNp?HZ'S!l]F_uH.4]7x{ꭚ#A&0`U-MXbu0cXqBI%M|k9&#_dhOvLl7RII4!. :i e'TOyÿ᧷Ėg,TF~$;\Է<*6ʽ/4M%7#qk]u׃؅⩅L?5,Ԫ=r|]Νo]U-04;]֡"%,Ow"|3-F'kw_v8S]5Ӄ MtՍs ax-Fl`0(FPn5tWF"U f1Yuq87g~Uj\t!j,&{4WB6%nR#xPC5WS9@ :Be[]f'+< 6pAB"(2| EMhTMRRs˨/6i )L׿ 26׊sm9:&n 2 Az^{yq{$U!J7m֥v|Av0(; BV^'r9pGn4n}-2@@c )/(f}<|nYڶBH ~ 5 ~.㟠A Q~ 3&  ݞ.zip㖗[Rf%IF40 ?[(>Q& gy=-e*vALH=u\uH4rrutE<8 , 8pN[FT2q7Rtk~o '/ _-V}mY zǓl+yjpץ]CK|+e|MQL(ZC֞V=&>D]a8*5֜wvgVR1ȋP`< 몀wE5sPB}8[b <#9BKȏy'֑"(hxiăT'>~%JVL$@Ek zj V$?ͨzy|_YZ!zj SB6Љ6+g"OeG=Cˇ62'܁%2ڟ$)hB!odw[fɝez;aGT{w?9*XxCj lw`PYr@*տSg{^[4[x} "kXT3]`-HCIy)p)GO`RrlC+0UZm긜]w*BU)?kr#w}V'e›ےx^(;~b$˛u {B#ꁃ 3;:|>#O4lB螗[sBާ!&PYmriI9d6`'OnQ:m{ oeoB?w0_L7W=aS{v"cQ)X5>F[nwrѤ\#d!WJƤ'֤Wi~[ڥ: QLNSJ鹦aR=ؐ@'!}Lq^g㕛@!_Hȸi/#,A8^BNN4V %.<B?HY_t:mt@UgZOn6_)PQޠ&ɶ̙+{Q^J@ C`;>`Rh*xrr$7,U3q{vsȸ]Cћ}z%s9.0rY4J't0czv񵪐`x wwahv]TKܞe"w B("^" z%K»[}Oru |Ip lUV7ŷ.@ŝzuWīS1V2#VxNsnI,X$U̖έ]Hҩxٺiv(hHsV 4$)?8Jhs5#߁~oY5[KUeUl%@}jQJk+>22)5xСJf0L }xĈ'a`lO z7 7Fdk{X>+a^Js0XtaVj!s'Q &#/x(%1VuyH;ج)!HR/ Ѵ*rrv:%@V~IN3%? ~LEWw[~Vr/zA$bu`(? H+w5ntRqwOQmh =O1+(^an>r`xcbpsVN^vS4R :@Az-"0m<>c32nL)Iǃ-'%ˈ5t|QoO3EM ?``I{4ܤ֎ A]>0oFSEĘAQx>]zX5ESidusrP'zzj )YFhGv R3~#"1lñߊX5T\"Rm9ݳR2D+D\y/&y Ɗ%lr14R3W 0]}-⮃;%Z!91қ3IN,Q73tBzg"3t9O(Q<΅bf?BZPf_@; HK2OA}U0qrFVUh}OV& f(^ < &ձfldUd=ܫyhH,VN. \N49@)wKJ zE{YD U"q}; e([Z/@.0ߦ *Qh.tF]y~X⎋gGSVrAà^5y||kzgO1Xu尃>?&V)8ÚII<ǀ2j(Wv*@5y|3;"^PE\%pˀ‡|Cqm2(0[dc3c[`4hB#ټ7 1PHpwtO?Id =1[8hxXs}2Š~)b-7ғK̺.8:)SXZu, I"n k] iRJ?n 2rjt%1R`_~s'['mD8fR%Ф YТqqQmbKUgIBrjb ͨ3KrA)( 8oaijt˴xspQ;4) qSP~Ygm]px'vMAa󽳵=yO;:8 A nS٪tv받f nn:_gm'qGje[ޑ !p ?`'2ulm.5G".ػbT k\AW|w6K4&{wHO FWx3[k-Hܖ(30b}}%VKd[͚$Ϩ&ӧ{gpl)Qy2(K\A3YWBl~$(|m1!`>EюyK=z$:k\V9*ڨq'3PaRTwCu곩>76VC Ѻ b>6sOn`cƘlBXSP]w۩/D3H)_TS,,ey%PCe:?k#d\Nz$̪Ė5ZTMSIB9&,$~v1Bj_&R? 5Hı5 U+g\oCw7uy|5*PJښMMIΞOy $ ZBoDŽ8@sԏD[̓/cwiBS(pq.ei"H]xrL{UZIy]M›#3mvEF dfC)tVkb^jF\+`P~bηI_ȏUpD(H$E=R}VjJP̈`&f(;zDOQ|7\<&C h $!0O?BFҢ-6oPxxzKh?ً}_gԃ1icx|>zLPK[/ibw49e3]F|9)n\ҠSK+@$k(/b(Maύ"@s&慞si' cf(EcCtth0NS<~M^4bE{OMЄetzb=YI;+qt<ў * Ync:Yp]@ Nq<` Ce}tg g) -֝sxYZR\F%}3,_= c@4ؾ4JUpkΓ7HY/;w3-9Z ׍":F Z[sf/^op,`-V+OoT&̙Vځ V4qJ!):'q:(r1BCd03m6>g$PDLNNwMA]k1ʉf(ݧzr]k] (diK+FD0<l׮ T,գfYI5mr8֮T6{n\z` j-4\-FLS>j2)ߩ)wJO c35?n,dCӖ/la>">-hQ>ȥd}N&$0Y}z zuT[%}J㿲Q 2N7RPxFo %zOwΓeA"f໱" ЙqKc<5KP~f"y.R&y 8]M3DxJ.\aDiSn` ũaSkW}9 '򯓊m4;/`"F109l(oP ĿjB`o}CDTQ *"`]%GacF|HȶDI[(噕K eI[bK1 %qجŸD8xn6S6 5 J2sB qm*K]ρA-R4mwRE VKBL|u UO!T; W~<|^z!Y[I'O}-"6E">2sg3,U Qsiף Jc7ƃܑPţ?&+\ D ەc$!W$FN)i"pJ;;$5 lmqiImA稉L '/QFV`?|a,r0"0LQ݀UΎnv qBo)wA~*n{GDWDmWM^"yDvX%4ª6AآKoƖ y{GHhwIu*˅HL0(SubLی@9Q`Iq% |I(ueMIgݙLIэ,1/'5pyӐ_J_UZXƂ$WVe}W@~эN:KZ$$^`9s@I[L-/pCbm6QXY.k9-WO\ՙ~|j?`ګ~UgnoU3>svmey`v|QFsz9*n\rX7CuTɂv#;YyԆA]E0:>%WUl?gRv8 '.K 4;U:Ue;$MOgJ/r]%zDf=Y.Cn&g 7X๑3O{a@>1u=$R'Ti p2 R%t TlJ#4Sc5)ޤ^<{<&#nͣ=ʫ N$[!O,4z'R }ݧ*ZR:W M?Wԡ\y"JS`ٛGpzFXA5re6^*:7QH(af ]k:)xbަڹk+$dK늁tߒbUC2z|E)Z~8,gE-DSDrFjI1ߔU6e(}hvoWF+9~޸B˚9p%Ƴ(=}W0:auC|uK66MARsvfXl#ЬǪӆ?>N s555 阴Kxc|JO-X}{11;#a{]*EFЧt'Tɡo,Vi\?`OrÃ63#ͮ=,q+hGy3=·KQ(ׂEv "LcpԕH 4nǵQrڀ 5k$5Ri֢! R2mMrX Mm֍q;R,U!c`0x^)iCs]7;͓lk &l*)Fc|8b+G=:eq aRן#p (ȏ8'1(WLjs&G)D4f(l$Rȹ}uxӌˢDzw]i7.0;TL1A/7~Ҩ uA]t` #,{Iͅ&P'AŦ)6kYzX;]S í^^lje Rʲah#GWX-7<$PH3;w w wp``gw 0pVY, ҈f\{o-\g$Z-*ƄQ䘦D,W{;)d~m(MsiBa TDp jBO>FJ1zpg`=pA{#"GqYӰ&-{QY[bIH%v/OWIe7׀; udIw\LD=O W-Ykpm~(zow-^0pz@ R,M,[Ibb^/P3HX!̫aގTIYJ筄 ( W m "­dЕެ;!ڠ(-+r`aB; ۜ|XtY X۟]΍4um'F`݅R9MN5 ];ad1R$Ʃޞٽ/-oÐU@^3ۊ6{lƢ]ڮ"/v]C9g#b (-~#7:S%nxч K&~l{Y8uؠ4e@w# ǀn*Ң8=/mHgvsM;˴+0l&^QƩbeт‚GŌBЄbP0ɟ$ sy@y߽%;lDV@@ M$%S`oZ%,\z<lѲjj+L$ݎrYr @l-"B0pyE4O/jXuhU80R,A&5]v?=;oB2/,|DFUZCH%}UgЖ~;: LcR^}1@۬vğ!ʌIXѻQ{}` qWOquA9'>l.;ELf̝~ĜH5/;x:n g8ra: J`]S2:tyS4qǾ3,sl&GjEs`KPgg/`Svwt畲he&k]F.=%ȢG"ۡecϭ( j eCjmQ[TE(|AoU14fg"*CJE?g 0j6L oFJ*꽗ekbELڻӊj,\=%.GL#ǹfpdHf/ چmhM,YpU_V/J<(K!wj+7YMem¦,{3'}E~crStzù ܰ0.%157y'y:Lnn9ECOiȏ+@n@A{ס>;'a_ta ;[*G_Td|~@+a}1$.ͬX^fcV}\P]gYH*1l2'D~-P\m EoIBgOЎ\nբkV$K/ N`Bxm;#v:bVZk&v6 te-6zFy4\__.İpSE^ij6Nn%P r覯<='5ƣ,;=l0 ^nXA:{3J# 6oR3Ŕ_R+'/(WJz[61aПj{MgDĐ]dN[ͯ6PCӬ[a\kM95YOkHp<ս'{ѕ>K$8WVivov<[䛋zF8g_Eֱ=#7$gEzrQ`la=e/0 ()` ڎf,)IM^zjZI* htb%Tልml|Gmg8}ru8! 'J޴5;bk.g]/!x( k*n۽Ro_ᡷyGeUVeB,84v)u`'[-l{nzK+V ]t.UUv9"\9|Ko*W2HֳӕSv#$ǬA"4XI L'2tCɈ9_ PL}#lJH'ghvw1=i,d@*,9.G>>h?@p)A -VXB2MZvHlGZ"):J>BT2'̷X0 Ak}[y/{[A$רc07"Ж.WE |ux5D4fJNz~; :^y$cdV5aPC5ѸP;?DseujGYo}޳óqN'Eҙl4f( x7;(]eNuV:B%TBh.d5Ԧ>ebo[>1oGzf4َܠ/1ymXO-vrDlNز<;'mEȓiBLGEoi <Tj܋xn23kwʰ?f=_nwa}e8y9FW* lC$ AI&S8@}=T#7קi&@7Qڗ_R:&G8lMcuߩ.a@AdH9f[/ħF[KO}n #O2`gYW!\d/ԃ͂)8fO3Ŭ;SZzS!s}4ئCtʛ:3?Hk)_%g6kTyGPWq`&E˲-]XVfeդ`#*Ԅ ckpNpJKP, @ۣխTGB9teWc];e$C%oJl=vp TeQÇVZ+59RfJy D=/bz7#:O6nPY~?d5r6&FӿH_JŽP^6~rrT:o9DT>{#ch5LaxO=couC-Xt D_@MS~&5uf` P.bF 0h\D.X!3!TZՆlϐuՔ^@(*[(J7?9T]r,ųX+-a d [ΰ%aX1[E,;C74LpP}$vlnxe) lS݆pħ*KI4}ZGM}lu=<<C-jRjۄz9ל]*c9vP:|$ͭڊT .Fr{l!%I l}2蚵Re42MB/ I?j Uގ!+qYG ϶A¹Usp`>z\4bGKms$uaJ>j;,jz>wՐOº:Ot72J/c9?uTGl,VP>G~\TqHH$G}Rj(؎Ctv^Kd"q L0~Q ~r }yp*&vOA͇܇RU2-t$dS,nĺ}ʭ CY m#rMRXGܞج "X-LE,T3X+I T~Jևbv`1m- .-bCNs<4'LCJ;_ M]լ N%;n/!;x cS?`k .ȡ('hh1HTk.5aTă{CRy*RM*)M+S\ R{}29  cr'T59l5Fx~4AE[%^P JU%pPlm jyN% !;NkGjO}D3ڮQ޳EvՇ_2`V~uERdytxqS]iwnt=&jX<;iyL+nye/{wu"9v\hX蛊GW; "a1I>G' 0ɶC$#&(X?+±des9ti\~S~p^|0J?g*ns):Ύ7M޳4 fd.C0EE ;n 3v5hz9L$ V0QS3 IA^nb7׶r`6p.T͇5`;D\X 8PR T%^٦~8Lf)MGލ@fYpYD\eŕrV-u~ x5]};Ǝ4q T`6bm@ !Avh>CԵ _O'tNUo;:n3f޼B'h-'m[ PxL1L#{l-/F/-FzϞSlB zGͪySʺ,v-=׾t1 ֌be6oݏ s~b$3^!*:1GINX}c@kWY$g7bCEq3 ,#?]:puB`NY*87^k!sڎi/͋VEeuiXߏ2s:ӎ;v;^LV;[Of2W $>iѶgZvh H8$Ϻ Xye*J8v[({.+yuX E0cKidIf9"AaiH0>ûyP18|Jj5"aj6ԛvOwR{쎗VkQc+ ϸd/SP>Xxb]V GjT7ð.lu+%MJW +a$DxszXF~| E5s3$|BW,F(r3j(Pgԁy,[u]QW͠YLISVv#V ;8R77wP}|s^g<'}OqYkT3o%by|=an,M*4*`mY c>Z [oG^tӽZ v+ķPYZ* !*] :Ua?G|_L0gT7fv~5War}{+Q{WÞG(&AI&Saן&jø5йES4JXl;qJ|?EfG>h +,)usyeGٽE#<"5D }&TH?A$pJIWŗ̧<}'d5C z !|kxo;4x!l/:bs`nowPmᩞogzw%~DE~Yo5(Y2J rQ-Y%mP<\k&JS2i飤XTa9Wl`+iLlNuMXf)nY ?oCq$>t&E2Q Mtt5?/8lDk*Ă]hKlO cVO4a7l:uV "yJ-˅K)I@d'0U$uV*fb__<#%@F5]<؟TXх"1h7?}GW |8􆞏G-wDΏ HQr dv= (P;z'+(O~cEߍK./|zV,Lͫ!_/;Q?4Nt]`ޱ?}TxeNh6dJ|54ݙTH]H}ݘEQq:i߯܏v3~0DN#k9Pf POT „}q1mPi҇dCv+$3"DqR;X?sʕu1:sCs9 9"}߈i9m!GQ҃r#}׸FD!WPBFv xRз?XZwi P>HA1f }rG9"b.I%gj^7m i `|Epn4=G7u?}{u$nҠdSfʬ$k1!y!F an,cApi{ Ty{wx>C1v,Mʟ,Nbp?&!1X_U{9X_QԊY%, Q cxIIָ eىS^3Cgu=,9KH)%]sdY j`b2ԥọ Ў{&2t8H)ګ`Atfi-ŏlQ+GV)o9d:v'mZrqm`t"3("F5{͓q]ù mO er ^יּ`y切NΜ}u8$\#\UovmUBq'΅a9oLZO + V'4ln>]ė6ɸpCqO.[rQa?Ev$^A];\݅H}2C+&[9Z歷CX[YLeMu%5[N ipu:ݼ?]wB|Z[d+{AUI2^ q ,Glcd#F]8&7Kɜz׫ϡ?H%׊J534PM@hO1s{Nŷ|0?&𠑳2`^ (7[uaU$Rt00mKfU%bhऩk"fJ1L(H+O "ٚAE+hXr>`CnA=cUL/q"$Rȗ^mc5k/*@lR$u=bvٟ$c%_{P B8OQpLD>erAZyҳki,Ŏ9NHH8JĜp3xI*^?N n׺a7uYFR \&/GnNG>u⇗ڭǴviv|MΩY=(rpBT:X![f!%BY!DQųYR6K8lD5]1 @Rx>OZ%H#l?p &/$91~:m}Fk2ze,c8\:5k-bXq%C)O[],̛v߫tN~TfS̖PadWf x0*pR^\@!$?-:X\xkP{գRw"7镛1@%2BP~`r#]ZiXfzxEH90[dKd13:¿Lg_ ځB j?%y[ӲÊ\>c3?=[S5/Ue_gߴV-reheS. e%%qe_b2`ț6G4wG2u7#n/DkQ+;ssi(M ]2b9>N%3ЪxE}œd@rӻLN7X,ڗo ZeâJ~4hhu}CH"=8u;RS^ĢETSP2.i̮]f})lN*zn`_z\p dTIsb0䳌nc$hE|LsևqD,3Zd\.׏+՛cIر1ڴ^uIk^AsvhtƽHOxJmJD~ .P<9!6MT|qP˓NM$$Ux/+;tIPp{zoYbGaxѶ,%Tva]lsEi4٪eK8*c@%xXjIbpŒE= t v(Uz fXGx[ bj3_T J PU˄VP*P^"_̺A&0k*V9B`he26nܡ(9D,2' 2L'NrL7B]:=V,GjNsmMIaqt[ංlE&Dm:!YU=t Ò=t;=jґPR o >orBZY 1/Y_g>5zuu@C-c佾o=ip8;./ճQp˅uXG%[aR3ip !;?t򟚛.fAx>Dǩa6~L*lbU,̿H#yq$}n%LOkUŧ: tÊrUQ<'\l;$2{͍Fdz-VXij>@ \ qz4$Z|VߊI˴p?}hV LOv$4rQz9IZaX)06e$gT$u$A*5=JH̗^MV<wB咛ֻi$ XY#c;pK3Ph 3{=Sih'zEUF!d:"lM5֮DQyIbga`*UWaS]ME~bk*/XnAMw§{ܹim '^qlwqceېa`z2.J,Ic5T$S 'aID5w4qjS}PY _lAdMR0B1NFx8 шY|z2I sAU[a[t8%K?` `KONZ]"dEuNf>0"B*{n<$$ p:|IDl#nMa~l I^\M͏d,UՄ9ڹ, ,Dà_2 k%1"4 Jra·E;K+I1yk]A 0 2^Kf ?ASWirU6R[ZG6gNxp--y~|3?YT8:(0kDCPLLЉ1N*VLh$O 3mՐݨ씄y20KRh4Vۗ9-#)}⻲SFHW2TdL"-\pSMbB]kSFyQ9J8˓p9P %v~\*dA\x;p^Dns(ɻzYV~>NhY.To ,O+xp`/_Trf uj\eS=S/L]ib+б]jP57`$je@Y*GhGBj%t JN#ML5%RJʾtIX[B:EWivラɤ iQG$[m(du*bKI _"Sz1+1q?('2W#!^\$ ;42E}=6IkG0>؂F/<cc0\ɷpI%Yx({Z*r/fe@l 'eütFX̧\ j1I4#@COAEJ.Ykzc ֊fSAI&Swv}M<hڵ@E" NV 8TO6PLB@kωW|ZJ_yfxM?$Ǯo였5ZiXxdXW=@6UkKj^ }ɟV'LBygT4ծ\ׂF ̑LqqpPwq *{2_[ `<18rQ_-lzvCC"?|N3 w9( )3Irt2`O7A1>(&pyòǞ4]ݝwA?bd5޶ %~o!,5i/$Q&f),0IBTnۄt.}Yu " Wb'õ6 ;6 vjI,Doha;DW?˖k~5Kp}BF&bj --SG[8UQ!;Hi4Ę;2#бKaؤ #W˾铯,Jډ,BmOmXvM*9B(nWjsl*nBNPN!Lo"񡸒{g(e4&IZMN.zgt¿5}l{br^fF3^b 7U˨u_*JPzM1}i$}xzڳ7DPtkl=OU)8KAlpj}4b lne8ær ~z{}y̠V|jUa^;@9RCKR!AV?] =5H+pKTt+]eV3=e6y{,_c&t5M+$?;<5z <ǵm}LUN ƞ^:eרzĺ]9TdD ׽7GGӠ72B|):g8_"X'*V3vloQJky\,]`Pڼ͑%oFfiYx6ᄝ]AV Ky~̖yԩח&j-6Ps*"m_^{AԮTֲ4T$YEG%ҡ؜"~Ʈ-c q[_`vrd<,CʬZ{9^ `kkB6vnPQ3?W]sKlu|Gÿu%D^^ X@4B0vqkm5r7قEb:sQXlf79oZbK+ q/ű~k1 |p.ͽ~@u65P`u|$&r 44 b0GZ𼓠ƞ{{P'j.ڑ9 AKݝ yEbvJhpܓ{@uT5ܝlw<4UaIB+<A޿uB›S*|o_P1 eg|FM/9od:#LmNf@2O`F˜#3ˮ'T\ͶETSS;;'*7|>!1@?HAg*6XsWkwE{z3|W)He`:!@[4Fp;pxQϩI=۲|ڤ^"JbJH&I/NiJB#G׹Qa-\ETMrc*p'tH㲰fÐ@8lMs[f }d/xH: SP%aQ 6T7`^,<]z=|BqRe}1qRݴ І7|~ ⶞l~'[iT^Cyw'Zw4 _"0QU.[J&U;vPU|&BF(FX " ltNK<s\;? ~) "R'|. P7r67^RKYKۊ0`Rꐙn\B#5 A I&S4 KIf~d`YfeWovf9|[#w> M|u'3} $9K,` Y E :?oj^eπkgsHGr9!@e%G8/ﻬ]ANcڜ|}qYx[lt2&Q[hM(ylʼnhTDqWu"9]?JDB1A=L-N蔃Mm7v:˗9I Pov]>DZp.ej'PcrKz۔iuu[ 7.,檲2³< 20J L&5mV#vOGЇ- g{z& `d^aO !=bXNcb[1JF]%m=gUې9 'I dQ!]\ZSK Wتj@uaާmhg"(%_0d5PknP.T]yIjLc'E}Q8캫ޢ„]O bÛyF*~o#:{~Qo8; 06iYr"sLwpM"%krcQ%&$}S|VH4L> )pp-rW|qN8(mcbZ6E{';޸\&PEhPv-.$+7sBCǵ$"#^\4Aaw{&|{:̊/@>o}/WJ;GCe'{9;C9gǐ޴t",z8M;*5"x"8Q2!ݠ6g 6AH%,u6TG<X] 11_Bx9!:chQа3^AA,G;>ҪoB72Z|yB5o}]B_XW@h/LEtL n( BZ^_~_%oOG=v? `6 7l 'C' d_fѻ[>I^Xp  tAbl0"K—|Q(ϔ%uwI1byK5N8`q|/:y" CZ9wn}-."U4&h W.5ʉ8 QJ^(7綾huy ~7M)8Hd8c"*D|;7fa{ /W#GUJ$JZTuoumf/`Nl{|m(Vo-4!F w_:˳yIi hu ($Y+ ,!G%!߱Z#;E mZ8dTNiI,Q(gG}W&[gľ!L&fPB[w.Ѝ8diUr?PCBNaz, op͍a*A{n$OcB, 8'H5Y>0L\:Vjcu455,+x!TMbamRގ#ZD^4\4$ 53pijbw/ !š# !. .A*I&S;Oa o wKf1vy&*4 Іn|ցMQظo"VF#sDw)BoG@V~64H.oOUޖ X 7N/%r;r˙}1 j)ZX[36x-|DCDs,l'3,_=J vRI_oU/bI;*=Q)IV[su"8L&|n 6=zkFNiPPϹ/()ӄp )vJU1O;\,.0(A՞wu%=WJ.ZX )ަ1fz4I{h`<5_=M+hE 937UFڍYǟG<Lk.ec:E?mm.q h4%TDOɲk=H +ɂ1L,gڍU9k=t6pmdPQZ㶅1v4WL*+0V5ȺwPb}o1i%f\<1Sw)aА:"{6sDbۗZk,*2==qa?EwoHxkQGIуĮ(A,F WR=7tqe$;Ġv@cA!*d ݮ^eXK 1՘OPe@8cܕ<+$ e=*3?x`Kd rlU<<~c,DX 9x)r!mAK‘1G&5GSZ3:+VpXmްP bB ױ'ݫ/Us+V*gW@XLh{`E0q l5} !._Ĉ|ƢsƉVTmIl2'%Bx_W<@*\J"”vbgs:`W1χ #*^\Ym;%Wa*<@(bq0~) vI\lrwt`a֥P`o6dDkA=P7nh CgEa[*4Ao ƐVJmIk\&5X(GFSb#i充!d$6IZ` 1Qjav:e"2gpLpK5&i7*K;ka":~[D}I!$N,UJV-$Ic6猞rDHy޾ u)M#zߪ;cC@ėES d|]&Dme*V`4-`/D /du"m~Ya+mdA )R]$zݟn(zZwAB}]c\43Gl?:@rUxĿ@HC~?*=ḼZ"Lpj ۂgZxA((^CSrl0}6>-zFd+FyAk3|rh۶Z;FϺ]}L‹~z f( jЫyB.}cEP]}O./*v/t-p>ˤ'!ln{#i%IhD7W;N~bCb!keLXMtϛrbKS5(Y$c(ꊟ;q \om.q J Oq.[Ǧ~ NxvؖyL X%@@ɖMrڛ9o a|\V@XAj"dߌ}nJ#=(r8Gaso; D3<{*U!"IJf. -#t󗍒ʷ@,}D lV )fRʃ̮3sY.3z*nk@㩕`;8ʻN?_{h{J{/}#ç5=Ё 'ȳ%OÖiT ob *KIk޵OX27]﫴IXLmV_^ >!-_&F"6s>zTiD`>MD,1Pdj^dmQ(}/`nRxWs ʳߢ9 {z'NA̫QSJtq'??epO]"VW;~y7 -%'9.`_݈oF* C2qmrᰄK: #/2ȃE,#M\;)*|5x;Kw^o~eU,-a{̽ۆ&Hқ񙎃?Wi66T۱x1vD=ǯW_K:6z͛%lHZS_"Xs}2>ӶgBZG+cflm 7v"93vd֥P.*-c99")_|AKI&S]xh-M޴a.v-s6aV?S1_.nTcJ(X8W-RR{{>C!7! XUX9] xnh 3,μK.Ui5bK.K\Unϰge "Hv,Kd.Rم$\LV9լ/2 rk}(XP"j%پ"viuP!65fhn"{Z[kQJ0[+\ߘޞ~`?\7ˍdLWC"#4Wl$D"3džaޠཿVZLBchv$FrO>:(^IrN2V"cZ5l[TTmz!Ly,F7ڌAC|{6PÒlNgy-~L`SHq-kqgIaZ%CV!Uo\rۯ@SU7H.Y* wonbU L3@Jzh,_@ZxTZBr yi`8渆+I?I򠣰LR ]AMh8l":9-CL侍v ͻI\?|ۿE^w^Y0QϹtF& 밧Ъ.㸩vsP:FXba[s1y;Lີ(_w+;N; ˀ՜p˚ Am<Gz~w< Mq}HܩAxoώz>׷#8 Oqn^3=SYxvâuZj1jÓC"?mIN%-:'+ |kO7[\&}^O"8se-b">WV&")uSEs'٤ ^[SVGhe,Jk&XEvGIWi_ˎ@V͒p~3[k?*N2eDѣ51uU۝9XW7=rb~-[E׀etg+ce* G6xF@IB)lYXۆgV,/gf)"$ޫ˟x@Bw3QrH4vZd!&ԲN4&w$/ N9oO3N (cnJyj+éh=;=p.bUWT=϶skXSS|R `O#us}OײSmjȞ13zAbTRI^lN1G<L]A R,e!ڍT_d":VO} doo0\29P HiE@NOaGҤQB_y7??/ڼ!6N|ٯbdp2 ]%cmO(eVDanC1K*#=FChm:ēP`z;əf 8Ǫ Wf̈'Ee)&IʶYW?LzoX|@j*?@EKloqO9;ғ'bq!`FcVz@[]}{z6]gQ`eĨ,poZAlI&S?V] I  ֕@"(s1`N AGٖ8~K-Ϸ WnzKNz:k-B3n d bB9EmR-0") )a( YM`C$s>L!A^GM{S RF;V%8i|AO # 1xCkܤ HpQ.IpripBGңU ^NiA=2X,9/oXJ+Jk< }@ŶИ0꜅&U,9MPaw%H{ͧ{ý13CϬ^tZdnʺθ*ft j {DT(kC_B ,Z~F efaJeM)es~GFc jtvH m$cΟMKdBY6yDȜTQc¶1KGK l5ͽM:%wa=ieZw'3VdWEs1Se`J-K9A4U.mZ"BtdXATmD 2n46H`P{~?\L@ u1Glocƫl&%68<,שX}ypȜ ::?'~cf=tTOg_j[XwYQ M i V{;y߂0F FRu35u_E`ب_CaSC!5鿕 fKG۝8Qj@ 1cXЉff߫2\|q;n?'Vѻnqzh#jiqNDqdmyE0Bu~E0K$ݍ_ʁO:&Z ȳ6 SY*3YܼUC&RbJ*Ӭ4@1#5%:FO:ZQ1HJV5ƁZDdLޛ|&`5kx6mz厾wsʚ -$yGζ$ \CHFZO= QC5 [iE_?!k9$`]w_yAz= EvtA]z`"INYO8%>]2T-$Sx;7.;$;#"VA*eB!FvetW|T@;/ˈ % ZãI*D:1rK`*l4WQ4a=)#BNQ p̘^0+#|C:H=*8䪊g]k)kZ>ݦ*/vKHX2r"哯u&Hج;xLZ"fk2\+*ى .*织r8Q*f6x=QdGK#(2wTCк-*Q1H-zyy&Xˇ5Oy?Rf2yT6Ի1q5Ey::k;HX9dmRHV>{;3ȁQ4:.fřrb"X4笝4)tTe ɳ\y"'e`[ԥdyFq8)C)@^@,R}ܸ:e΋‘!v;B"tL{7ַ>?a#R7"M92 a] )7[T3@ggC,vIm'w(gsx$Q3~ bBO!u&6/3,uIwc>չec|Z2'߁(MNڼnI~d34nܽ6P/$j!Y*N\B=,J7{rbٿa*kll"d]<^ ' ɽn%I6uN$ǁT;-_Q&mcR,%lW'kOijiZ _֑Qo8u6($}Akf˂! mOc'Tf*V_@f{&稦Au8e\t&@K˝q\![Ќ̃mHW|7_*r~LX(lA Ð1Z1ȀtDO2HXHX1x-=~|` ''%|TޯhKOx "< QJqHLxa}bס|S b jh_ "-R@ШZ pg}EN"G((h`~˼  D|s(VI` &1 5ly?pZF^zw\c缫@Rg'|{)(5 _:;;enݡo|Ebn}n~?:z[bv%#mJKzڏ 2L?dtR*<6a|/2Hy`C.Uё"T ́.gcoLd`BP(*|~c %Tħ*' %"d1C˚˴FL_kҲH}F]8q= ATl5F~XZҹ̐"Wܖ+E0fnn0AHs6Tt_^sali@98o~oOpy8J%6(ʄ9sw&}nUk\L[ĩ3!:t,78H{o Q$g&8D`[&ԩ^X 3svoy.C/+ bE}ퟚS-v1hO9XjQ Zzݢ^AH[m Wjw2<}~4H" %a^72_x>@#wDu`M A`4,ϷK )&t(Q(Y.5 MC2ݶrX6qF4S ֹEW &'y}<=ap3N*ZSezR$6A̫63G85q\h3u 3. MJR0 :k7Gei= wr}'^Q!'\2@HޞΩǁжn&y>@8^yU#|6fV4e1F^[ro'~a{C W|ɉcq'ӮkZjA-󥪇UK[f'յ yCpW?Pp``_ ״a$y|gI-]}DL _7F<rbMccPcxqܕ?*'Qʵ2ߪjE:vڞ\0šFk2o`!ÄueH;vn X &hpM񷖵X[nl41*,$GH.qDETN*kT#I:+9D:zVEZlH+{s# -2\!yɓz^ Y&~Ou y)'pz&}.<;cv yhq?t ,45Kԛ/u&>^C$&m.oE"wjcf < n ]?<*-. n ŤU-u1 "i;L|uxbd{}j=}LA=Qͳh F em6!oÓu|!f:L̈WxVA 壥A4Eb#VW2y,5xeNeeiSˮ]`ΰ=el4}Fn*:E=~-mv15-r  c#oٌЯ9v4͌'֛HWs )CX`\SfX Q<6Ӟ]N"PŠAd>EFw[?gom$ֱU.sGli=6杨#~~Kp}MN8ͭ&  wpCq|3oO@ D@#^?șĆo/9wy?z#\>Xw6#Hxßgѐlڰ+r~+O%ƴH;hش޹<a&B߯`c+u!u:QW7?*eUZ0crb=z+8=8! /ef~~pH2#Dסur-WVcCf0 <{>s0qP9 ) z!NqFOHO:_U4Zg/֤R!y|#"{,(G&y9qva^á$~.b+1?W):RMyx}5A1sQ3ѴIv?$G3NznKvFh-w$_Ĩ4A^MbVCo`D=]귒Yfxx]0|H ym3f \'¥SQL@].:]T8k}e\rt7umRmaP-U}&2D Y2TQ}5`Y>kR٢Ors?lE&u(0=mŕG^ND0{ LdAg"֬k̻YOC"QXJn m~yep6@N'gUDzUZQ V.p6f݈)` 8?mGj/%صÒRkέN{JsA}Fcam y/fcSM'5N^x:,.6%z1CTq՝ $W! ^H]F]MG?;:LGfLZ06! /1p>/ڕ61 Q|p\[Ui}8~1i{bx6|0@f/d;s)rbhqe܄D&v~u-Z)KL[+҉ƹ_e.CF2v#{!fѿ,GG \T֪'f70mCq.o@nE+]F`0[_t)$ȥxTWT\FT EN7!{IK/ѧw6m C\ƓFx7>0+"QWj'B/{#B7*ߵD%wX_dLk+6c DQri놷jV=ߑGI}W"wKM[ⶣeKzlNXەrF="og@܍EiP^6O#=/L|n,6[?n.`ő//aZdѡ"1mG`p_ c1 Cnb`Q1gUY*Eca&7y232S0݈=G3"mP156K2IsYr'bcF*`V;@J8˂oXMG;r,G"ѕ^Dan z {.݅?gZ#$Eۤs_[dHB1Q!'˷V:-]DϦ: 4lD'mЀxjܻZx9s~GG{i៲,("nZl NhsDn SH/V-g 08 Y&g2l.0=[1oX`zSJ#kl J5ZJw)+X?xiOp6Tl!uΟJyw kNVvVb%=6L yM 3HRWj*7 [\>M}b嗡2B Plp^ƨ[XAk, }&W,N@)*(~qo:ȖvAJ 2jBGd'm0/ƪ3*.k;LH|@NZh) D@mc k ? ۠9o. )T?z8i$oD|crvd߃'%x/K!J,+ClW=ԭXG4o xr<(ñh^nUk 0(\XL U`eVoj5(wb~Odz&s禰luvH v( 8ӺW"Y8fhWC~N@E[[A{0'AܶW ƻH@b]Pg[?jgU%Bdi gEFÝK٨ f 5ن6B2(rm2.NU_~GН>,qaU>AkNuO(O7w4K٧e#_r~'hïCKZug'C#3[輽I=C:Bh>X'blBN[ YXza(f#6GMtR:-Ƹx`c$U7^Wi 2mUi K 鲇jRMEN5Ng pwBؒcMM5ut.M2Ԣѧ_ڣ -~8X?nC|#)eDy;^k^QS,+͌$npqM:pTEW"Ne*r)\ ƃ`^e\_cVXQ 9=#'1 "rrHuF%iV=60:.QRih#},U?8ע/pQW1,3ȭ ~P aEX>KPhYֳV+bۼZNo(<#&-p۪wIiku%%hb )=j(}w!B Q{Q }PH.8fa\Pr. Wƀn)QQʿs[~N7z\% @vԛ6)a#|A* Zc{9ލqrGDd`Phxp$/ڙQ;v"쑐Qmf ’ӷggৌ \b-R,r;Ee?WSSgXy?SƝ͐n:OI[KmwH*9mynԛg( GĚOSIZ~J0Jt(F\ćr.{ ~n7bc {mJ8J L|\nM5Yil&KF;w'9])/~]cxREo$4\L@юZH®_f(bVJn(COL=߹Ԣ\PiT[~o*1ɯژUXOnnb6MdoDJ?&-y#}.@A:lC9=ߝ̻+N6 8Oi^KGRiA3~7t]=}3CDծЬ?wDARS~\MWGвgKE>^(}%ݜ?u*d4O f3G$PLA(Z$gi`"л䍦&(:p qR#-G@tketʰi) pQc(|4">E`/ Gf+c7O.!Yb@ G!#v Ts nMn@N8 5h׺*i~#qkȼ:G aIeI*e2Qrt#_ ݲG[4uΠB6{p>.qܿeTTN[d#" j)fG=JןqO7ݳVрJ̎TVdcY9i|0F TqNsvJMDAFz|H~304W@:` 1AI&Sj Ƴ<@)Xu} l:vOQT.Q:H<dia0ڲ K1$)9dQT3!ho/ W?@7[G1?HIi˳ /wV}k׵뻃N9S=pƪ["*KXg҆PKl $m[Ų"ߑWؙK. (;Oh|\b5lXCcC}:=̖z5 ݚx^,4i ěuM )[7"'^uȝa} ۟P 2!d~Ғ1Jnao\P1\YN >x= l,MYM0ٸ 4MfD4oTg)'Aㆩ3"}H m}$M7NQF &=y[9.G!$ "K?h#%#R%r%|*o 'GPaty 1DlZn·%-[h@,bWavFjWt!]hHT*K^`8\,>vu=a~ӟ@ $= ;v=`>Z Pa(3hO7ZD=F= p0 r,-u 9͝XZEP֩_i3]RGK|:(ˍ) _-$Aa@% sij0з)YjJF22?\%7U:kJ{_S䩡7U45XhLH/XiC0T3 wgR*0kL+-/6',S+ӫ ,g< K6r&RtTg*/{TB %Wf_z@=򁡒 KѶ˵"k_M/I@WCno*`k.e|gy[&1BGZ;U(w \ v}0lfv Ⱥ`L=XDVɂqU28W{h]6':0]3jWכ9* !~NB>W%A/?-\pҙa.A/̺/ϤX.Nkf6֎{)*Z펁5z]Ɗً6v άTjcFc/KLx~3eS'Ӄ/ͷ+q;MXrH֊J䉟{8wH:M_ 4pr_cֱǓ+@,O-46P7y`1AʍAK(XK [5MNuHXAW1Mμ*HDE+4ԠbZaQ1>|W@e,`EoE+Ϻtfe<*=DU6jc6XАrUIR7WŖȦ~y|8Oh8uHf}A=tpô6uT6ro^bv̜cI^v+薌  zC9hS&޲˿v>l[Sg(αV C& +  B-*Th0E :mmAH-/)tCZ>.O3LE\6fZT<&* {,F`Mù2I> 0+ iMqDj8*HX1 .[pzUO?!6@dDMe6Ē_TKyq lE3۩8p3'k{Z~%?$ U 3yq%Lß2w&p)^kŹz^?qixa`-^gsI:WzkВ RT?v`A8Jݗ&"&ޮ]U w-4*S8l?EmhUz9rXō:冐;*=}zn UgUn9-*`m(Frж[wBre78F<\[0Ј#a궳G߆}ߠͻa5 2o Aa0 ${uT+uKA;ے{5*z+~ʀ2SҰмJOİڮ=,oIx`{tvx64$T#R(<'g|+U`d͌IC?eC81SCVc fWGyd b#X ќǨE;=FMC,_]["v2]MM1U)Q)ߘ}r nS-ßqfEj|\SLՄI0I jAEpW0mK{dV=X^c˚w0;L8)r]GbSQflRh Ҙ+"\o&dnv:&Mq #݇>ɺN)S {:@ǞT)Q|a Cl"jƉɁ?[V-F<{'vQ+5s&J >R=Lp? 0lwmS]2[(}|DC>YPaCl]`ɛT`|r@kahl VBm5^BQwz]VK;y]D.Ζ~˓^=jĆ+_m6Ґvj%Ƥ@H[𙽗Է/])rmȾU#mYSYʗ xjٝr<2diYÆhE6't!{2rzߞ\0 j&ܡ<˖Ve!ϾU=[e4?|Yu?Ty5'M{"2*v,Ij1U1ekҁ ]6xc%c L>C* Ip#9|b"Md2c#wR}m-vہv>Qݷ4 HZU붏(FP`Ƃ(#p*վȻKIE_>E$\[)za"7vfd30uF0`oyM`Fxg.5,@~[r'U?ɦctc lXY qE+e݅I55MNcK4Eҽ= WgĆ+rgN>PG5Rj}P[~Oŭ 13z۔X AOGj,2v7Ts[C0~yw͍ lB0kKL?#߹v@\/)ǒ \HO_7ߴѶATH䔊lZhZ^x-ȫ; |huVWvl$zxvI v5r(u69ayLwά0giIa@6Mgp&Juݔ!_'{)bm Q'2,UC )<CwI%wBCt:s(RY.(3oss#"#ww{, =B:.9b}h_)+L0/_'$ te]Ba혪2Eh 28^INbVuqۺȁ8r2+;{uv,']*U;K7q:fNry6_an.㏅\҈ ^_U :2Վ̃M}7v=iweSlۢU/۔G 7W:!<  stl `gWpFsY%I) | Y$fjKj˾rq3C{xPK}_&_Dk[MrIĄ]VClx(_\;Gaܫe)sKpn-juDL`i9v3V4̫N(у|5ptO< ti2?4`74L,֤FJX*ag=We3Swk~Yy< acs끲lPE^TpW~ (bkZaChG7Ͳv늟 |C DDFn;\n[ªӕelMQ[x]wNo$t5DLM"}S\ 7[1KN7eLp| +^2WI)Ɂwvx TzjoG6FW\RI4\d@ދ"*^C(Znj)%:R15<7\BuP` $U '-$yi>̙l)$ 0USI6[+ (W,*x+ENOiI ~^L::O"sO +U*D^Izx5D2>1/&B- G`C1SD"iB 8H~6zL(؅KU>A.Ln-ᇑT{tqx!śag.)au!<"0>Ro&RAK$ƞP,۟4Zh]%?m@#۬+v)$q 48|m\tM"(Cq C"*W&P*$uq&Y$ova@r·IggII .{!:I, [fI^@cSj J5̄kCs3@_f=#C){z݂Ԁ6X7p@eG+JLY?MfAXא&HgEt}xPhi81DP=mMИ`X y tD p&|-?oZK!K6(YzA^PYτ˲|P03hxw`vW0]E\)rB픹Ky}}ATh !k\CƩ +{ahЉ}wmLm@\u$&e JCO7k$;{<9zɥ5 x㶞,~U:j7DYY%GtE/*en{=Si`;zI.YW$V~lp-t1?6:FyٓoeFAb\$u fa4q5]~8ؐ4xAVƱ Ȏ`RCWN ,)ܼ<;M:~5{0M%(ޑf4BF<kC-AI&Su~p57i\5(P\0+Zϒjl7s>븜mNŸy&=_KQ&mN+-2]SKpr[uٓs'}`GxτtB}qDMۡSR#YG3/_6G jb3d{H1~wRFьWca.r-YK21٭DM!g˷MzعՔF>#Vf{ny61Ջbk/\1JB0<-]kzyINu63E5&G;TKqɓ(ҁsvn 抈+Ot q/-8=y,~15bQw,0 S`~"<S F[5 ކ1װ|r)er͟E4T׽7 QH+٩J;~D$*r;YSbI3' j\">R)ʊH/!}Wm.J9߻ Sc·(*~Xzu}~&Pqĩw9_ =bʮG̠w5xt}IP$;;k\Fm4bg5@l.' =KZ<;Σ 7AI&Sc8NwF=@6)ucU]6`~oL't)y/u(<} Mqex-*ڂa_zV c /5?vBrE8Q,"¾a? g"ʊ~2-̓"0h XQG}MrU4I st&RoX.\ΥQL^wf6S@(d 3ȟ(-J,`|7 'J4䉅XG;4ywI("Lu+6D>Áphl.5W iWsŞ# \JUjwxwMwkLֿ#Y~Q̹nRWi L#+:t8yf l0Հy])wroԘog3/<;A(dyMݭX(}QgɠD GsbM 0^~gn2y6p@H \8mH p =F1;Թ2.5y*K[~U7AfGC3auC؍'X=i%1z2u-[}!$]ۣP^$D|~᤺ĩG @MAm{wݴi?g P ~j,x GifEPApyWތhѣ8]cb`rpCrɘSGZSJ+y>/oBm4^X%PGU)fȸ;)Oib̆JrQ1effJd(GD7y;JGv#Ϟ;5Y;:"?l e`Z2X9YZ_"CV& u糄aB& d<.+Xd`|hLkkM]`eJR[JzYp]7b^w҇(WJpV]啈}@HD#L7/}Q#nWKnHg@|fxFǞ2=I?\Qi&iq@۾"OG<-+X[(3R"ٺ~zE#x+t77{#ks+]]v%=ps vRBG2 xu06(1?S2LpӿG~@)y&Yz?MN+vAvpʺM~ c_=qۤ#gTh~P-01/q#<$Jc&nkpmqQuo)SB.KELObg8b_D-Ed?ڰ-oRw.lA(d ̇k gߘTm@qiUYQMW?Bm' R/5~ະ3;G@qҭ]v/~Z Sh@O*\oU#.DF_23Ӭa曞Fp!:h{k hsp4 X6VBϣ'O5=Gʝʳ)Mn)v3`"2q]$55`576,"nm[WQb";8/V$tЄ'(p㼜zW6s?S]j%䇾YԆ_[O}/hZEVa&;'f4\7_or馤KjYhF-c"poA=FRd+.);Y9 {4M\"phGxDtw }VYmMKX),£=HZx X3QxugIRv[Rye.ވ  ueF*qF ́Q-KZ aVI"p=J8b.`-H>y߻|g-44TI( Yaa`b!/Lԋr(Ǯ>\ !zEN☘5*s_5m}.~)01U迤*r3u=CM:fQX]E\Y%!~R{ҾhH!3 Ǫ6 Uru/ztAPAs{,'l؇bKXj%>:E W4WxU6-lc7P @嶋V_i+{iyp DC 7 ^M`3LCPivFISPA Ly:oOqi/N6}1J-\#G~9WXrر5~W0({ס`¡gZ|+*ZڻW㵨k-q@.1NM;6Ԙ^-;\'O,R;t,U (3Ɋ?5IBzd^8}tN#F YIu󘪮BY/"n~&ׯ B"Z"S=81z(lP9z\~8ٶlw~FaL6p! !{L+p kwABfm8emAKR?r]Xz0S8=GJtŒJ/~ZX_WoJtOs04"$7_qҎf АRR*KL*ۨ JطJ+A,Uq<{Ǟ.* 9-h}d^+"'*I~_ S]v 2"r72ax xW*JFuZVE4cl$ QqQ'GҌP5},t;j+jDAƪ6/n8;B 4rAU|!\=y0fVV=ec;< $n:IdЌޛh娗GjVDv"FpD5B 0&51jI/Uba(Y55sf`$_NXsnnV_4<3iIfp(}#4|79u-CyɌ2iYz;DJ=w۔k|D+dEcN.jT:5ꀜ.*R@.3[ɆĵBj&M?|_.@lL0C:hr`mhܖՋZT"q sG`eQ70exX?i}8wzGe_#)\Y5)"KMHcj/j28Mi:{:M=FշxͿ~~•4Զhk|[tlL$psJyi(D}QćtϚf/e;*WROȃ,L5>O~ @Uc*yyڧ*BztB`%l7,R~?#ѪM_A֘:F5m]4[kLIWu0CW#Pè)AyH؍ M\?j{s}@#H5.KYi6%@o厫V8qX !Ew\x(HDfϿƁ[avedP;2=1k"-Na9.i^f{]%uY覥1AS&4ʲW߬wړޓE/Twѭ9ZJ~Sw;<m͔6ce$zw#Q^^jҹ(VWIjwdWBRAK7YWִ@6)HX@9TDlh~K&^ʨ{;*Ԣ;eWYgZ `c)yz('+XoЅBHg_gS5ĉpĶa`ʎ8babCu@*'(6 1t!;̋V·* D:}*maȌ<#,.qPO)MYm!R/A pKpV/wxvP+ԊmunX^\4q\H{Ҋ6\4*K͙8m`bi4V%dD [ :chuAZ@ EdQq §Xv}tNݴ0wHNe(T:[%2sBQ}Gs׃N.-7R\bc|QNQ :PUmˉ&K!.%kͫEIńgϷ`E/%[FS1I9ܫ:vJk+Hɣxۢ#5 \6kJ8aXvA ЙU4 "ʱwa8MCɦqT+<̧!F#!:;Hb=;%\'3K;>g_@ 3L[t% !D.&ݤuu.9tloK?0|Oj\AJe@$FTw_Ṙ'+Rq7;yk^-#ܴig[3g)KYomK̕'1uC2#?Igܓ˫v1Z;Q= [>L-}wnPe Ot"?P{Hte .2d, &MS-=HmW5pیVUF;o cފ3ڳe}Egy~jD[5s0g ˲ndYʾu9y43XWg2a_3s+NO/$/"~94 v0(o?9}#/R VqUWS +e* 2=0$d:ұ jn?U/ٝ3Ol$e0=խ !A  f)s7GߘpgV56*]*Dx-ؙ)#%O%PnB]Ư:LF?n$H5"9g˘/"j IvT:uc;ev {6b4nZXk أhTã9BqG~|R ,ij+*h'}6ZdR[5'!EM7|NqIB:7BU[41^ 5־\nY/x?,d9roUxyARj$XI#Ŭhy JЍoN/m K,'A9?1HR@GZOB4U (\D͎'>d9א? Wᎆ;Q÷c%_x):IQM+m{?GaʂmY4_ Ý}ݣx-lu) Iw TAal5?2́36"u3٠**,\e{mn]0k0I{jD(q{ [8A|',W=I8E;s<>;naD;t>%nU^8j;YH[^’{m\pVTQ_q8,U٤u~q6pqqLV#2?.'R yR]鳚ؒL _aoo$:yCU#IbvĐajK"1>1WTﴵ^FvG;wD3 ?hQATqȕ \ND03RN+0x85XŽDu~G֙1rpO;$.2HX /U:`8(i%4f✲reoC:63?c3CD b"\`2dץ&`GGDy-Fcm^wD$ZAVY|(-Sh4j_$yf_ <Oi^(F #^9B1!$|`;kY'ُ U_&=h(l1%Zn!4{۟cBDR ,qZοs$ UzBm kU@NGwt1ϔfCEo qb/:?!␣+Bmn,T2uIj"/,EGĤ&3"a𙙛u)8A,&Cp#`CCm([{:Bjo"_vNc)S \'ݨ粯6_"L'kNgU-+'ؓ1 ]ju/#&=Q4v4]%%eqrTGgOP#;Iˠ:@kP@/bO73$Pİ%@/r(  2tݵW:TVV07[V߰?ל~U@s* '/N-'[k8;w- AuI&S̺Gny0 hXh=oIJ0=gl hx2W bړV74gJ 6EDô;h}O]>\K/ct~Djjg-+clDol +IRwFIϥ_ NƲSwqu\kOdK4IiKa!u"m̌g90l{m]OR…|V=w@KMn; *SAfdbDf.wY1Z Iw1;e⩳`kՆ"E>;yd>h maQI5Y+` f's&\JG'~ƪ|yaw- qt@$TU!?IW@@~U`.ͧWp/WQǭŇҲ`> '0? vګu\vM蛍=f/߸xRUh14W~$*:NNwj+z * '@X@Z@@>3so?:_fz?ժ=^IʿO!Et;FBO҅~(8M+8%Fk?YTϩ2oAxsx'3/llimƬT#@8(ez0?A;.f k?iE zx2=K.Ӓ,F 6;B -vvvYl}K3qe9GL-F?2܃/mp{S+lI5˩\3B|lN^uM!B`~;vW^ވ>0Y?A5>#FnWNNZ*^WrS-cE==+d69h+4m ?JJSO8x> ҧ|ؐyʲMkE֜gOW;+hӀK&Rc6 )@LSBl}Nml(fo IL 4FZp4@VaC&zL^9&P3ѐy9Gs8%";YŖHd$0\KcV)SU[&5S&K>yk]P-B)sX0QGeN谓?Bs5hS--#}*f;{$,|z:u6J # cӸUOʧ-[Cy*2V=pOlS'^܈x&7yt,kèAa3ĆgRxgJNqv>p#kS؊#COW=@܇8 i.Ìp( Z "K(XL=Zt6spn֏"bٳ^m(#Cd󲄂=Ƈ|3X?*&(~]1`De~{ՍjdAnCg2 A3`Ė[5x3Q|fYt +Cݤ..7.Gk*vjޘcgB%G-͇%\$jz@o dd9E5pz˕ND`<]qz LVL+D~Rh46%J=TRѡh}<s+s+ߠB7@tjD!&X-[;&~He4T(F#+_յnNҝEB EaYXi` _EfpϜkpHqb\-n%2 \ q`ۙ2At4 gvd"uL4 N>CƞOxSFP^tK\ gqղ{3nb)ճB԰ڞcd| ރ궵k(( !5ێ}H5QJV-d!~3t;D(R}0j\P[!gw;W5Vo=B~IۅNL Źbyt\HSkrYu5xl2S4r!TvVA'nNҎUD ^:nAԶ+ ?I*W+d`o@ގF6wy<; &C 4Fg Y8[2^P?%烩>=^-^]C!< \N{ts3μ|4[gCYZS;a>5RMB폾2)I#b8r!U1mf(N/Z6GAP~DC#Sj]}۪fm%{>)mZhAI&SgxNDya]~Gf* tjkCLb@yt1X-Wkg_Ea-\dluZ>cj&v.ѧM;4eI9zB17ɂL+Ib7R=;y'fn?/GN :mE#\9Vi=L< ?1~HmW"Sm"68쬔&Y:De& $5bq|q(:Op[ʶ;>!Bqg920Vk?ZPCTx 1Ӆ %WjuVvaeu.$:q1 !-76w?eW |Q|5[فWHsO'͌iգ&DDBHG06&Aچ EJz)AC'F/'mbݗ:Y?AЌW tT9>{0ֲOQ~M*.(tǨ) c{Й SOӵs7Ee?xLmHsLLi9[E8 S90"Zֲt@cX';3E&i8bK@|K94*}(ŧ7B u nЅϑ0u82䳇)zT:8QRGŧ;\P_q_H-Z>=|oҧM&v\Ʃ\5| FsvوzbMmjs?MR{ORR`9 ·P)c6:p=<.F;`nd 5iF<|4q%mVO_}F+WxQxGL\*\sDO(n.bC/U)*]B@?xxrHN*+H&ωZImfܸԉMuYG |!LwYYf]+󳑾kPSJ Sh ?Ei".#)d*m 2A#~mÓS"H3s`B<r9%#ٝdy. }ը倬)!Hݕ ̈́Я=Ѿj<‡v2 3 {OV]@ŊF NRz~c2~Rr5ljpeLxQdtJx8S:5-It1eJ> NbctPpR4_m βxh:uY ^!> 4y jXm9aڟWҿ1; c#}xz a6@<;FSuΎ.`Ͱty#_vx-K\d8A,;O/<9Oq(*,rڟ4҆s?Mp8\Vvpʇm eW6u|mZ b*=mp}3<ᛩ: LI .-=f{T- +Ϲ(%йKC?8cW"iUh?v߆a_Oi2gc]4$hP Vl+^R|SvR9?|g4ͤFB|,늅\73e! ;W^\)KG*1f)R =t+q:uʦthzk3,{Aa?Tl4HVQb:E*)ٜ{_)vm}%(+`,3vd\ěƜo%jZFJHwf"cmp/x^X@[BB|$wC-kbBטu?'"4v6gȸJ oKv>m{"+F$Dm5CΌX NC[ NlYdmΑLC,4V/a*q! 5M28i!;HS(&\W?KF_4~c/u&,=֔A8(b%϶ K<Վ/fO 1f*d\?/+3}Yz|S ~IC b-/|{\]PYmeoq@K*w]EM|'AG{ʌbr%W[͍x6֢v<%.Yw/=~jxuguh ,h6C\m8 ϷwN._tz_{Q:iQrH*`#kZ{6ߧ=IE {5,HE1訬DɕqlY] !JUIaͺADAd5®)Н" zQa1AVȁeh;Js4~~ʕz!KZhWu7x?RȈ|p9n@,Y׍3R$7[h4ӈǸޡYtnL_*sJ0woʯ>U&-//EZx́] Tg+ lR\$0Я̯9M^qՖޒ `׺Kh\Zm@,PF CHYÜId͞(P!&Ag^!Qsh]obYxI2\Nwic$Ϭ$'9D!X˺8@*$ u!Io8U .ۓ4~i(E%x  ֔rEB\Mv$2׫_31x:l )UPLC hKQ8~O|e 1ViվWμHJ\ӶvQF/]7t7šK&kC?6q{s6yiQaՃw7Q}AxVN~'FG.pٸP!x]okPd@)ϋq4Nͥ[0%n&[KcU@oʩh}ru$r3#H컠 \ L&t-Bc]@S ȜA$FCdHxmVxz:$ȳWgf].*@E>#vpzmqO1d~acU8%inr@Sd:+oC``'/w_§*,WL+5OK[eJ0t{]8U{KaZi;@vNJa VWz؈:!CR|+5%<aLwOkIZaDr`ρFjE-om#-A\@~4ʚHv7#4o)̟R<.pG."e,t9}lDߣ-:(mX]9aLZ uYLer? `U}7z_^w^ra>> wJ.=Bh`Cvݞ eMl0}$F.::ȊH /:3t )iÏi o'f+,1RxkJ+qmܖ ^?0e!#S,3Jww_L*:Da1?|EAyMލz/>؇R\O$;0Ջ7r]{k7o }I+)dIޚѫy(̒C4*'&9>\vE%~ߕ阓";8Nn)ȹD @E5] 7ķބs*d@ AI&S;2X_,?k iH+F&iUԧ>6 {Yoݱ&h[WzdN֒_dPK Vq%5Gtָ GS<$G';4MF̭m%V3WeKh &`eO|Rq`zR(սRf Cg@}2zòYry |.A2*)OXHQ`N^s5F_ޜe\FVf:mlwi\2V^D?R)'Z\* #F0CQN}b.~]H>]|f̨{IO 0(k/lP{0;rӥT@P rbW%|wR3%j/F>\!I-&[ر{KWKø\8SevVOxN79{Om"N V8 gn*JY9 _AC)Ϣ fv/f>Ԋl2֟o/TfQLzpFH.QZv8Kioq m#JjpHH7.ks%,d{(`W?bKl)t@:~jމ/I Gdh7βS>sFzUb:{3q$#Q·R:bFO?ڇ'CdK%ۑu-dcw!WFcwᗅE%ЮD}7eu0S~ֈZ!\{QkX>+hg>o:.7W)@ǟ9#0}_]SSRt1||UbN >~ljWN5cJt!hA4aT)686F =/呍]|`u4LTb]cs ѯ6.3z=H,IAINv`fHaQ{@e%*np%16ګqG~  C@孫O|͔ A c.!(=~!Ӂ>a'fToby<1>ޖXDhp׵,C|OͲS ^NDj*1x3`r$=Em،$ wDKQ0='eQ@,`u]( ?OK-=Uݼ,s # \2QPMH5D:j60MϚMPy]Ho ^mLj?Q:4J6J%_oʳc9#sei8|[FOT#s`i6MqGxB";ZǦ^^tZóMQρ %=φH|rO{nBkn]^%_6$;`!`3dۇ{,XR:BetQb\*}[PVĊ>-oTkt]#l6l.̢;w#'ˉtU| Z4[{3X6K*(f_jg/h7z2W2sƢV~EP Ԋx-VHv\%|g|[Q(VNQocD Usi!$/tׯj~ON;@)r` ȗHG'E(> qOLmgEŅ@_lj|햾>!k ֖kWD> 9Oo-d\,_| uK¼r35$qRUc45dUbu$E8\&5)5eu ±W)w\ B,F d4 PpP{ yĖw9:C8Sg9r] FQTN:_ ]C!p]+궱`ς%3CbX`woRϐw$ݲcq{ ns_c1 g$6PweyIY"!#jPs?Y!tf)eFd7l4C%kG}) /v=z?aM^]; Z顝ЌE"8BbHe`aWk'=8Y#z˔qc޽9. gQXw;IA .<XIb;q*7)\p2+Ou/z-{=%;W莅86]!-ɑLꥉ14ݰ9z_)TyJgYL>:>cR,&[L<21zmɮjH"-mnZN*)b-n/^с/?wSu^| }S_v4__UqbљٓQG' ԭG]U%,KPM:XS}S؝hcKF[ aGz%~O$ ;/V&; -jTX/'ŵH _zɭ/-=Ov"1t?^K0bgѱ5(3P"uRҒ0v*S^>n3 3ZJ@N9Izvr@3VC#[^YOБ%mU˚b[j e\%mwSs \6giSc 䅩[ .(5t;EXT ]=D3)omu):' K_?ѽ3P 7:xC>[yȼSXKIAUOL :KJ~0HI{20k8]]-?!a=V?ֿ# 8&,g]wlFV TbW"Λ; 1;|/Gy&6whM30mP7Wb:;Sd!>bKGdUNI^s!FF׽O$:'#x(9Bbͳ1b6k: (SL'YnX0 庤1ӑfE6-kbU ٕ4W>αfC5٘.kj@4\Ѧޓ (wdY+hDz|Cyl6H'Ϟi ٢x K^/)&c 3Dy5;!)M\-tx@V̱,(N* E=e{Agk/iQ) LVC-O b`Sl0;i-Wb3Ot{Qv=†9-<Ȍ(h}-!hQx2s /ߩn _eo?"z|1z3q{3R<mv٬ R%Q7a|Ͻ aKqzg۲Ӳ!N >3('Xo洺h}`ýBkսg9I~Gk{Ď@)1 [C0Pn[ޱ̄:f-)/ߴ[P #Ÿ̹n;^"dTKו<4$iԢF 5qŶMX wA- eUUځm;ZnV#u)C[ʕ8H)jHĩ~ȟ3{jp y#s[m~nK,o]:Ag6Rsg1f27kw5ODt <u)OFL²gEOA3f⸨j} Т@g,7FEI:eDS/;.£ N)X,2#H4PD!8lvmTϯ׊_1b?z y< 1 qFrݔ$~o׋03czoɄNV`q5 4M_kM5(4':Wfj 8dmV`#Rqr Wq/PL%g㍾P(itPϙ,BZJǰϻnXaGdMߔ@UR) xڀed _3 |* `ݥdl Z}tCui#2筶$}VZ݃eÂ#^ e0Ҫ+:fنFHy^{tqhW _F9ZjtSv ;/~V$?v7}hx `Ϳn) '\7բAWT$G؊ [l3rYHHp\\TWU , Nu֧|#3R*_'!xM,?o;@Uk%e2YPضGԁQ8hҝ9H]6nl6I('=b3u?JmNc=Ͽɹ#$yT夊 4&l>Hql~].Hz0$r2rOM5 I݄SYMt %o5L5nbݺe_k}o|mΑh \wQ mL[ NHYDI;{|e>¨_k\Ǖ'u8>{+E\+r[8 ޻p e) iF;Vʢɀ 9ZI9wYQ~Tg:?Q_k ,~h^ S >L8POtJ ŝ AjO8KFn;-x|jvBBiz iD?I*5C4Mk7AT@crlM>/m[@Ty0#&dWdx nt'z5qHrSR9muOm #kt4>,e$s4ؖlcg]yPT œsy;g{RuͲfk-u$CL0X O Dn/-U>Gr06p*'3]Ȃ,\_Ŧvn( 2Mf!\4"}?IбL?kxvҝCCw.<]T0"𥊻O(,v]%D\F ׎.4*FOKc?F⼁z_]Y,]^>BS/0gQ3@ӚYA0Aqfy|h܋ 1S0HR{ُv\ZBh2} $٥:haguku=BshTe EMGUsDw07-,;2QBdS`Y_[TPU!bN,]NTk RA&fF'X$Si 퓖tyz,بEXε'kpTk,$7S9.+DOo#\!̅wN&Uo>vO0% _+1 -=JHzD)M~)g:leEi*Ìk kY>;9mG_UV:v1@ܫ4v&s}/Q-\5S6MqOYuj3+'g/rRI;a #h?L!x8dܿTF=ю:?/L@¸䊞Nb2ODUWDW> EOxyf"өBHCi]݀guٖ. Ic2rM'|E4!Ӕ֤%ۚFJ&-ɓPH^Q Fs?6d|R3nbt^Nq1 'Losݫ*[9f1qJ  Ol?AV 嫓1i\)V3 P@>(GRguW 砛N p埒<=b[DYH$z15j/!9 % Zڸ# z_QM NŲlOdљ߮_֖\|$u8eE(t몛~g"O10(&7G>'è:Kz Ϻy %r$֞Tщ!TH`nA,JRxfRyC9I?l^Dz۪ oH=w9e,Xjv@U+n1JE}rGl|cfL8m,Y>p EOmNJ,ϔ][tˋ(OD۟37Mb}I,[1r|ԽJ XcrGK>x; $Nq99T;ýA`}9A|"֗#g89vK=~5^vRR C28DQ%`2'OjY߀YAw2?)Z+D.ع|&_"@Bi@`.d*7.!7C!n>$7:|Yʕg* C׶n*cjH'BRT4TAnĥ]#LڿK=GE B9\2R&  tzX=ǧdkD˯[.xN?d.Or4Fpb{,{܂HߘSRʙȖ*<5hOIda̵eDp$FVfRWYBƋq co; +iZXFzPq|B6^IulO|(SDnl`FY.AOγloꛀU/K EQX\SZ?iM(DœJVFuz*U2Ab@v! d4` e[߰A9a^fEϷ_2QzT{(OՍS2. tA[ eI̧̽K 3tn@5q?4??*WQ^{kӕňGSK E|ڮ1Df2:QE3Ex\v O6X~!޻8{NnM*'Y8a=̀лz{5[K\OOi ?:D@ Cജ))J28?`-Jє0?-^Skc2٘rzCXSEFRxN5}侶aE4{206}}'.8U",ZWuƬjZ >C[(rH4 6 #(YEs'@ ?h@(9>7 (tYkT7'"_\9O。mT.'$}WKCfvX&ӗ <7)zhՕu <6ड़ay^›;T%<4ٳphMy-\|@aT '6,M+}'4bbu|Ìe҄$+IS/*HjAM8VݾAe$Q1ʼn) 0cY;RMΝbZ궣$Jլw`hU_#Bֿw] E\#hP*8o?jo =*2OG2#C~=ڸc%^FH/٫yaO%@qj%l;Y?UOBO?EJӓOihJp e`w0lG0{R^VA>4cd/U?>;m@!?y;-p9ZOp}=W.4(vBer:**9])'Oa j\\ĈJ|IOt`[rnIaGj7 ҠRRtwBEY~:Qvtן֮ OtM t/[MЮw~We4riKDg3 Ĕ~TFOkI՞ƵrēX.0sd{Sh utjÃO(bWdBBd08/栽go~fk]@ N\/dɮ/y6FE8t[K'De?~s&8dYS xuiʤD}m4<[Ey+"z ;yDHjkp#i ZB?K] R-%bRSlj|˜ BV9l?Hj== Iػ [d; OAv|;.^ARRUl:p7gad^AKFj]{e#3Lw7P( ߪUGk$$YPZN1yޤ{ Zq8?E+dbZw̾1)~|kJa/^V(sh@h=+!5){|;"[uqѳ!T8OK)1(z[kJGicyɪ(CӍi#Lh #lVS]|xj藸'ZKX'~|b2|mQ. I " e# )~1@| l#ngf5HK<7e  hY-c] (˚?w65a_Q8~z6j*03NNN﹕ reH^Pqj4lz MCA ,'v|+?W~xŗxjӓ9\]\`rd[ B!KcCݥa7ЯTuAğCrd,iz,[)UW q߯Z#ebđke?T)w\ǚt:= !®K8WrKԨgAT)B__7FAh *;70"ؖ+"?ahu\^+Dop+N =wM-A}fa+q,V_gXЃB$Gy;Uz }tm3] ?|F#+cX-h3N# Y0#q{|XTw\^ޝk fAx^άʪ>~QK"j:?D ]ԅSCEL nJd1;P!Ϊv}ih3d:I:}fVKL@:c~M8J¡1 n|/ [l^9f҅Ÿ2192D>~z8cXnW4,͂[B\%BnƖ6倒JIka5pfx5 vր- g9fk8g Q:+ )t xY 7$1ia7H`ìl.wEy('t?0=tET񤞗s5OĤvc;A_k?Cz6 /~2A\{JsNjk1gQ N2J<CJ| r,E_=4|w^2s {T'a14ӆOfƉ]ǘo3p3E+§p ȣe=NA!%$FNJMا^ A!lC?xKE c^%WIaB opDr, GY""]+sݐŠVCExfn^ [A54XtOo` glv눗_񤴇}ȊS'P9T.DvO6̮jhRKg2gO%sBAB!/2^>m!Y{m -&|:~c6OfiͳzC>Sz29mjL$k~WY*d RݯOrE:Аt~OhWq"*/ 0B#yb}#vZ1{ \I1۟:.!0z} >ߊ@IX32NKlAWl*ga,kB؞DNB¢ 8@5QBNyd/1:yhSԯul+#'xriPfCB $iƹ&ZormP4g<F@pyl/t M̫NEX QK_f7h.SO@vؗvdyuzf&0LZqX//aGrRlOjRcfD}3@5UD؈dڥ x&-9+ p2KbY̴n+ XIz,=ƀϢCB& r61.) "Wyal8'aJLЉ[ނV6I?;TiJ^!Ste|{K` ǽb5h +DEp0EF_\s}ɡ)lR ~U/"j'0Acj@M#;.^{Ǽ†GypjSNz_pMk|poN t-ѥiaar>Wkh) 9}6x@a\ɹdYؿؙJR#]dXy9Soa@]5%fwߦ-1ݧs}hZ!g@Dγ[v|2C'o>0¶1QU3KS®*cө"^p ,uDwl+iX1ϋsI+[t kTǛ$,#[we!@bZƀS'{ p-& D͗WY:c r[lRS{~x'*Vˁᦠ4O*YV>C|;e9kȇ:zvE,/( @@rן{ 㗯I)0DT'eԃ{*0S#5ntR 298+_6ܩ=+@(ج$@d0mS RMcx͇fDfy0Ϳ~!rZ;6 qqVDrn2Z g0 z|% J]7C}4F9U2 7^\r9,qS핕b|+2 `+op-%0`L,s<[VCH^Ր0rpA{[П~.eh:1 xO i#< gbMuJkrL4%֝2oKQ# JLmWznL&{K(;Vo8W*,&9]fM_)"}8Fd8rrO=zgӍ>U˓e9`ME߀Ȓf]XebOӗօ a0 &!R'yz$X|L_QB g7_ABdVeRo8DQUobd]춑neLu` m3usJJp}Ѷ*rTԳQ>]vM`r|X߀XG8ep)zYᬀ2z7Jc X ^#c -:'?/it~G C@yMaRQaY"c A lGvL5ثrf ~0y>Tg_! 54 .2 W;c6De(_ыl+s `;y=VI:P@xt= P5*ryPB>?PW0 =Px<=v,S\m ׄ T*e j౰%*aQKA6NT)j Xebd8jJ*iׇB9ڜJRХAQHo~wX DNUuH\z% Ri&4Nv {4: LL{#DigZr{xt]WkP}oKޟ]TPN* DPvuf&✫zGeUv;ة0#!F5;+]hD+9tk2iV c '>#C4IS MQb4W̓͝L(Ϧg %_UfV|0,*1[אַYd=k3" Z|{@x< %ud#K9GSe+)A AU85[1}i6@`6 \X`ɠ7-"Γ6Y@w_~SP l)Bi[OѠVo%Ez.">F. SuE!QL~n|GHGT2ʵϗr{YBũ/O}) AxHb!h 56Cxɾlٷe7,22qU׊, 6y80CV#n!k^Adm!&H͑)ģneW}MAzl浬RBm?[DS{;J?b{8l9L>ys^QEG&"2N4^gW(Cb0\N}˶3pwZɕ).o  ^oxLBULLGgTCN:E эj4Uø<*V;kmFEl8n}y%͝^ClXkWav(Alӻx"b6E 8kQXp{7+AV8 T‹>Ku>xURa&Dn3gGQ6ぉ/Fv8&9QCn@HWЄ檔\iĉ##ݡi.OP5^ ϩJ$.jdΕRe7j03 |;fkM=Q 75+A1mћC]֢,u50kN9II:g9&byuNR%.Lj5"@eҡghVB\Ҟ\u>W5 ^AIl_.ώcGpLMOcV.' ^|6 &L!'{uhk,#NEjWU4,x@ƃkjj z@'Nϒ/fp@[Rٓ-:F?˹Ȼ1S,_34ktQ&g8{2kP0jlOB8>쿩^%d lP)@Vdr;A۾=ѝ*K{+eŀǥ`dG5YLY,i-zOMs<(TEV]Vn{Rl"+Ct3p__DQaO zhS鍕q @w4|q %3,CC8~%{ZTGϚYօ!tі0ܣBґAF O^߃@׽k'6 k]܋)`bp4d9\(0/INSTL_[z92=B7-+WV E|9j!^Ӵ'"AIA蝥Ⱦśo*:rq3wUP8T ';-mףS{!_( 3"0h'ZoMAjs/vIM$x}ip?C0+CM9*3_Yr׶rL˹b<Ed`Sn=0K\۽#U[#7,{kX8FfgYV퓞Lh_mߪA!N Ȧۡ'bs0$wʗ2nap#,zzRjw3ØԊ uIB6=,vye{hx#)aqP;b~JDDpAyuה|BB"}횪HPȴ+cљ-\2)gmUHAg6h0Zm%AVx#ko|v?:P:ͭ6?H/1jj>Dx1:LO-Cǫ -̅Kj UCe)&vG$ CJIXx7qe3ʧe2+ {sۅo}?/dBp\WLyguHU#o{m)YG_䝳,~pbj'J^#O,8^^P?ak8^ r񤥩r\f[g5ܐKK!2('lՠ)Q[b?a+-ŝF^ i^naǨ\)W)^輸 q%;Pu$l s3dAGyTdS_àZL.hi6f'?l3NOdij#qXn Q0e,%n_oJbA1}"\W fDmȘV_0%& fH(1֣Ed?}cs] X%8I铼mXl"iC'`#| *?}9y;  *, Zh>r'ԥY=QI=;fN Z~5=qBi7eDE2;OYLjٓ:<`U\mNRFgY.o&7BΒbA{;?y3urNͤQ(T|1iв\ZEd %#S d`]j9|,-@-)Ӓ]q`CFpEo_^q{N/۸Je K2}G2gYGZ6gٔ9` _,GQU*zKq|Y$GAj@܏2z6_⹔`OL GM> ds),~-| S8'3bk9BoOsL xټOOgrHetMp9|%vYvz(gWQK_9PN? :G#v#|Xc|`$nCܼ"7O矯bz#?)#M!Yl)xx`:@&䆍o /BTμ]+9ǰM܆Ch<9 z 3JK \Od@m\›#\ 34 AuFP&˷fܯcSrW5n{S} pX9ĸN#{)1(Ux# EmU}.|Y '#<`}j YlRocXs}< UΐCQ pAdI&S,{S3#@{rMZ)"nP&spU=T:Ff9zh"Ji!1uoST`0* Ax\qBRCGctf+-SlQ#2罹蛕1.|+vڱ])V@$vF0-%t`^>;$jrM޽\McTz3>`97Bce7L]f$ :]׍tEXOF;Hpa]b{c<)z A M 6JphOgcS\Eтk`qa#%ks=^ˠ!nN<;||ͮsb0KO?/-:#!}Ef}-Īع(S xʞ>^;[\LvCUѵǀT( Zfh8o\\N,1!c7̷nܸ.7u-m Vnsk~|)LpNJN _^!G$4v@1K3vlgh5ͩB8h p, 8\y4{Bڲ75m`ݼj 2eʼ"=,<#`lkdn~8gʉ(>K̪0*;:&j"RH-pCا&V =}CRNbU+"p$2=y d,aeB?+x28N / & לOwK^mJ{64S&S9Ύ0ăknJaW02:5ˈWub I|αmQ qHSZ⌟{}u(F%#hdPDGgR{(k ) 1y)!7Ayi`1l I{E~^&z#[.=Q\%ET\R Ϋ<ӈMff<}uwG#eӀ kjBNȴiklVsYwߗ$cnN$bӭ[UC6E64-O+;O6L<= ӿrN޵Ej` |@37a6di7XE E>''XBY! _y]دP^[B%k$1raE@nI(ӻƏ~kA-FEϷڞ1|2IJ̢ҕ@}n _gEXA݇kA{ :hRQ'f)tbZ&wi1mJݞ ҄sK.{qVIHWF- d%e~?B~%r`2ew/h N~A1+nΏ &4W,u`I YԶ3x~M aɮѯ |1܍ I֓\m6=̪S]w,x?P ub~RA99b˚e)'7s9 "t=a Hfw͇hedkхRZs}ޤؔP$IU]W>b6((pE s2'K"5n$MIPr :ooʢ!zv鏒20h]Wvgplj]^ʏ,eRwj(?tľa*~UTCĸv{)hhM\U]O`0)ۄ?qׇv !4(7n!}^;V yh'){-(9mf X*?8oUV?s8$y[|'|BCT|jGb/ozM TF^R$gQ{Df{ﯕb؇n t+ؐ٥˧8NB5@}!'ɍ1Ue5@ˁ AI&S8@ Nۀ?&s:\ed1 WcwO,/֗[ˡ8+gL͐,kiCoX; rܹ˂`^XZwmAEb"C,p&1ޮJ7l/o<ߐ3`E?ew}۩X@Y#9 Q6-1S[bӕ%FOU ֥16 _ J.0Y$'}lc*DR#o[<9\rc2k6? Zt:G6/_?X re84%[H j Bt`z/`PkӼ:¡ -H& 0eF~=rjr 'M2򚷔Y)>ASG8t._k Y ! t r(1 *BJ^|R/T|"nJ'Fڍ !g\WW]a4_?[(mwF?J5 h/65$}VZ+"c(D뒭C d285#Trڗ|VpQ ku5@"i}ij/$sr妘TPz'DZH@35 o72:j;)' qR)Y2W*ސbXЈI {w5[8ȦOKXDM"XP,Qm,4EEXh4[SԬ;Jkg]IbH$*,"%hӺ}sl 7&Y L!3AH:"Hl!S<8/ijZwQE pV+iqƃSZK^f$ SэՄ"\" Iq."_Z Btd^Q~+2@ Ctjd*L5 6Ԯg)&HG#z-Ԋ}ӡF<$_~8Iu̍\C$qt" A2^7fquMWew6Iӱ5o81M~F`]ϗLd︶ ̈8Pݺ?qɋ8CoZ[v)ϣKtsׁ9wOo/;!sH+Pj}u;7|+8*~堟VVo}v0bsfmG;Noj՘z|O}}џR܌r|4]>L\$s-ˑ_U$*۶3-O+~es+?y3I,5u}5=@2Ƨ\oXˍ2C `%@ S#w.n?\fV%牱XX[Ҥ;BCas[WGWZghrR0g lֆB^w xg^tڇ_R? Janewu)W7OC@%OD ۸'Z5F0@'."IJW - oQxÇ *{[x6>#t)xnl¨qy #]\k(kQDeq-`J~P?#= AI&Sg~pmW 7Ë5|aT !]^LW RŠD5{rnBS`1`#:DF7}6wٺ 8MխWe:mV5={[1옍HJ/ޯ{bp(ԭ/t޴ieRG6gXR I/޹\-"|ȫϩ@fنqctNW=(||:Fgx@ V2znЊQبϒ--7(?/P),X'Ef*+Z Yc;ݺǺU-Cq!n; W#)gE'Ulh+ДYo :F۬e]`?٪ PbXymҵ D}4dwa_z%l=R֫+%Gv͎0glVpBωu.|"X,mKRGDaI0UH%p{JVu8^S:r !л pGP6zm ͚ȧ7?wi!yM1zE3é%ೞz6XU$&;?]2(.D_ĈD5_6:tD}~EۓKKfG]嶩ͱ, XHnz ͸YoROLdj!΅k)vM Z|$lq+@w:LW``] Ҫ2&lFtP[J'Q'poҮQk'2f}_V5- ڢ0 ؝W]TذiTn3ՄUr^@&!AZD>W† ڥt %[hjr~\B:(fE.䑼{~1~AiN 6n?w׌tFo`2xyf8kb24>1dXfs^ATdLnyun2X& &skIͪ "Np(w]ƻ@X13kp|z==$7K@."{- .~驏ߗRz&rA۷{EE6e1 KҠrO]|'qxh!!B<.Jyla0Hb|9RȐ]MwϜ=AwnSyiXʝZQoFH'0^F6ҭ.|# L=J- ,y$A5AX;rjj "̡?]-75];7PM7C7-7o<$Չ^x ٍid?;'W`Ͼ͇]:哨v@{t0 xs;T3o> WlmJ1ޥt$pqUlxK=Z@wԩퟙii'dpCe犴nznm'"K~0qnU 2.vUPC$hhXgŧ e[> g:7*K]1Ą=Ը`[vv6-8ExqG~s(p$q]C3=#~]7z3qw`N(ЕDLX)Umty/?](smN?k:#zBWU&q?(k7L'я:p1xc@]P%[qН1ZʹHw5Y]S xtcޣ!xeIވu}s+?0A+5gtް]*?W*rȄDl#Z!FKFT-6n*~IMnfuS.1XJ=o?^f,:=!e1 v;O%6 N-zk=&5um~Y!I`}NluHRDX=+Dk'98]d8߼4^>OϪB| J!~ mkH(O{Dvʱiΰ- <  lI#ɰՆr*6si퐙@2˃J~fxnizˌC!H&4:OmS<;D9voc#D0{msY8$z=),g9Llv\ yN8Hx.ڎ<(7LcߺN%}kK=(|:/!17RqyRڊd{Ng}גX'Û,lFZ췷l! o̫l*51E-4N:.VN%:G @# L&q6ΓMw 4ƣdZ[[o`m&=?89vdƌ _PSێUX%EO x2?yeؖ$c1~!W,q8Q@UEyhS59բ8277vMUu$|v) MLMryItK f4tvyIYw݇tgxg(\ P.76oJ@eXR>WoOⰠכ"^x{ې J[9Q8;EU*^ II"enLq. t֢% О#}o7;-H A 7,Ļ|=H*hKK GwkZq(r]_)-Óށi,`Ϲ+3mHq@]IJ36L@4i;uؒw,4U 3;*&,*25=GI.AI&S8@[7nF]Gbߣ  }s{Ab4NXC5I%_~TC?dt&^ؗarC̴|1ksU`ϘE?N3s0Qlф@ ~`;4\˗]Wmo~ggʹ?I1LM#f^x EO(b+H\$n r{1 Eo_>!8%nl290~cf!H+Yc}2~fex&:zAy$HSőP : ? CDq-ZGp ʻi 8 NFgI-k>|U-)B0ّ}8gjC$pmYP_LM4Gb/V~|rޙ * ~T##_OMܨRY -`ԍq-:Qݗ9'jF~1 *?+dLEcDzBw F& 0ЄGcd{.~!gXsi)Hcr7kD8䛵b|0}DOWkY :#x}'%""R\ڥǒ#h};)c#jPǘ08&lPG aϽ3g?`,u|N~j,uGȖ05r7L_͚so2K_ι+3m563&φ 60x(8).1M-`8K(yբ8E2REWEA2ߔTu;xzPiۺF\HA*nh0•jm@;3ʀ ķO$[x\/HJ5l5w.Y%^o#e%BdAAFѕ'eݼcܢ%t8*Fy$QңYјH,pц;,<wYQktVraJ<)?׷wM٩=T=ʁh#(seyCJ?y%ϋOx=nk]1mbf}y^  V- ?[A`;oRQ@UǼy\%$9^.:p]x= Z;}cYlRF֡yKPvJ[X7c0 hPĂh(:9>|ab$+st]E>vT3+YM 3y~wms Q?wR/ l5ũLĽiͨAa ^irBiB7s'a[l ܇cMf$)&S|5!2 \J(jRLjJ u0|X@yRGd+Y,"yDu4JT V@#-"d4aXq>Aw"`,GP*h^l0Z|`Tk9/^ȹQ(h[ @ԢC|c@6Zg ʠqGvCNɜ,=)9C5k19UZ)k jTp+A!<21;tb,-eg%ʨ2#no\N; EZLֶۈ}Ӄ;sp]/7 4dȂoNp.щLW;A۰BRrR?wivEAI&SZ]$^y/'#׉WP<-݅{z\?ҫԯHdYy&%(@a͕;' l&8>Pl(_dQnM 76ܼ%LDm]ɟ;nE5mqalG;=-V~`Q;%C+0,AOb`v~gY ;QljS +r?Bg}u|U'#7ˇ3 4,sMPCOHFfťILn.2X5!e{-H⍂Mtr8 ΁O }mC|NkfxmafF _>0kИw]g̚Pjq; 0݂&>tf"m3FֵcYq΢O6<IRjB>R(Bm^E,uW@"Y4${jP%(d?j pq;1`r N}-bH&!`@R` MA|xdh=wAU}mxĴMv݇Eƞ5_m Rrc21*Q?/N{]Hee^0:dZA9lwgh]Zq$QCϭB[ZL@:f|evK=hU9},oE 8QgX;z{.4:D ̓XhK LɷЋd ]ח6Bؐԍ2#fkoh~qxfenY" }Z@9٥C%Ob0^iJWĀb`+qr-L])<۝Nߟ>S_'ncMA`̺M=n0KX+UZNعpK#W+%=wb?K0-6WiS=礉;QuώjYpùB!fLX 7W!LI?I+H 0p{kAPbeh5/?X`F.'9~g'(nVk/DSӹ rUn}[>"W6Z8 s&YPh+ɪ󳥫|]D>\M5/Z0u]6ױgu5Y\n@Qo;8yB_zκFKMc97ZǪ/Pkjz#Cǝ\w8AF蚷| :ۗ{p&ϤCi0Efק jkҰ'bT$k\?ܫ`i0| psF7@6lgxlU z7Xc㔣,ѹUwNf", MocԹdOPəYPc1XN'6Cg&5DjoP6S[,'R#@ޏ B_`bXpX} *zy!^aiFd-Tuog3&g "-.vaY *9NL(t,[jA_ŧ^[$72(L*T`#F16o9`? (g Rfi!  G%F\s>i Ѭ|`N]=xؖХZa#ұ%b) me _Uil ŔwV^2(S{&Tbs6tuKB|ˌ+$=#Ǧȹ<uW;YdRkgEN۲)"IMռFώuE-d">k`*Puq?ybx|?Y`\MXŸ 1yjЖg;wu.!@pP2 6q<ZUʙt_(r@LiLsݢCtH' Jㆴ0MvP/c60h6uRvlw?(BQ%$G4I4zοz*3ŠΤ tG#Ҕ8h`ڱ D:Bu/zG/B+\MGL8)B|dU }\;#zKuwaXz WH3 Z2j<_#?Hi-Fp` ʈW-׋눰S1㦛M m4fA[mFZj!kmTl=5"aUCy&qOv0x}5[ {inaU8E>BKM[yN$_,W#Lh@-*`I DgG]- ӀM GZ)J+PHas+@ҡe;CUZʃvu:a@O}#[K. ȫLO>uۑpjX>: hmG(Qm(zd)ߵ عn sF2Pt-;@>_Ԛ&!Ycem~iuvWAZL0ct_'b6&JuD`#$4RBI:; S"D( yϝI[MjВ,.׋Ē:cp)isԥ#˗3">xA=N/ևBfOe^ O:i7%C⺃?j..(@xR&rL(ipC4>6>w۠w/eؾMdk+\IzX3ZCxI ,@ywNt=zYv3r͈$)#}Ъ/.ntQ:t'1`z]cũŃ|>s(V~Fwf| GΞ[ucE^@Hጟ}MrWA :c^<`8L#ny61mV0sǐA[`j44@H<}5֑g>s-곸lgJ+8)Q$Ncpa}-رm_w3b2w8NjW.+ 8Or+ouU D<}}.v,4(ܢ4z–{/.6 1h޸U_Tl~oKc'MB-%,6 'i &)8D䯽qi qf& lZ"=7`8M]%}rWf ѯ&y=^u(̹xZ<[?D)yZd!tnRS=كKRǕbȦ{LUJR|*)j'C !Y16Enp]+J0!C{U}-Oxq~ sy/ԕ׀Q4VBen/w|X>he)_.c`J^J= oa[< 칛mzXX>`3 *stC_OuN |U伈Z!P3~G6Hx Q! JOkVUko[N;e $Gb1ҴAlB{=J4R,Az)jBC2oj sBLbUFnYtX: (YX%j:Jc g7"ildm=WK?3%XQUT]$L="/s3x<.$p :Ia g@Q4+e2HY6sX^ ]kٕx0̍/% oajoްḵdIIi?1Eq4Wc$FFRWS$-ln ~8e[R W=ޝ b)ɇh b`Ngpg~M_#^Jh)҂%&7PIX__DPzH~RD#yfT§w5SØ`>hX\2O` r㪺#5F=xhԚe;T9#@pjm,$AR,[JP_Twx.`JԆmNǧ'7z>$`5}.Y2!j=ӫzp$i,xSӧ-ͪ@No[bY"؎jflj62l:\AxuR&>}m hu0`Tnåe¦Z4Eд0V +q9I6X'r ) vr`C3h嗋oj9Oj,J_fT6e$*muZ0+8%WG$kT<6hɹ2?ə7>7 `߻+G=MQRoT5J3mo!3{YfH#¡W ?YBL/!ڨ~S=\Y9|ދS;X2̯~n4mD"1h;N؎m[|i@bBwn/V[[z0 Y1-0B6_2` f-}*vAK )r} ҅Hc ]K#n9wzh{\J0 jx_?}ɧq+SQb:ͪ,׳.R]XÆledKSJfrD8#D}8-I8xuFҦO4ti|(*A~3y!K\CFhB{ow8D?9tv>J (FKcнُA:s?2oS3e 8S '%k)ɓ ܁zHg~.PxݱrlWV.ٸ@8fPlwf/ MN$*8f1[ a2.rU:F]n˪܂p,=k&zs6gQ Ч jLbEhCi(䷟} gk $N i 'Ux#S)4|BtJŔq2R EU *0,6ベ=34@J%QXOJv?f5' nIr/i?'W5w{$O~Lk,mhZ2ɐxhyu*K'¥C %S-kjE6*d|h"8G *G:  wY5cFقa'~AŘ co_ (<|\U/QŶTE39pTAؓ@k#ΜPpAI]0U[6rN}tZ.{50;^d&>y0t0m) [2˭4ꚁ pfl Bdf~M/.&1E|C&A{u"*ryg IWeֺ`ʚ`4"5%غ0D0_Ȋ 7D$;D ]I;#L3۔;)QԈ~ۦ; _]V?tQ.NZ0ũrR s7pzLxF|4H$^q]ҊCo)K3sVgį)Ҽv+ӷTcD^.8s:W!@ \Zs1,2D;Yl3F?d jԄZ~FeYc K㮒o'&<!u&-S?'7"6P WixKB7(| ,TwZ, m+бVB7Q[UDfLW%ܛBoYEҪ gM#N_t5%L})) QlRՊo8|kcYA$XWwX=iX' hW&Mhd(;\F+\6^z\(۸>^[ӶvXq:!s;'M{# :T$bo똷BnT, ʁ%s ~[9kȸrOv85s2gJnKb "ŃF 0Zl]WzӘjӚ/u\{'Փ#BF'l_=ER]56^=%|Ǚ8sP!NN~vlH,N􊂳^-¡x5 zv-h7m+r*4Ӹ^LA:5;߉Amt`0TͫㅪG1F{ i{d?`m&$;@{S ԣ'^wGՒWd}Oӗ+-K + H/-C:Y#qZ$Gtew(~LJ`n#J>Sy#YwH,VI%H:6m1x"X=A+n3ě|T@= XK LoJuj=4s\@ujD_iG,,ܨV.6ﶾ"l&㗇Fpg~\pj&ky*4 6\~ )T{-p&’ 2F R4r1 3qOujΚl7wWzP#U*z~s7Jg'/׭4oe N2%y3t ,|g>,p^pjn I53= 2*p6tAbHb;w.mݐVMu!]m@Z0A͞, (s9Q%,C9gV+{鼃l˷7c)@!3$ NNV dnx'&;5w"l74i@BCdzMuJPRV-2ňzOGhY{J9;. 綒z4Š>%.%*"擴80kZkSmקkG&z,#@r[="[ "gewC?S pV/QVxziOjJkWl% wsò5`t?:IJdczwM;y0YTJX!~v2YBx wtG~bQ&q]Y( 0ܰ X#YzXXɸOH_ȶTDKѵz%b3sgY|.ZZ!8}Ir9O-Uq!SǚUi<ʉ(m|}gJ:X)PcޫBBZ@se=}{St(EzB2T,!}JԂkRAxkpV_MYfO2B0qp78#.9Ije Ls<9* q)7Vr%)ū-!5gC}'KTM#gL*_8gpsP$x;ާ;ו$;4p_s2ݝ(_=S coa.8fK(5'@ ; ]}t,hn_;X5*V0CsB7=:[E`&vW_=ԋ#)I6^:"# `%oT^6 Vęa:QI8Yn5ɴ8A#AfHxdGn"pT~?l` 4&SPf Hv z^ARAE`\6)9#]Eb%e;$.bE`U鵹B02ͮgKQo>5~tqv1ew:+h}dfӨQs>ت<(}r$0v+Kq%T:2fNL~y@H=&blq Nҭ8PRlzGZ)%@5\T͊R?M <&E̙ҍWӆ̬JF @תk NXVdu 0Wc8յ.4C*tyBMdf5Y>aj)2]kD׻nWbM'q78.uNgV*`[dø%Os8OF""x .㙾IEz-+cc::3?4_"[6Y-~bbJ~`L2^\-2uTlcbڈ/O_&s$W|BH"6{?~jol lQ 1\6$r._{q4=P/IحOX9λ@3`oiw<GddM,8j0)2r*`_x v+`RQy0= Qr';t_>U>|xiotؤBEIvP%|w 咳q6jëP35*2ED&T}|y~F3NqehBEpthEͅ/FM!q8ubOyixعCTyAJKFA Ŧ27.ۢ6ի:YS]~3I )=Wg[{: )J;*ͬ>y*zZrFelAߗrUqG, /ep8NGc][`NS3&Y]S|Zހ_JS>Fv?ݲ8y+k[sZ;6&CrOB[z@Y6ıYEV.Ng& WX՗ /؍4DM NaoV8 }O5|͊޲Od^V068_$'hӴ)d~8S&`) ZsUAg#X}`J^(E R;?6sOҚ+(XgٳK:^7O?eJ6n*őDKV #YnD-F69GSr@xrIMI6mOӜIsIkYm>b*]p:=&v,r(׊#/ODƽgtTakYI;Yv}2cq?;}Zs`2L :g$-}[ {f9IEç6z4C6n8uNko)DYjrkw~>?;&q`O^Gò6-(i fD.POIY~] Lv3CQl}w_S;x.>/ƶE Y~;.mILu"“]^A{_i6Sl 1|VxUx\Ȼ IiVML!VIy/Z5v'P1H}x&LSFԺ>5f.%{\rxˬ 1,dm1N52Hn|هpFG Z­3cU!TbGX$;t5e؞8ʮh]+ň#-M9dOY=/S`O_Ezj0DگCuiSX,୆ՕñxNC9!u^]j} q3{lg *c.[2ALI&Sa=Jz4#~;[^Xo'@>fCkl!yE? dld+@(Lv wQK1gV2C"ӨCG|fa4&,,uoWSas;ӘC#WG%T1I׉f<<ƷtK?<+եwœ8 7.^B%i}c~\#=_rm,{6ST+f[*dXImVTrެ% 8 % *)2m[W!ے^H܅0dh[(:.0^͐h.#zAaw{0u?*?T>Zy57PH23ں(DF({(P!՜xqԑuh_;Yz;>X;,#m+"зBGY-FK_[.ZD=$JP{q }UFG]pu̙7Շ`[޵$8$38e[eAq Y>[:Kwo~H&)'D1s9UJ fal׽)g7(z &_ON`L2Jg̖ۅS' tT-3vF3 䨤hu"CF 0囋sl':Xpتq~ij6 ФE-kXJMW(4mkcߦPmZ$~@`׌0Pg2.qY"9T,*mD{jːe"6Nt3.m |D(:Su&ܲv XEc٬$5(fbNq+E9HBbm Jwp DebjuR)qBGsAY^c,K kT\~yo[wTpĀ&OtKjƈTo 50+j֤&'9rc7<9\-*s]L}Pu;2+WJ%A㙩i06fMET4"DuǮ8}P(.ΛN4y}Y!~rҫ-K:(;;. oµG&ˎpq6*6v.LZK >[!t #H8kis$}1Qan. 2ȵI Q:L?HpKQJ}{/Jk#eTD:}"7'2Їo@y5~O~ L*\xz=ോKTPy<&+aRk DְNƱ0S|mt7=rxli .BsǸWو :l]3'T|"p믢uxƊ!sfl\xLK כJ4{&) >nTc;+ҽ!_F %kMya'Ϸ|eOzS2Z_nO_nSg/:>\89?xb:ѰIkm4CS=HXFEwo1pqRB9BgV@9nhSQ|M(ҚJD,dӄQb\#Xbe^uCaթ:DBX񸨨42kƝRv4׸g[&V9?l[f=MH{pgB涠=7W`o<29;O#`W|VЃgZ"d[ȗR3L&Գ ehN? 1XqR' NHsrHL,f`ʪ1Qa@hp,m."l{Me,1&,r\u(IuaM%#鏸ƪTK}kvQl_0%#MjLU$KEKle*Jb0Awy99ܱ{jK1%<'#45Vgs_5s%%\\ɘjmP=ff~ӊxТ3A5|9}F[밒顔4/G"syd٪?0QjrEe3,=X".S ewRmUn}%28iy1Ƃ*+KA0VSQJͲL~dAEV;4ոYߌF)kLpSrR)%miNGn) _60&OZ7Hm݂ 5$QaM8wS[;+Lwln,C'-%YS1C*P8k| 'bS\r2J%شg[}$rFY hc̗ekۧIo)?D6sŷ/smZc6ix`b?@Z_gȭ&iH."X^ZWf:>9{k6 F F`|Kj@DboG%VTT *  FYPvh7e9(y ܤ0#C̓n_ke6i/,q"G=Li;Z}Y#){7A5Mȉ0BU%CM9\rh+~E(016=wC63 ?.[cS.zu#mO*Uy숈/r 8%1ETeYxi=S3j0vT=HNx{aBCGyq2\Y}zS`]b a9Z8 ;ˁg5BC*ﶟ? UQF&fekAA4;lmwݦ1vHP^rƀ&SQ<"INkH/΄MABqoy§[Χb刑-@ ^6 bӈ:jGzY_Lwr 9i/87>:xFb[ Ҡan˧Ǝ>yuSܲI,ugz^Jל{ՄR#C4r' 7eULm1L7%I 3״XoC& ugQQxW: |dk6sa?mz[rmߕpk#y%$L=,OF/06]KPi_V|3ʣK(Q{񓞌 j+Nz=V |<ᭂ A"'ՀI2T} mydƲ4d<LH\f&jr:\}*8/lG \#durXYw'bYl;6db+gDޣV2bTe+@^mQCU^C 亟˥/J2Wl^GbҀk?B!9;߯.d-*P5Cu„IVm7rjwQϖBVA$g/B`˳+_z&x)9DfB75iVv2/sPh9ـA\_ҿBD.wDJ>Z e-OPV#@`&8mbvn/K7J_3 ]łF;|ϙeqG ͕.wwв.δɣ'P .8%;W#qR҂rsjUZ8NN[om^P29T ghPlERtmV3:2AV4%Dz*=+ܣTT4ҍ,@up͊02Sl|[!?"Qyty HV(gzryϯF7O$ٰ 4p![#)/"(5Lӡ5ׂJd6`me$ AmI&Swꝇ FvSگtT&Ur輄J`MNԠGэ`) m^aHu 3OV>Y0$NVz5dEejkP]r ,9$*+m,ߧq`Vxt ȓBcTjd;<Ӭ i'\кxY"92w~Au?u"A#0`YT:mbTca7vokl!{Z6ᒀV:Cڂbb7]l&)BJ~MQƥ,β&u|\d* 8:b&iƽ(nPTK@60F9o |F"FsuKGUv]ӿ}wrI2OA9z Ĉ2Pp=&ZUMUC6R >2uبHf$FdMY{g=M|iCHgOCXpF9A/5AWn9OptC|9e02!à c0Rs# hR(z^ A),rTIA_cQ]HgȕMȰ<7%]K>_iڞ6]q-Vk~>rDFGה!X EU-x&XrZ5%"\jDOu+RzÌVilb`l@ߎmh?#/Ԑ '|3?W?BU5pP)w#L]sT3!@}ǟ>̂x&۞&'X֔ ycQ{-Sk.DGoȆOgSnSzzZ:hafFm2 H+E'ԣJ'[YA D[4 ָ3I{{vZ; Pi;H%Cx<2%]Qm sC?Vhz'ȷ1G:[X_֌huktt &>| e4 5#{b_ڗsϬAcN(n귶6&j G",JvNJﳎ! SH% C*Db AI&S*oAJ %2!31M:*"D/GַOZ ~V+*$wh\ϩRZsu`H4^_.W:NPb uoX3vXVhyAѤM ;ҹnđm`~ 'OHn,ZK?[tt/lO m'UO&dH0:d=-'Sy vԛr[R6Tuae&_>r({ÿͼX"LDIvMxt8ސEIjW XCjoL|UM:Aqpj3]"{ErRyئ0%$_RcD8_(cwׅ7ؽIGrK'L=!fYN*'xFC:v޺PzY\G>ؗo:JteLe{Hp7qe2V[kQU @ ZjfjDl%0:Y.Lpezs8v%L!q^,8%EժpGߋ>!m$ ET\*r015ʈD1h',1QJD"7)P%1٫]L75-HYW㮱(5+KO\ڲ? ݩg~7mEc5[V q E:S_l4u P} g]Psmy Ɠ9E^麯 %ZUm^䅑h[]_Ǭ1t6F{~pQ.sS|qÄж.[1%q't{1 vcu9{ᵢ!K~ n_rCxl;YZK%iϧn,wQSϷ!];&4PPj&ұE:C/]0i[j0v0en*ps8sI;fmXmw94Z]~)t굎ٸ14GgAڤ s{3:SǛ&'FemZXGn1`cfv UvJ8462b^ I5(h LzzOː0vŌ2NK[&kU;TȲrǓq3?)乀n2a>N  }&$+갲!3)UI.IZ :X?*؂i`1˿^k yVph,Q];#`$c{N/h)j'Nd~мZ7mx~Bber(\3g"t&,~:|_f]S(w;&ma/r| AN>=eiLpE{0X/; ۀ\,&q=;.Z7A-Ihe&g\Z[Y%bu9%)l[ K-KC_,l"55UXØ#0d(br߾1 ⦆';)fVwat^harV/YxawYu᠖f4O?#}USd K9~& .1C$A9wo%Eɭ%em^pX@ [b.Q࠮mw|Yo![?cj [CP D(0ɞB]2AQ?Uŕ@FEl1Tw5#7ږI㾫"0PV&2.TwGΫkη_w/-1G:)y AIvz*= ]ojCp7y*Z ƲJ dl+yyܵ ium0IJs]Z-P`bSk_BWRrٙ0QZϮb\Rs.u|~V;ǟp1 14(7/݌a#쒈fߤ̬Wd,Q# c*G&GLྐh~sbTHn=6/i_蜸^MBƋ-Ĥl˒ :QMè0Hl AI&S;=hls ^4¸&&ԇpb}yMZ G6fw=Ie.r!U[p!Ş{m4Ec,C~r"1o74!o"8K-ae3d[ȩdsr__:=fDtk/88-(L%.+os^5WV. el8/ xЄe0.1,glnQ=XCyaMZT ֮KzrD.\][c-pt سȜ8YUi4<:܊c>cOE}](C04 92FT/7/YrCEy-),,ǻl/_IH_pZ+ΦW1/ p߰SuL9(8 Iv̥+50D, >ƻkz Pb?ֿoC_{I_I優)|BЅ^JeBP0s j@K*cW ]aՌpySb?\4uBEoML䢭"2.-|;<"tUNSb!F <!e3=%LK٪lnW,9HTCVp63 6vIk]ccx&,?GIfp҂ b-ze4iѽr͎r7WjoQ\=}XpHOp>ҰV$Æ|Nwk:AC&XZ0wt:+߈|՞&.8}fӜǩi/QKzd_3!/uiT\i$٭ͼaűTbMAg?.7v2vFI ՆPX#?TZ~Is+*Pd躛KFt`[GShK|-(FFw2R6Cdn7qwyj8~ yl)mšEK+ G9= *G%T{n/C[,^uf*UqKA$ˠVt٠=YE4&CY4Wzr=%i{a!P*:(Xdm*vѯixjt{]FWx_inp}UTﳕyKIކ;!u^^ /˫<sVžw=٬d_7DlLFE ACtzX&.q} |xRmEW6'IH}ȫzʁ=;p#`MDl7r[C5tm9W:>m$s ci٩r& ݞ-yLHvۖv!5& 8S-ͫH9U9o@H) |G4Qߊh:4N?#mn(Z7imDZ2N Wmmդޖ{ |$/~p NJNES7Χe+J,>'#dB^ €O5l|' FӂEn\ *t| utV qPSkf1hdo0bZI'|cXR/3xvQAK)[-.b׀["^ʸ!A Im%j葅?p#Q?nu W_f:Hߠ>) `z\K_^yMK]ekC3>P|~,埉X#I^ .2?!~", p( 2.r/7ZU"spFu[  W\Dj3=hI\q#RIe3w>֨z<D[kX.؆W.JإhQ?N[Ѻli^|SdOe)n` + Nzީ{MJv+xJ2S-c4.(*&!O"Il16 )]T!yI{6ee^xvA| sfX֨3ˈF1q)7*).R (E~L]WLȧgX wB(!;V /ʘ؄Z൛54ba8)r(ҹ{ÐQBkRpBj+i0 2X!Rҥ3ƳD&̋}A )efK<J C"Y l"u֩ۢ=job nNW6p0v?}m^g>KP%LYFDV%" 2-ьs2NJqL@`=*w9J[<ƔȓWH76:A=` xq<w8XZ?GnXg;&6[/% m]R[:PniT$A(AI&S]xh-M޴a.v-s6aV?S1_.n S-#'ߔ {q97k%3?_2{,gjR UNgo"M2#'ag0{ifh Is{{cЗA 0t+vUvdIVza׻fG$8’(|NE}&prt˫f''QtSU/#E6Xym )8کi(XdaByZf g8{'>x^iF܈s0К8s|EfӧJ {4JG+|[nSSuX6e]QF2^qq6^W6xӫ^Ve[,b^{έZ"eЫZTnmHFᵫim2J>[ω 3,LGnr-hN8U^|:ˢіo@RL`zw[cMb%Z\ qARkj"8?Aa ^ɩ.pޣކj|dN&څfl쨘 Cd}ӀEF1WmմicɣmMc9/o#$k}C+ ďК5 {Ikbt`}ag7i|t鋼sJ*`#)} \C/r{4(0P{Zo)vzMOhw#.mSIRT&LwLY j"a@bt AӏگI' 5NJ+B0R< 1ܜ|# f\6@m+mNb[Peռ0bz{X%SpT&u3Rg.W_^%0I&! ڔ;8_.g p+fxY;kr! oK#R #;zNoN YP<:H>%rmZ@ ;7r]L\=ɰ&x$]R19.?R<']C WȌLDޣ`3wudTǻ|GpeX,Bt( ~f?fQ3`@Si ۬.o@zy)Bĵ,DJL(f(KtO {3+1.X,W4H4+|й{ (ܮt a% WU? $SEF_!3J:J Eɫ7 1:6[@~GUvPo҄5,#i "wؖ3AI&S:+q3Ƭ(rĜXJ.y+嬏,0 ա[x۴GSz fbRYz52oQKyJeMTsxtb/)uxD&GV3}scBBlKSc6&;#`ܩC#\nJHjħ:AiU P=!ET u}b(Š;O:xh9,+]t_.EN,UlƏo=\^sz5IyB$ggv9>42}:HdF&Ưh!|O=4#CTʓچ]:SȂfG^{fyKevTs| ܧhi[k?>K-5HLi,8<3Ueџކm&ɇrVRd.x¾Bb YPǗ[iG 6t-ysetY#Ӄ)͚ճ[:hvOi+@~\?vLz\!r:}f: čڨ,\<6+'hQi!(< iX]f1ш4PF^Hnfk`Vo;Կ G$+yQ`a\z<T`O+R]8$d*e5.-I&bk/Y1 +"^X?V"mTKRDC\<go;ZcrH6.u 1Nfkb+?O~q7;dyGڤYN_ ى)|Ϸr1GAGׯwTB7xq"@=uHZ\8CK]\BD<3@0_gUQU] ͌ߤªI e]Pas& V7:u[ڻVs{& woB(3Ɔ0a# N_ֵ@^=U^ vJIAX0 l9Hzߑ^?) v[.HrNj/fOoDڞGІyB3xgM4If%ݴbM&<BִuT;S`dC s>0}x0f9B2zWpH9~V:f:o*RB{ugM7*wl0vbllXJt䰠F*m7+-CnO.GS  `ZӧujvL!Lꪭd<4D< Y0{DU n$mv-; $frKihfJ]nB8Cc8mN;KxcG&>YG엕l%$̸|jT[`>fJVWHAzx9rYÁ pC1d|GLP޾Kbqaη!Ӆ !VtzF=:$8|Srd>{ ܷ'T |l|>nz]𤘣 x:9?uO@{/B]!FRDX }sign`L\Q^:RCngvu\|ݍE\PvTJg.t smLԱU-2"qDpA vAu0<"[!JF|{Q'2*KB:Y^"{[(ꐈNs1wG=F8ɣA8"t"<ն 3]`/E :P(I8LѱU#^'C?&[h5C $re8kYogԮE ?Y+v1Ï勨;n5|1M+1Mztsءr/YWif$KOȚ?*U`=BA%Mj7rF+h,!1)z>mTq*2k>K3xDx ~݄zd'Ynʫ4<Ƚ ~Ʒ*wH7?٥G>8QūP b9P@pnG@Xz#WWpJMfZC#Y z6iu0`p,zL DQ:W#­,8pwOZNuY"\-se%6i9%|!6WpX,=(!c;pfRd.C~m[D@y.X<lRy-󑡡WgoU ,aw 8Ssm(eG4R@,GCǑ7ɡ_6,t%}CC+o'!]f1ZyLȮ=]ZCvVq$^55yU Qt^g:_H-nj^o!x.dlw' 4 D\^|Q|(?:b6A9 WF̪*lC NkgOSiH3<(/YI}ܤ5w%yVӵ14 Uzif -dWpE{1 "h;ѱ"@ҡb`{~g7#qPQX\M-Ѭu EԥA-,R}SbzT|D|'9_)͕Vo`utcpZ D_V;|6>1H?r"_8Iէ(Q'SNW..E{_qy_z 7KAa8Ma/ո4kx &"@O.Q $+?,eeq%ڙxt5z2F@$ ? ,u@fOA4I&S<3Z]#LC;hxN ("gnI\[e+RԱ!R\? bj/*Mo0 Ll,p䳐}#M@0 Nh1[挞)_kkQIJ h:rjyihuj|ʽxe( ѕTୟ9=o/HWVɚ1͊%ru1*JV?($g#xSy մm(23Y)֕#6&N(ŔYԉ퓽+v?Nϵ-W75ȭ ŗMq8g42Ԥ0\c (tPIdC9Z&u{8J;kpr ,=D?qR -bM/ֱE l5;0V <4l#-HppIYڮ ϴ-A@86A0vofxWΝB sv-VD*7d=vPW=jOH[葌dig>xk >؁S kp#WT?:4c& 4Ǐ07? T(.['ݷ5=$+S0Qͳh%߮wCk.ž{r:Lq7CVc<=bB^6/ğ_$V ʐUP@$dlJlS:h*|Y֙uv(TM xšiD]$X+-: Ks|O+dN9= / rB+l92 :!8\@'zM5)]gY[ @?+lʳŽwn8jð`Oinxj:72.wuܧqܲ)I'[S)sU"%G:&o7.db(·9T6 xT6Lnkp $?R0 :\=H{BA4@L^Ti:L+[3`SHܷ ۧ&tn]o]|b'wG>X@D 0k#aiԦK'=^:.HP̖HIUN-CLc jtQh|Gs@9x[ M!b4$D,-g]a .fٳD/:Òn͆l^W&J?1~j5^F7Y)K/4kjs1%TR?^y#3MZt&%'hs^h apMw ^5.<'|\;ĭ**Q5M܀1SV tق')@^[dOdڝėP{=wX_m;C{ⅸljELX ?*lvBdHa@?"=~C?]E M>P7Y\Qؿgbu4d+f}_d9c TPaDo6}]ٞ bs޼T,7/Lxgj[r$+ǎi,<4X0] fW){)H@4׼. DOR`N,DtQl~&.}cQ1}EyC[t3FZhXtȯ:7;|?Ԙn7O>V)ᬏ~st5.ct6)B(.8zUc׷0&Ujw)zk` 2@SOQ};Na.KTʠ:a+x 4jnB>9 (HOPR*[ 2N"]x k< nG{\6p J:; W]$cMP%ld!ԁve_C!n8ϒX~i` sRE 4|-eO2Q]7fHEziE~T+mʾ׼*4\d+y9-R/Mqxy,hTv;hЄη.yBg>i-SL*ZjVM"E=BE1BH4Kvlw PHDeeX[^E oQdF(ݰ迂h[(10;8k/X#1eG Q[wX3ҬWeK')ǒe<ޚiƣWI&kpǩ!ȫeyJFF!а4[~/ 15&uŒ`` H {Ĝ΅ԧٹ,!f 8YXkaNDHX[rYގvE]vڣ>Aխ|@oN{HnJărid@T[3NJa;7 /_LWËͭi[6]({Nj. # TSgF&|{(H"RVdkC&խG#teCkt \2м'-:Wbٜ!PRdKtyo+CK gI5/˫i` Ӹ("н5<1,9Ub>/Uvi ѺN ҙlz̷0ReBH.Mu N2q`m9Dxx>O=o XcN}뀯n{'ą}@ɶO|9P5Fkv/kye8C , YE"-H s ? 9jʬM'Q%P,ReT|ݸ0\|>*vdRk8^J SjBMui !qU5՝&b$WUbh, 'H+d[X^ӿfK"GiCRVYTo< /? *V& $4CԓV($њ˓[^}d03R.WrN#|mI7?)ַ~4i4a8amFk&1{] K־ݘ`c%K5` Ow& B~506刭1 Ot?'|3$+#zTt,QW?tFsBOxkZ4gnPcyNjdTjd@56"0 ۘ4<(E&;ؠwV~i)DBM/ I6jH^oKYhi򞆞Se/ƉbBؓO 70נI*qk.?e|FF/ 3% `+No|XID&jD$# '3>xd`7M)-=@cha Qpt`p³/My׼3#Y{{ SiUd_×o(q|وVC:<W}=yGdZ"sY6 w&]DFIgաL9& [ O^g G a?HP)> LmA{.㬯}o (L~F };CP l=26lwPMg?[i2 K#Tl$MJ1iU uM+0nv&"Eۃ8"$#/G-.Fe)SwbN:j vvϔUi{tr#XoG|ns  Y8mrN$sg ƴ}*^* Oz]m|UIƫ֙y^*f(ꅦp"ݫ*g:y!IPp:xWk.q_~QR2\[޽~ٙ:N9Ά#.wa`?jes3Jԑ&BBod$` ~qi3V@c[Rd!+ lq*}bەWEi(R9)!B>iPx6ͼWz%BC_ /_f2"Lw|;#ȔD R9wU#4-s7?lj)ֿe@!c&7ޫ\0wu+3EzfO> JwQ9RR&V *l~$s`WB>Fla1PRg6_½4b !)I(K JewF,Iz[џ, O;>!gn6V]sK mt,tRғ&O7#^YQޱa͞a>'7c5yȬ[GnT\q6PwŸ&\g)۔6|t9 ܔ56AQ1⽺4 dql\o? ]1tzm};WWca.I0[VxIF=]5a$Bw(ANz@yl<)%u7ߊ p >+L@5S*`QumgThIy!ޫ^R`_TCt2y9|4W =9^& Ub`CEE}sPt!qƞ*!s}k4o9hbm<ˤVnRTrbME| yuk^% ܝkhlr>XpO&X$wd%iݢ9H{&ŧSNe"gD r Z>h<#&e!Ԧ?u(c N5Fg48g}UE1D$dOUx?h}t'$<NdXY 6im2쌶ߖT\^l<"AH v*yeTCB/lGzeYxRR)77؍(ui$بʒ7\=8th<՝'_fψ2DҤԅRzlzT|YJ~?51z{r;v`ɶ1? kм;`y f\XK}c޶͇<}͂f`4+vuyn8M܌}8 nb | JےTA"eK_MKقIiT?vd e  %=N}f5K'P7K:~yTxTSddqd%][#&9>̯>Jrt[8a3`ݲ;wF#cޗ|N=(c> \Rlpg' xO8Vo4 tcN6.;+tnMW!ڛɳyAd[ؗmPχR+PAa yve2<6PxN)xr #Fb)͙E%x $5l᭹s )N05K\W^D| 0OOuNvۗ7E ⛋<=`7!`-Z96H+2 hmI[U ȣE֓wYv5*pn/E!ȼIqo.$FZϰaޔzC$~D>f.)g2lU3JM:4/"8pfM"y px0TH&2%CpR"[׵Tr9ʨ.IW>ƿE8hqHl8!}QD6};5owB~ڽ̞?WKXzXChe@62,g8[aٝ:-W A9Jc&1Auah e;%ɠ QOQ{m) !ʊD:񇥋E;q JL_ @8Ib=˕5g>w[L=><"Uoo'W?$bS LNcۅ-o1bpl~pܳ3H';8S?0R7 Qt|!_'?V~08*y&yMJ|g0)ʊrXs69bق#sˠ+#~FflI3gq7Q&1㔆Km^b>ULY{jk5F;>~O-''~3(g5}ujk Dվ܆/ BJه#?y$}.*'$t ݍ&..j̯Lv*Ph 2=1ẂˆSR&)Xmd8Ħ]<l<kWl2dU}C3R^[`ϴuzB'BqP<Н|S*"fdFbP10q`ny Ew+b꘯=}4y)j!5A;"l .lfl_IA%w6t, {NfP 5:2Rߜn"ґAvI&S6f~۰no+*_ @8P?17z Fmrn"L=vpg['wzU|".10ԉs<#}'?ّL>޷2\Yl\]NW]_2~fx@a*xtPnXb@O ,:o?u=woNۘF՝QeuX RQ)'C)@=CF3:ýD+}$FoJv}(f8{sJ@76X-*vi겙CKQٖP'ϊAϠMqV{m^B: ȼfxBs_gnxf麸BPPB3\T05}[ =mj+k8֭;h1-Q2LY `Rue^N Msf%m4OH"߹2#q15^:d_%er,7-!GQ{^zhRU_bՕ>[QW<Fy)Z)/Dž_S9S021'| sNT^d}xXp ZΦU6?TQWkWDh[#z0|k,=PF4x\% ufoSAX>?Y$P^a4}]ZӂkBݏ)Wit (PgLOdW%,xT.I/Բ(dٛ I-;RQ՛<Lŕσ+/MQ=/2Od|} %fuOBѳ:8q~ښ rvf-#o8пAd*!!H~@R`&NU ׼JF9^(!~.u:Ƞ.C+9:t53q8c,vnt=rǴMZA"G=> fAҮbxy$LDzYTVdzA`qV 8mio#ވ%VY8z; nsvD3wmۗm]kב$\rmo(6 =;jOaMd֩URODx!@#b,q 1olh*^`.m۬o_ afEEnY>GI Rާbp𼌱}2q F.Xjlڐx (`޳>HFGa%gZt5Vl'_Yѝv˽T\`ϑI֧JA d[A?p4Vf}նԎ[b%1NcdYmea{(eTj7H}1:Ns>P| 4JU$}>NrАGH"/ k.?:9K dD %ҥ»-:.(swXee7*ȮoiW58s\~-'hQ%D˘ :m7PF1d yfO*5eCfX3s5.XX n|Ƃ3] #ˏ;z#:"#4Ъken@Gy*htZcwj0k73"PG[:qsb5.-b<3@'j F*KjiBBU7G;հkΎt"y;bjCa_iyΖpNnS`1h-SncjrMlz"@`ɠM]42r-dBBNe8 |m^e.-&K6$Ԍ'h>d%gt @K7$F2UfC Wgs])Eu$<ߣC(vEs4磙:(z;$enUyhF#zȚ { ˭XQ7dĕ9Ƥo:Oj:{Vy|z4u7^wtCPlmtd4Bs(f!+n(m cG8#wċ+S7Ry5PY*僄+C޴Zcb?o3iA腚65 VǭldhCQl2WoQӒϗLn' XII2_$F•Y R=^/?9n w&Cvq\@TgT d^=@Ϛs+uݔXeN14J2?ԶV5|r"B^6KVx);}mXn(簥cOŽ Ylt>S2Md}mƹ=I%orCa*h|Wn-X4RLgVx `9?W'GC'茅 d vqȦʐBt7[~U#3I J &8: zP52| r6 |5W~Qbvf4B׮&uUϘe `7:/p^P ǎK6xBkz5׼<,=l* kw+/en;]iUҟGhu,+5z\]5KMY:%aDQ#Wؿc+gڙ|9@GԔw f%{Ni%RY_S87xL(^tJhdds,ˬFTTo"9EKheKnsI܌m&o#folr Qg!ٕ<۔ē];C LYvS |EKFoRXv]@e{ ugB{4 |vySA|`! cC:m߽K8YR`FqLᓓ MXp?7/:Pj5MGD#)Am(i#s!UVĒGKvAɔ;"@(كъb=bRFF U5^kHaCgxh`iBΨCI5pay%)D݄ ~>ǜ9ގJUh$݄CÊHzlo}(֌ߗ"sB[S QO 9yxe-|JC{WMΗpn}̷_8Wd@Ӿ#UAzC2J[[ X( f7->ʍBFι@ Be2g&Ǐ2IV|f r>D^OgC ~+Nt7Ǒ{wBY,܆:"`|%L4-!X;MNabFlCKH޲Z p 1{H %It/\wgA=yb.A@%%̹BvnTLIF}$ryS[Hz84&q *;+ _(CRy htߓ%eF)GVKH}& WtmD6pOZ0O H0v yO 0d~SIqXJ{vEXB=_ͮa 5%lPt6{nXaz"bvیOKU} aQS7kZ}!ա 3~@zwHNNbw|h$[WX$E$/IbB_:;wsLcc?7+ۗ.;6>k3 F N唗[dA: dYkiBvyp@1?9$jVd=3xF]SR =P7"(cIN}#kc- umG#_{ysD|pO*V7a>yst2Q`:\#7&nz5'؄Q\Msgqg!OԵl!jkn2a/I\kRSaX]r ?N5%^5(քBu=ed~^kK온`ó p{.0I̺{oٮɵTV.Bb5|6-s4h6@^ _Z}BHNvZs3No¼'2ʛE.)w9(DKP)r9,P rdB`sŭao<f @~xIsfVa-Oz@jL( -4%FGCTP Ǡ-йaW|/B . zkՅ:*9C\X|M3!Lc5>"(Q'}*',STpH*JMg鎑H"NV~#lQhpc1` ٗS(99_ L}!4I-wisy qx|ÖRkocV:i!(5"KRqҫH.qS3鉢倓lB꿶q@(ܱ[B ?+ #8R6 BmBRY"<սoS7\n}+:EǢr"lB5*aOkj=[w5t$'(3 ,:#!઼p#o(N-cS(\?$U*(b9{RX+'yU'Z']c 'M6v$V-[Ciʟ 0_H%!q>KM~URc5j Vy)8@_ )7aWgη#a+P!5w(;g/# |_}cy@s(Se*l>*aD`8SIי#e iyE;SD5یKYлlk,I{fCѴ)pO@6jl#5N2|R2 )zý'EP]-!e;PTtL(cOl7}5`UQD+OMD& @&@C@ ,Z>;qk9i~GOpV"ڲVo׬R4AxiV5MdžqJX@|Dj p5\Xx/LA_:$S4H6+qh_' )X3+! ӠeҰ;ʞR'Yya]s ڲ-Hr^vՂA5|(>4m:ĖeQLR fL["_\6U+(Rs5ŘFv)}  U J'1oҩU&j3v=Ýe8 KBn%!@n.hIkc\의ہ^.8a~|]B-؞f'OY+e9Ä4T5lp9#eզ&q6=l8u1?~Myv6 -3ė>Fؔɱ#-,LQ2] i622mz]$2ܛgߌ91KU(4O?98 OkWgymL_S,59 b>7=]UMW*9YQǘ`gc!OuUW|5&4§(\5V`*.UCZdoJj["6xjpQHdrcYwYiO-83^Wni+񭨲ݱ#e?Hu&ECjR啰dҮϘRkͪn\%l޽$Y2 IK6kvd(PK:\9,z^>첦iÂvE>qECOA1cř=Su@XoPlŎ/fH^Ľw"kz g!5;hw b&D8Va Ɍʉ}96acBvDGd&؁1ײ{bp5 RlOħ ȵiLiRG5 9&r BRGrgډ ":p`+ffmc4΄9BO@_ >h1/yk 8Eգ._ 9Y ǫW<68 Cʗ;_If/j|3"'wcJm%^ &Z?Qg,x|r[5eGI p$^XtkΘW^"w$: j(=Q ==m~d;y:Z>R9aJIL_(MyY{HlLΰ}&EwK()7U@Zai~Wl12Bm)]:> br*AJycGwnDl{{!:̵,N@$odt6Yk5ou}ʥEjj*ȋd[26Y(wH9ґHfcxO|/! '>=Ҧ[T515R> q奔unIhF~==6hj6j)Kl}|*AFG]yhf*NĭH]1tf@L62$wIl%X sӆK8FOMxzȀ( sq"$~he`ש_Nj%:CyŇf `M}OWSK 4-yw O z,`c6g[8±Y۾[t`@&j󱓳8Q # Uu~ n^V"Hօ= Qz$ VQoٙ̄P6)T!Kj(_+#<\bi۳Q[\>fnnz8%w%??aAxkgGrpxAmf\6LZnǫhrB ~7Nכޣ4p:US n|_wy>$=0ꔟ.u}E)}``푥!hc5#E½s͡!=9-2!<ΓzO}=A G_u;m-.8a0wب|7ގ(ohyjJ@>-S].Jspg."yC^d&H:|(Y$zo +X|Y^'4%A6)DlVX"*дcګ͸m\Bk.)ŒuJTԓ"` q`R=6`|lӀyk ڧIq%hmZ#$z'|z˩;q%2J$bI6Lr2zA9;mV>R 'X_^rҀnog1i8,D#nrI ۘƃrax |O߃]aҶ`*Lgy74$J^b=o?X,-y_yu\Lإ*xG _H:@ zØu23a2v- LuQ7weCVAֿ:F'0Fe[xRQz= \, f5yT7V~1 re'o9 Q?Rs~k"oy.Ǣ+CM3\Kf`Ra7l([zT'䵝=ӎ˜.0Mma%9MR5c`̬>]d;EkV3I[y+<)+&tMD7^% fNkYL6'¡3س$d~;5F`YQ.rE56eV]pnhÈ(o@n!V"WUA/ohceͭh1)7qM ~-6֎|E R'”Q %짌85"F%2 Bfq |y? t D4.{@:#0.kv56ު[A>:Л'b:q82ZxL{ٟ':FÞ8}N-Po{5' A58tX|e 1ʙk1y-SE1ȠWdR.zPY yh8<W@Vm"V05V ^p;u/I^0t?.AS%x^ﴐi9ⴣ); fitBJkLqn͆])4YOpY {š_V݄R שhV7 #1#wżZ۴M7[-91QF^fmW);%ȫ! G`#lڞc01&z'oހ=qG'ҫ%|ug1UkJPOFûg $P/*kNaxЬzLDHv_2DHZ.q3rv_@ylځ5 v#j>mfNDz#J5Y;(߯)+ëH@ʡ2 +n1яA=2`v[#+-'D(KrTK4 tR]_rsq'K{1Τ4;Ka]?`J&4y<XK#HEt',l~D*;^>:[--``.b4ѦC6U($4!`35jݬ> _A@ 6õEuLH몐n*5CP'16o۾m$E2V&~zSI!2RXGj7_CCx^_-QLUZX"8O -ض}#og8EQ*2$Sw2f_gMܘ++_k"A*m'zz9Kj1{HVa=8" 0LNSsDtyUrpBM0q"aX7?VAf{tGNgw A7B1 [G1Bҧ*8Oٝܗ2·ZдE;s / @W,cOp+hQÛVya)吠=Q*˗G13ZI 2`m045'w-"<2ZeRm~AOmٵKo>-0Bא+Fړ<1+JD?c$;[G2^ GrL uPCMFF^j. j:\r;yi+C4ch5e6w%ר5d=IWd[B ?8Z7IE'!g 1%lbʼ;{<Ԃ)+E#\qh' rxVB'D"~2H19 m`DiEͪQ y6Vsҝ1δjrX!Yyc +Ǥ ˍ5i\x覸@X1uS4;J 2?n<. {*͜h\tF#E?/ \g ߴA'S_ z$z?J̭xf G9!.iQ90= zu֘UIkX6&?T;b u}'{8C•Ӓqݷ~ o/:$M, wL{d^~+zm|,F5g[z/#`/15Ce.uA[w)e'("֯Ѡ=tÚ,~0x+#{*5ݴ_A+9ݔcwatho0pnB侉x;S qaYG77wN-Q,/+6"@VH}$v~$ì޽˳&+a݀ ȟ6^ǓGΘJA*qޑ8>]N1kDdQR8)Ò&h~ܻfHzr/Ŗ#$[,2ݵ-:60+ӓi.O7]u=Ѩ"A,JB‘=$<iq8 S-}I w? Fێx _i홶6BƎh`Ӓ2pJސC#6D,zxi2*#_9$Ţȋ${D;JX'r0?A"z"sT7~܏-\AsT?`W5E09Љ E4F;7Nu_'݋L,ʤH2ky_ N@ GKc2]YyNdύx2ϚGIxbV$f“a[0g^%Z̋GDus2ZZf+~E UwHٗi04X[cZuz^f?ٷeh@]+` S3tLj[#陞D>qdnYx+qH68!-zлM.*İDO8X~S> Fؾ[_pJ%΃I&6VeS r*pKWCK슖9_32ذoeZ,{9uz8Րʲ-{"+_^UG=]da ]/ꦧ]yC0nm{baIa/Q ۧ GF!"4 `q4C+;&7bB5A;{5yzE sDq~g;rDƙb~=u~0{yjU&w64wӭ;n)β=Oݯw%#c;\KL&X6JGq0m-HĄbfѪiND=x3394%i}"i ^GHABqEr~ de> oRj&~^hh%H?5N.m(\-Hw9EQagʇċbp:AXq}m;2gwE;|O)O=U\;\˨w֭Z"pV8Tuމ]'=L؝ި8&ƻMT[rA\ ց ̒}G`:Ўk/gVP&s'kO x@lwܸ\iݕ ΑN>g4ϛ^% ,cYI?֘ ?y i]{}{@V zڽN #ϳRow:jNcMLDT CYCz^nB4[nnq"_E~p*s=BRA l}A?j=A\$2v>7Xz!%,,\N. 6UnLJO~ʪx7G`Qkiۭ%}Kj@;o( CyiAEk8] 5WsNX{MFZ' !cd~q8&c 7"7L-g%3Nm\f0:VzY02Z4xj=Wn79m5ҀQ.6:64$-dT*`E?);&,?0ӹݧ{sPRƍĐSW3pf$h'_ N: -艥=Ǭ7]&>]F|ٍ" L/OyQDKhJ? w2r]hK*[t);ecqp +f|~(-HP;sr+ܮ< UOmUD!>a(8nfHLRާ$G|{ުI`CqŴ޿&W]:pKKq/UFhʂ$'K)Nf?(_{gHL#HӼ>[.*IY,VWAPLGDx3.%h ] Rk@O&SϑT1+(E-0p;p;.rYך^qa'MRpXF2 )кd8==42tWFhe0T5ױ9 NVl YP]_G4_y|~DZK0adzn ȍ8C`* |<9SJ-眤M4\@σ-M);ɸJUǼS/Pe"n :(pxM; F(ow;Ar* Ek鈧aIr0s@*-z,{+%do R >wV>Y|ť5Us66$bpx]du;]zX^&&g>{Kj5ӕ4I} 2GnWq#,5}ٝ윯G9_;ۖltm4qZmBG3aTn.8 cP 6JN򂢢'B *˽kpC > : 局2sEGRR@z R\hԹ1GE[8ObӆU_!*td s޷NXz0̿$jc6cbmQ9'f2EgzSkm , .>i]enz6Y׷@_Z))KUqDK+?+ D8 k?ss XlrwImJ} Sܑ`V,5jc!~McBsMR1>ɏ&9Ns Ÿc_!qX)TФ? t&q";3d܌X)J&WG(h A.0h'",K%^}RQ_ex#Ίu*#$S9"Lgpt3'D$a8(NrL{&uŹ1=77mzl(NtYT;hԢ= g4׌Pnf).;)):-Hq#n"l~>rMLM4pSW&໏D?X%M<ע hW2O<нo-$ne U۽oR03!F4V&4Q؈?CBPK?r.]i||@ yWD™P,:ONL ~gX)Šr{a^E:g*0 R_B{4G, GIz03(;!J]2n L-wwsI$-CB$k[@)F۔1CPt>X~Iƨ`gs?B%Aņ+ŅͬrOaǀ6жܨh+ ?i"{H w%EJ-p?qfqI ?;54tZ/-V j8҈HhYo&,Y%$SCA6 .L9_㍭ÿ(a懰[x% ZQ]$hڴa4Rl Cf4 tt>H'?>2 ~qo bU}r5LuC^QbCW8iM~c,:-pk_&`A d|ejؽGgjER7koϡ mPL\_¥R6[!~E `"=ZE0:8d^5_|Q UǙnP@L?0_a_+1_ %78B5`$q CYg%Ƙ,2 ;EK֥{+x/Lw@8["JC#=+{L=4^̵ SNP&{A!lBbE!P0?w穞Y U%"dy;`(~2,S9 ( pI";{b5LC!, (v0]oX>S&M0#lQgVqX[]CZqXs9y@$xAkWQ4q R%8VvwEkkU#ę;r vԁi.Zx2[+*Gm!j?c1I'9OڔIW!ΩG--kj5kU' ޙ莒K#s?xluXL)kZ7uHvWHo'9if.ۆ6pP$-٨="$k:"$CLAB[M^ (=FVrPpN|NR0"+tе3.5yTiǛb.HŤX8D֋7*O,SPN٩T#da{YIi$ÙxsWK!Ikco 6ĽabzgC.n_ J\32:P- Aun> t^BhQT7^9OTR_Plkõ\OwP# @5l=t@>׍tNC +21@>.1P\) qκ 6XNЊ.>nb%b*Eőܷ}e~"|<55NTrK,|z<}-] 1#8ԤaלӱF{*6[4jIUi 0z!b:H!4L0MS`zބV$pŸElK,z܎o׷Mт4xgITv z݇]`_cR'uP\OkYhb'^Sg)*$rlF2a~J 5oBS8_Ge/k/jܙ+;W嬋.oF&*{^ {u( u?L+8FF<`ju=Njoz< Սs㙨2ə\JsS6}WVm5ݴ>P}tЗ _CCe @bƸ}v$fuQwut{^.YkDN,H=7<.kpa<4D{d?-ufmvhd?M#Z=v<[(Զ5&O)*觘 |r׉ΑS僀ęW~ŮoDZ9"Tn~} ZH2 PLl"'+ۺ5ҏn'4 Rgg?\k@߉ ה6#X;uٌIe({j=ISM0Ȯ$:uP(v YR'R+5FS}. OzI슭Qɼ>83і1Ǫsq;I֠mTzATeϮv ;ڜ1؇#?uB8d̦C+G^ڡP0r&Y7`-,Zq1}4v ˳qJަ:`2'hv`" pv.ժdlAi.P8ӕnr{2e]8NI"N9VuTAQ YJfAn[3N*: ^GB`LQ7.CvB~_# >o\۫]N_0`ii\?D_1;]^QuMZ,btc\\eTjD 5WjdեҪmT>tЯR4C<: sr"bP&xճ{KŲSp L'X_PRۛsʈF @s۶Agbo:`a=5u g Jq;4#w*l%b> M>d^_j6C_ FµPRzCH V\\+*C;gԚw%|:՘K[C}%Mvp1.7X+Km&[6u[rT؉|i,/Y!;m:-*gŶ8*x?T'̲n:|> VJ[vur13Ssi`2M?ZFi%Kgo d[eF{=``n%OlHEG ? (DؒS_a$|L "Iñzwjm wi-0]Ȋײa6ڴmK=>b6])=kI 5+3Сd[NQn¶&|^+$Hq7~FV<&4 E}OQ:ˌh6D2;n`80v['<_۔*RDw$ f%6^#sMG.Éh$ FIk+A iX9[Bb0\bz߸A EoV ChJ"ȅJ2dG8mtY, Ṹֵ1x60_G_7>+erl¬lzؘ`&1$}O&):Q9قd'ƺBqJ6$Twzn2D,Q?μT\w hI+*W4 ww:ex +xkBPp.}Z(-VzL Of8b'x-ly_}4+%gEaÀ@4{bZUZ xmQwl^!A~믓Űt{&BtUbΘ?5@;oݸ=0+( śэ>p=ڨ5QʂHg ܙeFݣf`Y˒)9jܔ6gk.o/isLȰ8y))CȺ0.F$*#b}-X6*Y48.X&X Ò7Tߣ{شCqt?q0WqcamS({(?ޱfNߛA\E |-[ |*s%+T=kǠʁK}cBh!!M;Ԗ2#8u,/'vئ&ن~>+޵E:DedAʹ|,'237xFKIcjWb;oXxʧum$jhsUI#9.yw/B[cxkIKG&k($9|)=9uD)g#gX?ArSlIOgtNmt2:*π4 [ܻARV%W-0o{`Vc̲sh7 AB~G3$+) a 7C{Q4u- T6>OYhˮe֟"g>޾4-W~V.[-~MFgxWpDу[\cgP||_Y" Y f\B*Bs&"3|L8w`]1~J?(Wȁd|:&&&* .+w>|tLJ7VrP{[o98sK3i\߂hˢZJYJclYhׂ9Ɏt%E90r$}I! @g.H T|ExSqF ѳO1L)rd`UxV4F'L-ybd[VA9^\[ UٯJ@JR|H"&,ttf <`(,ݹۧ/&I@wgRX#yڜu[ a@g/[M :Amr h[H/ӥ!w(ˮ'}I$>2d^1v{^P? SX-L^O]r^8z- `ΰ:H?fߤOk-ޔtldhR~m"/XG)b&Y^O2wv!zgA:-&*ҠUڝVŮCMqXv9vHi3HÂD,1K떦HX!r%'L,Bs:T;wX l@@M EJ=-#^a2KP:vC"- [qU_CT &' P4BG'lЮ, Ą p #*w_eVhz,EP˜[? %D[ UOpsG@_I#eʓ iK\F}b WsPRNG*rf)] Iƪ@bձ#l M*Yt4{un +~ݸB-kUJ?&i-Lw?F_]̴9 3h"ՙց4;8Uk,lA!Bt1--/A/ tʑ(,QP CТ6ƙr0p\=m4m wS"mfk;rڤTy-V]TܲYo!^&IE)c oq}wވAOQjsůGQrL 9f%O,8=IoV}^ɧ Wy%p ^QF##(IQ>r!IFR=Q ?J'*Z`>! oYm?2R N^rJwߕh.:sl%QQw;]]AÕ]%MH9y@*: )QKUnLA]~ܠJ+8?u4bW[Ag]#_%1mYff-Аe]t) Rϭ]m^(iw0pB3}utȶzIjXf~E/9vA_vTBz 3 iS" _EgaF?bߐd,hdU\&TFV.LrKdž#dEpS"Xr^{nBRXvd͟tsAE8Т|4o",S&֊r̿_e #y6O)![t[JcOAYQ/~&\pQ^{䮨".BU`E_nQ/ 1) j݀2* @ԁ|gւ_*ڪ [}8k]F5NG3 hɗC<C!:lJD.}tP JNDTX΍9drJ5pz[lI8\8&f32FV$6V.lW# E?Ni(dVξ676/ii6z@a*ˮz! -IZvV)R\4pa_ֻ 窹d;pf5Z ZAcI&S8@'}0F1MԅϘk/xQ++~Jh?Z=(dPwk–瀋-3rO>|wPft5|i%HI453PE;Z 5M!Ӂзͦb_cC~HKvADZvFOwk -x 2G/܏1x3kNE=h+-9W_]]¿$t}{5ڐE<-ju$^Ux;֪ CHIuJ/CS6 QcMG.w`Ϡ!tےh3֣mµ!p?KZ3%hVG1W0n4P %i_\qau9Ղ)\20%論\%Sd ӿљP۬ ~9T6yۄܮesf'ܖK h`bzQS`X_U 鮶WIVogU,r M(S@\R7[@OIQ'B 3QUѕR<+Tzy vH}MOCf9޽r"MG:ܛG|G0D+ު"(^҉UQ}c~j]`KAkXvc规9_*;0Jl !9$P고 kܚ}oMXkZ'q@_I;&Gbܟ`q)d 0_/<fIDa:HF15LhnW`>9ґqzL`1@vF_-<* RdbP(=ҍF3H}5{5F&H糠(p!Df/g Zo4 h &A9wF#L1>jgolk~.j MMgF 9 {j̈́0px@ ocɧ>,a]3&ғ`2߿-BT+Ԍ'x#쟔Ci}GL*asQZ *63N2 VzG:ؑk~5rwHa1Ņ贈n7|[A^չ=@PNCѠ1El9dxߔj8O=@*ߢXE?q <(BKOG5cW8 @ɪv;㑜꺽bFo8*$9i`fFЭV?ZHnWe.F1d!?yk,!CSot@kS1JZޙlN25_l0&̼%NLQ24]Ɏ$@+fLHr1=}PRcbʥWT|p.tPD@P/f?=!ٶp0h$fsV4ؤhdfSv/8J] J7J3Pg$eQL8+eҞApЕБBh75Ajs^jbkS0CS_ l -0^ &/DY*Z$.$O%ianLK8VuNr۸A~A.;2 *{tm{0QOǾ: ajSG_&_3IxM2AI&Sg}P`z982n-\u{ڹϠR,!ӦT=ad>F{EAi5s\-э~2c=e,̰rס[Z TԲ* ]x, 3*YFx 3hzY3i1]$隆IɟӗTh)X՛Ă 9Г։ěBٰBjS 'Ak2-wxo΃@ TEBtfMG` G&}-G*|RF7j36-.6GNq ۆ@Jj꠴%5q?IhVh~$+P _e ==sϿy'ZR4 !BG0#D"V*Yۈ$Kuהج\w,e(x6^YcmSX9vmOB&PxqYV5T!} Jձ|lV ix)M*dǠNU1ղ5bWG?~M6iZ.JZct(U63>uv\pMWBsK2krMb8vH _Tޗ)~}AӍ;ŊPAm$b-Mƣ+5F?}C\nޖzRЮŮ aE^;@maYb[L< 3]PpnHku\Hٷ47[S ,7ʨQ5cUBn[cb|[pI8Ι?aoNxYaY_4RuKʴK)Q~cKe.[E*' gYG<(ѕ`QÐDRYe|q4Vs.[Ns[79;XXϾ֝BQFzDt&LR6. TSr *4!#.9v{C &7 5|Dzz?iJE@S*,#o!皕/=(k m=ۈa&KlaזIڴתpN͞\5^bkP+t,RRv|/pPoK'h5a{qr-} W|cnrz$O֖Y=Jf~3blǠI%v#IqqCsO=q/ YW" #v%M¨{լzQ>7]_">ÔXtRrIPOXﴙ3[\VAhѯeCk®|ҒUB46Ͳ hCPU\@\Đ'N=$)De͓Y\;Μ=ͪmrovMxxsU)‚/?koMzE#bp2jwayݕ 49j:(+}HS0mCzMˆ&О'9. aՅptnCb{Ts⻝50`Y?T( vJݐ^\mu٪XS66-'7.g$6a]w]0gc=إ$=ҁ:h^1D lb1t&}Cs@ >eB!p} Fܶ?ю3TjL te 9 7ѻ Y\gg퍄j"J}Q/ŤX܆" &{NבY xeZ) q^DZTK,Χ3ɹpc:75NmSqIJv?#S'*:9ia)Q^ PDfM&^M \ \~賆v[ARRu dk,P S>2vzI=(o$ϗܔMxBg,tGYm[ \$Ԛ5ZR-O[B266-?^uxlP1h[=ҁ|%d/B 3z9}mR Ãnb"ݥM~ D@'IڮG= vp7BK]klVgge4^0UF\':Di؅#t '8`%o|} M3$+ڈn$ Tyq*!k7 ƣ|lj-0p?Rm8F|xK/xh3v ',9.[,a&7 oZձQIH*vnUsKOtr$K= -9r g1:444m)P<5uL,mACs4Ij2I]E6)᤿|e;R( I!djGKHwTkDVY:ONhZ)} t*_E.|:vFIg@ '$Ntˆ-S>Oܯh\< |1: n)5DS WЭf) i8Mr>*jX%_:4L#G^6 ܔIp9 Ko@L?EL Pa%gv|sN979)@FCG"T5~xX^f{5ڗdthjJ >o,)Ic0C<_K H!Bl (eq4̂k lb_Q;b. ߃8l*ވ׻-2=gT@<>Vg-͋nzLrqTkko-e]?xnB6}Q4侞H9drnW|=缽8"'6=~ȂiRv;t4.g^Kkρ7]Ǯ4VOXL>z!fbz7),v tmNjvJ^uy/u#h/Z)ҊN#KML;'f;XEtA~y`عz bêLHm#P8kb'؂P<]Į4ڜ:V~BX^g;hޗ۟!j`IS_;ۋKHÞp $y6 zg6 N!:i-⠕gRHjy0Zט!I9(,tM"NmrᬽvdM>ppY<!t8շɥzMv/l - :0?<fV!%ÈZl Ԁr~[$ [rEH|.q'%:ݡa?g3*q3y>6OzītCw0Bt9i<m_]q0lt`;oAD{^$qqYJr:V&X.8xqq|)Ctu&N,25PܙUO$|dF ĚӲ sJXfk4န&:0Ne'9vlPhWCe-u!'0 |D)p^0hyR&qz9VWO(An<[W%-7wfmgL:0p~Xsm]2]J1`W!`ӘԴ',g~0htAo/_AI&SHL$pV@[5L1 S/Y4kO:p،y?l]g^Cn"pF ,>9!wx=# 4aE3FIz%_VI&5J~^G/kZ'}(7i.v7?rd3IE@j 差Qe?y|Pz2g1fFђu`ONypf5 I1'D ;*Ժ#C0Ԅ1XhGĸ^>8h!c$q} Գ9ݬ4vTAmgbӖȑ),X9.ZePeMfk͂5ECI #(cͱKƚeGfZGJ{5s1HR 2! j bH9Kw͝S.AzMPѰZ~)!+f7}R msBxYCpa8%D\\g oGJӳh Adߘ]_R5J*W'= ›@>=\9| C*(C̉*މ7& 7$KsYkH >oV9!i7Q/E1U \g4U҄#Czs|EB<+WsWc\N|8ͯ9$cAaL񎳇CkD$7HBUj(=*Kwa._7yK +S)P4AF(0;IH^0:sƤmc*4Hc$-pwX 1qޛ5jZ>r{82)Ik{ÊGmp[V@ 8\Klba\DhP'eAɿ,K;W5&RH@5)z#s˿UX4G2 ]IZZƬT2) jטzuXw8{n1ZL!$ј 8iy%S2АZ #H@v7MHX_LT]&2% Y| U£y֝3*T)`~;a ]+=61kK c`3L1KJi/VCĻNypR1WqZ,&[`ۜ%"uV_;hU7*mK*'.g:5WtԹ.v+wU'4կ( e{W5` i6"{ܿ<($WWTLq^ C I7^>[%]al> Xbz~'FJ9gG+? 8(xHA_~ر8QrfLhc)&w}T؃pBOmVCf?T,: Cc"S£JՎzؙɪ+Ik?CFER&WmMb %<*?`|:\ؚ曞~gZ*WLyX 8S%Tk0FhJ^]ɷ1c6 7Z2,9, ~սQڶNyeɡ,AZY 8H?tjºetR&|2+`HPAYsJgu#cMG809Қ~y'γvԏxgC+d'HBwD燝a{SNy2n'C 0ZoM.77I9bɯ'}]5_?;k<ʩ G]Lݘ Ja>n/f+O G1ʷϓ/CJ%1~$!o4^tO8O'e{.js2|T6gOS\d"se&;bƦL@`G2l7D"K uOz;G4e[dMEݫ\7_2OP C :lWA9"nV }dAU|&G1pCk>;`TRgi}8ۢG.=oBfZ~P6dEӬY\U뾣ޤ8O6=i52eAxg #VKo*$MS߷g3۹t0+Т>ģ[fg4MцOCAGO)^sih%Zn(d9*[}b BZ7.ݚwuvOLr<M<*XEH\ ISKp5O$je;#^Op0- D0B믵ka Lq)\ο: 5|Jt=&{Qn7ՎFaG]S!,j2l^{\p[N˽dESA;_2h5>XO"C7S/Y?v̨J*0,tO3ۍkĨZPffMBv%)5oU>1CL4˻#a"V^h,ͤ'$yϱH`{- u]E^I#Lqw}tphGʂTggHR'3+ M~OO:O􃩖S%?d]Lols^2uZ #Ъu\VR֚<;ȞI"0n*[Y[ŦsZrU:lO !l%[ْWgDޓ~W̻txz|@WwUg=*aȤy6}ӒqY6׫ϪZn#E%ƵxKqN #r,@DʐtiR(O0?N*?)ytxdL7ɿKahM@״<ͣh @csr ϰ\qha x2cd[ R̤U|-%8 qw~"ᔲ@wסd&I;&C&LeVx)~e|$E,(vk-N ]?)@ofw$$mM.,ehtR nNodbHN)NA}] opd"ت 盪 A/%n!@eTO1[9-Q3zU LH`|X8iA`Pki׽g<WVVƙhۭ$j}·'Xp `m=C{r06v>ŐM߶XA]khyXp,6|nITl@iPgFNv ј2E20wJEQ!B^4\(pVi'U~j;F{!ۆ- 8&! w b4\r%>tRkc, +|QWY Nġ<pzͷǞf('' } N 2>P FqSLsG6b~-g=['UPSٳ?]*MB{k&[Y rVP:|ޖK75dĆ.kq ,ȫcn``Uиijĕg84}.lYɝ`#BCѲ)zIX&;f潣2w¯ zS=e2A_I7@_7zd|YvU^;Q0ɅR?rkXFРQo&_@N `Jt.5_ܺ@3"c` }1y,>^o>HUa%{ۭWjK3f I7a=Rt [zQ0dޒq>^x%EwO?Fo<^OMdeiLĭA7+;" * x&((#_AZ\Wr=c2^|CB篯PqTv W M:Pdylˆ*99,,p϶(ȿk I57\ԃ^Oi<mh%)-ASjɵ ^os;FtOUxi?Ṳ'F+v;xrGM :b 憐?;yX'4 oBrei~t ɘCPļ.3isgJԧJ>!'܌ ^E$x=Lߥ_Orm5H*P[ ٰ AI&S8@[qi wbɡa|G 9,42 //de={)}~`-OC!DPyG86;?EBcj,G*\rD̯VSmsæEZ Pco+e~S{!Y6g6%JjC)e6 (S>HÀ]lFp+$CEsu $(ArxQ*LT7򠫛DkwӐ,.^&LpvE2Sf.Ak*ɧ%%=ޛmW BE4KQz N钠&;ɇ[ogp $0AFϐaW"?¼E\6WP`X8`70Ώz=kǂu8b2:'3>ZԬM&Ν8nm(4:uD=S?[yJN5u[9gĕy g゜$WrIw?bdZiG\॒(=~mD R+)9$0턺g{XfU` č fe#Y4:J~6Zx4t<9xdknN wǾډ}_bڅm1.K-m>+ \v3@b͓Jd1/2-ܑzȻ}Q&ğ,ZX A+sU^SY:*u *|\.qы1k\ 6<3TV(q'=A8e | &سS *B6X1![\nURQCj s-{O|vio*c5<+J0}4=alS%W\>G"dtؐcB] /GjQS7b# o;Y 2\?546S|:Ioyϣ@?W5_'Ju xiԫZPF+gDUET9%Knk_pt ZWdB@xP;</-8۩S}ώDۈ68WNF6>F*z#Th\`fcIeO Yy_uD1:GQv) |⼿:[-!4 c2vOݫ̙ul~lfS8x޶.ӎNcJTL'#= H"킀 %L;c^wD;fsq%~F&~vXV&D:'w$=l^(f-\ }.]{MͲ{c'Y0mUc9{/믅go6A,NC;Q@G̓x֢MƼ{3>ֹq Sz bdCe={X;A$k (̳Z@&k9(7M\T6OpOѶZ~%) ܂]Wr}6 =V>%n`4rׂ;aj6HI/WPI#VFS%c̓O,&ȖM65">5Eq8ͬ/Ӑ&~ee\7?Xѷ~GS]pُ/ľ zTQXEdg~$v2vțsOlۨVC+a̋r](DJSU8y48Q%S,7S-Dݭ": ~UCnThFEU=G@ M QO$*T۰uvU᰺TL-4=vŮ{8l)Q:Of1%|TIZ&VcJ(ʵ]1bb8?v J[|î1>ag"ubx v37.5e&N r_+W~THiɏƙٕs&[Z4A?rU@)u|* =Rؘ|/:]z0"w J,B@4OT[m05IS9B&>UX8?3cs'`JJEhQKH:G9}.ty_ej}}pBjt= q[ Y$wŝѭ~Pp:A>d066^)gYusOuyc඿0\hMEqmѹƅ/c?NeBv*Dv,%Ry=4D-nZ'9hN2Jl_PJKEwd8d]x UOTadMe\hG7;Ʒw!ÞA$xdA#-Fg5(J ^.dņb }%gL)5OƞrTLpCy-^ua^zp[+[o~=_(8#Mjٺ bX̀UTt,PD5ޯ(4`tm䲙D@^{2vMiGCziĥz"B_ )Äzwi7{Y9&ѕ0+x݌"ؚJOG `%AKzhnnh/_etIf+ _l<:QW O?ʳbf.[ˋ7roI1k0`jMp0 oZsOK"/h54ad]+'>fFz^ <vA1쫹 >mvSms\h%U%(6)YioRLH4lYHxr Pg2U̘y6;|]XFr/&2MWtjaAca YS}L,X]V hLf$Ҟj[mG:+|Nf ɦ:l6hbOi̟5la.Y@ +|RF/Ο#9z7{˂fn> cq<je·v j/q=|+cP麗49S{>ʆ~s>B kUzjƸQ,aQpQz\#Mi9Akd -t@a賽#* sN-M;+W LTi.*u+;_wU"5/k1+8Ó))vgl&OB  먕@J)c$2_& sE+b,E\ RJ-kz 8Lk^ӨȮdk&JY#U[F6>/2N6jBwc΋_Nz5yBxp/`6$6R^D&C?DyJ}:veeC_[&_@|Rlh kztۊPLW0/(\adÚ4Bu=km|UF#Ue'WWck$h # {6X$*Tc)]HҸd LvѽҾ^ d?eBMǥTQkGř);:k)y l^e|DlNuہ/id@|VO 7JU#X yx4xd^zIz6FdYwm^K80嶯N5A~n^PP Hһ{9E8R A?ˆ%1Cbހ1"0K$;\~n '֧Y*'P?E)Mؚ AQu̪T3"zqB 2.Un$I*h5%'̓mnZ?ytT\V;To~|vᤱNu,S=V-CAαF=v)]j5"5n0+]b %l*,-_6nx Ū;=W1G%-H4 P&-NCNϝn$ Ut6JB3tW(;Vj1A&.F7tpR ܕkG+.V?CH]}R3XI> SF?mD:#a?pZl &|:T$JK=@![1R'yiՀfcΜvfǽԧ|C bChWPNvy5zNFDd&R+u]JV)9=NPKҬų ?0n446wהPO&<b }[jKr  yy$[v>wK-goh(;Ľc ^ԷՏD|rn&HݔcA2}^XaNf :sG{gÜ#[K] RqFbAU$_N}@A8ڕ{/$빓Zj԰5xݵBLFeȘ "=Sxy4⍕m[ BQ^|#2\zahcsW;SzW٩p?Lez =)`1 "1`1)˹#(eTj^0x'gD쬡)fqy_?xszmԨ ?ET"EA sz<:6hTchbn$19#><]<-k)@wkml=VOq1wRCU=)jE#5j1H]BTXVk)j'~8< i.auA@ei4'r?Xl [^Vz h5UmB~WS$ rG@@FRMQZ{XF{O aBZG4g g$DZN h>>'w#EHv$EyKekjZfJ=ph׌(S7E3-X1 m yzQs뱧ŏu5 .z;pOI>)v0e7̒=x̹G*m:@;GFcԏgSm#MoyH~36`pR㗛^zÅwO2 >SMY41<^>}k*[21 T(@M 0Z߮iIku=uho 6kM3a;{LYqpRU_)v3Bs҄abf3CӢKnLˠ=6S"|]9KTҡ/|,go`M+)0,P}/a  ]CQExOq)?ϧDX׿ogN#Dhdj7_"4`y_7/p@'Y GE%67!AyE 5:ܵQ%pRǟ(%ٙMdLS̱Ya<(H0[J^Tz"RSvRٴmCj,:췖B^AL.q^pUC 8 L4d/<" H7LlG*xY=e>sRir=CMtP3B=Tz%D Ѵf\K&8V|3sϩ8 u::Bi})Yʱ4Eȫ̻g W3/ o[f\䂊p 07( [:]=ht؞^c564(+K:IaH G0T~W̓-މCʷ=Pj 1JjWs}ΕX[%5:Y[肀ݵ!%q%3*R!WHx:@oԡף BwLJa)LےI<ūg}/sfR1ۃq A  7r*S' \6-ʗBRƪ "D>;8fRgUq,. _W1uN;siod=6`dꍭT,S_6b 5΂KLVQ0n֒t#-؇gd}^wݗZr.ֲ#a` vhƂ u04Ly ycCE[{T_.JolZ>l/R{+ͣ63Q>W /-Ii* I9g_ ZInύɺxoggJ UȮ^ sj'_U^\otX|e r&d*ISuNݜ-lNLzGofUݘ3>_tO.shi)n^cC uko,y5L 4CIntbl ;y Zx:wj9`c .jB&#vs[yN~vux0:zr~NF9¿QE-5I^X$7Y`Uq)z3rb\ʭaEʂ}[8%BjX~l9U>>g+z"Jڽy4a_l|G-8LrlvgUh(i@@'4?VPp>HyI%RZ.G q#OUmۣ'e:D.9kb-xFzED Bz& `D;/B+6Oռ@{L_t  La(y Mj^؍ zU$I`˹2Q>f@7z(LEH#es[Sҫa *xZTUD)zenwky?qBS9+J$sR` 92{")ORz.ϻ!y2(Iڠ360 C"hlZsQ涏v}%=32'=>]E+8k{׳H'Ú> p-#rx=MBbR<|,Z?4?XU0ګɏP ҾOn3a ӅjKGxݜkzysN3M4T۔R6ͺI~!XPZ}T.YlMblYDlrǾ.dsqQ_'/;(" F^GsM3@_CF:0WeuԂ/Dq3w󦄆xWaZ'\1qdE %l콊z}#;1U+v1ts6a>S6&&H@kAA+eT<|Q\c#ǟ51KH@+/SA/y~̓%#MqF8#x5U %ysjhyI!եp]1G:zlR2Bȉ \1mI$)<4[5"H{!NŐ7ie!d<4gEٯ/{62hVS3vzfe|7ueW~,T7ˡtQlw}S#lN-w OFK ymQQ::-yXINIvwuޢn 7GQ *|U*?$VZGl@c -hᚐC Rlgh"[L= @q@jCt\{3j`qpg3 1=mՑ%?ӛIklE[,3լjH|'M$LP!yhMY/΍ l+ЇAA>1A`mg.wn4tPP`ן"i{g[̕Fi%Q}rѡy{~+ZO@t mLcYq3¹nٯ:{Pkﳥo{щ^wmiǍU 9LuPL$(fA?FmKgCPJoT\)=U =)Вzg*ɁkOD<}6IZ'?NUQ'u!~É:5P.0Jp)'+R=.sTC݅0fj&jiH䪅j%vը"$dZPj汹Wt;!og쓪<.뤙GOcxA/0V}oTD@xyOPjh""e)o}$=kq89/p0;[,3DNC=vCJ 5¡A5EgW*G4 JK/A&Yq;W$.Wpֵ.بn0@ͳ3'{9Z?^],U lO1Y 5,S:*4 H&sQlkFx lmV '+b+ʁepߤ]CiV -FW> [38`YFF+"1mP|NhTxn} <:pn*ŝW@RIrzz9F Λ2 aV2z}PNq0O9D6R |. =r}i3)Ƒ>C$XC:,_gFփC$k0wњX;]ʚ,.lyPLĥCª6Q z*C2E!2]9hd#>0 "z@iZsXW7Ep`%|ޥkd"DNj-@7Rt.O' ;'8BâCEi'DVAc+-sxt(>7x:=ՍbfGW+A%9n'\,ڬ*B һ;]"Uq/q<:p[ve>al h{oOKkKS-v6>#ݤ)z {xݦ\p)6:dAYOM%g{@7*+.0T]|/mi?XU:DV="K0(iQCA~ 6=#;]BGy?!ƈeV8Vd+1gԛ[^4cypk@:HJekU$MB5> R~yW;6jڬ ){| ]vz;´g>oXDkGxD V3̏ԌlF (ˠ>^c~ۉC'O$HU٥ŶJNAB+4cVH[\ x}drhy Z_]2Nge5uaItLZ߁5sNˌH=>QDŽ?ɐ@=U:[iCDrZ ܬ&$mFo5XЙ%]NZɵlU]+r2Sc%v<K8/WVj s'%r2(Na[𴤀0)[Ԏܚu%hӽ9uDTt͔` S'6ٱ)~kR3RĿ."qa@͕[a$HrrcBG*ZΔҙG2 Tf=NzNLK}+2KFgSRB@/U/)ugoHi|PZ_;XFF$a 礵(U : 8D,u]Rgn#T`ؘPܔ++tu#z;Ei#ZQUfrf9I]Du~*z[1 viv~ sAՂaf>_ "-)#?ՙPY'87A4̩ L)~pHO:ba"mq?ӬԿڹU~!'A$J`y;)s5+n5:{klnɕg 6ȽC!{+fN*)75`Cr9@?_ߨAv2ddmc$hPme~]oEH˷`MGb~oٟzXx; 6Q2pjMp^|UH78 A0TVqpB,7 q2ݓ%cݞ?DZ樢.p8+Q6`5k@E{ AzL1{! ;!/0?/|]\jT*^ v%'eZLIq<[݋ڙׄUWw dŨ5iʨM 6n[Pr%6ns)#Yt/&{mCkݐ?V/QY4цMIUH-toFFS#*1v0w/ԧ} 0+A I&S;wF>} vCPd?yp@AEK\ގg5A =XsYv$z[=H_tt46 ҌJyH'Έu`  3aP;Z{|:(zXdD`;kC5n2_#TU?jcA@pyJId C}<98}bX3(.#͊e\>jV$tM J2c8EsZTXxsrX 37raZZ P%nc+? wivH}}Y~iR D)̲$5,V(d =^EwSVըY^ QRnE }xqRN h9۬bctT(ֲQsʪe!.?03ALՠy ώYPCsRqهߍ9;'ΐ#~*rQDVȘlXlV@RPE1y9T z»ʃ+~L ؉я2?ud!gfeciq^p2vdk$O;I%ˣԝp$VwhƗ+?cj3.N1yмIu }|CP9FgqǽuVȔy~yV]͂h~/[q@9~ @qs)p8q,b;DIJ -w!opI5P0 SpQDqʼ\Ңoޥ.\֩%qY; XcdCgoXκ6ޔ|~Cgޢ˥R<3fG؛gbO7j"4q_x \A*I&S*s%J2!O(NMYXeDHHU)Y&󢇊s| arʏ;K{8&#~=95P8?C%k|ǁKK$ csKC. Wr$ca峂6}&wmY8An򘰼$D$vDMƅ<RpVRW˕o|r\+_HΧIgwUWX#Ru&vsK*\X>Ş걗B>g]: 0@H[1DK@EU07tT,,u]aZ|hn_mX1:c=gז{n0J u ]uϽ`uIPbM,--;`BA8MAZs*GB2 @&^?Q/Pk^H_-Z|oQ3MYIXx@(,ZJ+ЩܾԸa)؞]}2"5Х&c }phJp+y:Ϣ"X?Žwݯ`@auwGJؓVƔ;h 4nD%"+j~/x#ѮN x3?\&[a#@ xl Ki{94$wc e1צKނҋEKX wHF @O?+1޸AggNL][,>/ZԘAÈ]R !LLWK&i.q#_ 4Wdf7FcCSU^e=,)PpJyOd"|N4yb_OHtHm2&eWiP">4 -Ó18|r)7b|꭮%u<`4;}3Bʻ4l]}!Ԥc0i4*s$Zc%CC&~ij4vQTԍٌg"LvJ+VKݐ<3E@8gwUo|*`2Oy"V}609Ȝ~L$=h'3\YrRvۘ>U{+\x7 rEUی4 `Mg&O\T /(I9P:f5߳KG1 k5v`+ CIیHodHS^MU&4FT^6WF|= Y[~%=kqa $ ;%D\UB Zg3NH=*]^x|U0R!ķ{ǗL$ Ta.#gJv2dQDQ*1z]#_U@o&' CAx/DHm^ k8Nj.8iI oHL'6n;Jf_?q- }[|1cbCk]:\ȵE/2ʶ1Tğ I sp +hX=o^sKAFM(tHlcm=蘡,j@7r{oe_P a#^i/fQ =ogxҨ/$'~-s>q;Ln-V* DM5u[+V3J5ۙ_vCo Tgr|D6=5~s=l%Ē_h%ǺWeRV|EY1GJLu쿷#? \@ q4v: kA a]7K唄o, Kg/ Qu.L[U4i! ""ga8kD4*I⦍F H8!<$;qTy5"_ kwa o]nr0U+xJ">Zu$mautx0c;Ûw95cY)1L}8v i? 3Y[HEw,a.W'C)<kwBeN4 Y σG̾Wقj|#o#I஘e.ʜ8cջDaN֮dE2 bIm_y6̀-OA\2ng?@Mp4:V'~B9jR7ļi!M:/q ykEg7_q԰Q @Ec6j3^|,5L˵m΍HoS(ܬRH=9[Fl }ke<1)fZԬS>um`Lk&f@kOa'[P0h  kc B˰j !4gEKf!b By|HH o6Ҡ27</յЃB+r FFU`>CaFpFS⾒a*L%ZY''lJHZ4g AKI&Su~݇pZAy7 d+&UQP(>KpGNx)1馌#'R0K&dac;!ӦKcE^OZ73lj{*Q~;աqfΩ0~1RUŋmkͻ I3}#+yV>$gε~()1g+vQIsnQ ?Z~b8 J){mLjNdC$iz /hUb4? H&5Ƶ;WX0;^$i3ӳ\rb#Eu$2{P-yJ/gyuE/YAqn?縿J7N s}єNQ7l?#9ޖ{'Ee@{_zR4?j?E5N$'sA*|v~fhKaVАVؓu#y=7+z;BE mj0l(\$:h0jkr 9ݴe\fkb;y꽣٪sGMPVu1ˮB{+^tҔQZ X=GŻո"sǫu펈BC>AS#Z3ra4-n~AiFX,ZֶJDL\fVY+^hCld&!;ϧ aة DP|3avII@,NTS> BKD-"Yt.M< SJFk ncr-mj/^\.wA5w<0Eqv1lmVpg^4=*ShE3ky Ф$BvGSC0Z+&:6 єmz20N !da)92)aw }4K$ D޴+]~R\BZ+8ޭ;pku:ta(18,G5}E^'T0bmWm"vg~Q` g~>E^H$9ɼ5&ne1w#`~Ă_7&pUdJUmHh?lKo1䥯agv59e\@M;C({BUB"w"rtFҁ\#BU╰A R? ֝d; d"|!~iB_LJ uaЯ89x@/tMBMR )TrMF6ߔ3|JJ0C79+ͤ~GZIB뭀$9 ^V;萪W 3 I 贘3IHAY쒐Gaр@6ZOq_=[8Krn_\ITY\}N=)mHpWSUW5_g)/X;E.%,3ыMK֑K^eo遘73@ X>.q'l9Wu>`@>XKlt4!̧BN~':VLkʂg"uivyٯ0"<2v9 oKY'?ׅc|RA& )-:/8~y)J.$|65bXtاJ LRa&J?]snx%u? rkrNI-=i#k#I:o:V_;@Bר5<^][EVsn8Ζmg]a]3 sjUqJI+>$,D`EMܦ:BB|f"pdq7Ti2\߭NL9&I@#,'H eA}N>`5}@3$5_k9o`Uu.{]+~qշ_6Yy(.n- FJPmWUھj9Y^t PӉ?HG`&җGe9L^[2E{!W= 8;q@sٰC@΢6,g"r'WګO|nn>Xp++0HP{yNIu^G2by#+0"(3hNqN ǰl.Ѱ+I3θ~FR !eYlӢ.*6 rf֬/*0?QgwrHz6h{ Vw3f}s|pB R2Ej_ӻث}`N ɿO=Tj쒸hdMϷ[֥P`oJADk=q+շƊQy;?t%F-xZmxBI7yj"l'X@mN s#߻Vl$K@jϸX=w6quh]O؈hɟ5ьm";YvМܾ!DI'wvm -[ZF $ULu@+R x4狇Z#JuB9Li 9}d da7criL ,^FՎ0:Je!_Ze`e"+tJ@>9>phR[DeG2Rߙ8i? )[po\d_f/2DMyY_ '55Qz9#EV+U*mc> -3>ߌǛ&UPT2%p 2bxVoZ'T]s!s-wKKZ~Od[Y6ʢc|8I_D<`GT~{ڄ'nm:y^[&MXϴrƠ '18U'?J1"e7^3Dn˼I;sNxd4;5sߨ@ P߳8Ϯckܷ|N*{Jkl _ش[乼;XQӁJqp:5+tv*"5 &*n|\WᲸ 7V0W'5g~8ť)?'w-ad*[rn;wv[uK,-)dGSWȨ\ d 6>L(9 NދpU9=87mV :k/WC#'*zj \ 6Q;p ɣXȰұb pV98qz#,%`VN'`k3Z{f<|8G)&mPiƨL&: OI[XY R7wXޞH2jy'/<y&eͰnnKvvLY3% rNе#V~`3*LIiqvm?#1UG&'Yr᛫3Gv|*9&mMÜc'4*?{1R!,xQz]]WZ.yL[XEy2E!qcWuQdK7o}nffYK)+ܘtSs4P o«&K?6<.&;8 @5\ciB 54v! ^Mia1ug۬&Uyy[$Ձ7(b\VAy&0՛E(T,vtAI&S]xh-M޴a.v-s6aV?S1_.n S-#'ߔ {q97k%3?_2{)u8T.+p^9t0Z}`f8V5$5& Jf|,cEolsc& *,߭xVq+߲{Ⱦͻ^9 K5qJyiZppВ3 ^-K-2@=mv98=b*>l_Լ&{\ k a"PD4%oZ'Ca;k+#^DP3M6M}K#@*o®놭bl/^x؞XybMkNƂmۖ~4Z(Jb;QM e_ \oXfԨ;-); rŮ՟lSK Kj,Q0q@_ hKs PvT|A GpfenXyT&X޳>P5 Ӄ}C7$5i<ݿ-T2LYkN⋱[YʡQm CzaUi[VN+x`?,6U!13O3 X׭6i gVT9|W 'l*G`4sC`ﲂa.#~O*:g;\̛ _Ԯ͊٨|W=iU7"Fe%` ]2BL[#ʯq}pQdޙ(\[׷&6's{L'8-X ^M{tFsըM-dOF2MVHɕ5}JDԷ7d@k/g" *aV9= BBVؚ:)91:8]9nsխRa!,NvgDs5r} &I(8`~,;Xԭ^B0w%? +-u(UvOBPL%ޑt&'z#OH:ư#v=?@w|ʹ yFkED? e24jToCk~1ʽN\Mi"FkKs֛z wVJ%&NZ.Vkt- 킰9$F= xjr^߽@d´(yA߹gvĴ+PuN/tJeȑɘ PKCap1;LJ xdf^ ȚY_(S.h!vH6Cp6zutM ݄,x'`l7{#H|RQ,_mbHƉۀ蹒yQd\F- MJS38$zy+ æj*"nȺ=jlgV6읣 H|X6Wa`07H:@_)>y:4u[r'+hY+mIiOc\77m+n==~_Bc[A^?Fd*{P/-@=s@SXsKhnd=҆ 4x}pKOQh6m.asڛc`"C6 vf 6GmVBdn*FX#h̫rhHkrJRLI_rOS]oG4zж0?Xѱ͉! s5 n.lz5ƥ^/>c:esnd݊MkMRtWR2$ ,Mb05wu?5pI]0U2v\q*wgUm?N5c]ֶ DZ#v)ջO` Nm?GCp?pL3F Sx?k>\҅Cp(= ̡7 _c-w0W}=u? 6&Je2?k*1Q~8vR(3dzY'Y\Z3x\f( (̆Ӄ!>rWtpyщ7U{v sۼ ,CyP -h$:xsl^Zy%nZNTp (bأhH*&xsq dMG r^шz0qػs OB4XB:z#2&=5⡯&D4 u۩]CInPwI$=1YM !uW>?נ>(v7k2z+;m=IWo 677؀ᷬp\{n0]ABѷme$ed&!Xgc&y]=ɿP,"6f:؁^J4;.MK BtZn]h[_p.\) iXGnW+.+u^_l>qE<n*ofyw$iA~U*e_S>p_Ǐ 馕,[F>9܀$]V\\nC[,zeGp[ <ҽwZI.ͿE~k7MXJ w"߆9lޔZdyrGi"VJ !PZ "pX2zB Y¨j /@%M}q 4,W}Io$LjTnYLXZt'h-YyI9aPPӒ"88)UT4V=Ԉ/Cpu(2.jɧ!f,n/)]8aiHb|,GnQk_¹ OY4^A ,\c&|FaAТ/Nr-dmtޒG*$qRW绻)CkBz&a]],Ɔ-W$'FF%F Fi}[ Px5EWg={ع|o4+YQd$e$5=A2mnp)"ͷt"Ks$ X`+p1:k*=ɩX"|6q$B %v?vw6޹WQ6T,,T{:jj׹ Hc?<3uz։C\,AEC5"Ȃb0-G3EҮ4_ 8m3DƵ֊l*XsST3SE'tOɉ##Z"`N:5fMD;wX|G A"υbZﲙ[|sl~^Wj 6?tC( Y=z:W`8Ayu(b "̊J!&):$in/_MU?)͙:i$C?Ƅ $k%٤mv؈6c{^ϗ@[zWY,.vhEj.e~ YzB0X*-c))}b<%e=Wh8]Tb\6&.W!i0Ѵ_BV?1]o|9E-yV_D/@"r֏WVDum yKBI/NfYXj+*$'lw/5x岱:o}QCNм;ւ[iCnTsr0 GOњCM{.ASԖeeJZ= &׻5>R#Q)"E>L8ūOMXh۹ldl:) ["Ǔ 9KWʇcbYdA |/I~M2Z;gr>.mïI"@?ŝņ_˗*xՉ7 O,gTJBP.:CVlX0=e: RDf3p_C,8r/m3>Փ~i g7%ƘQX 5GCm3C@5.lیgaLtǂF:4jQ\ ךY~8<m?)9(r.s |h.xIҪBk 6(v"Q^UlD#;<}90M^\kμέCȚuЩ"s%e 52V){T3by v&"H]QǁzKڊZ:nbS}#_yhTJa0 ?>T2?OMONN>fN3 bNL%jf֖8XӿRnZԛ5LQKJ84tp4URJGN+4AhV :lIOEM`zalIG81%S ŘIY|βl۱BPb置}o<1@sLđVH~)NDJ#?xB rClbKIZ&qC4rFӉiV WP V)xj9{ BC#7k{9gGo%AD2;=+>h_`ʯ#(-.F`^3 F46vgo)(# eB(цFlQgtI2ݿ8a^Flw2Ӓayǡ cs>\z"*LJSYO۽3|̥kh/\GTqTƔ'B+ WDv[A!9 f1ء^uh8?W瞸) 0QRT(fu3y$ߘ / XOdw^$EwM(ʋ3@ jD"R-/GesaNuh85!q<}g(дV8@=L+r0Vq͎^YT1\Uh(7DR઺Y_Y@]bWFb̶ݿ( TLpV`JJqq>Whsٜ`[LCi(0%B7f';.Y_B[MzhGҫOD" 3̿쁻ԴoWxiFFokHᥫ|0JeVXGZSlL@M QyuwIU K?aGy&SPL6OC:ZF ~@ ^9MFXqJna6WP秦=]5Wyw[ň/61{= S+V6Ed ŷS^*A;I4Wk!]!AMvP_y&Vӈ2tAI&S<3҈{i0ˌ b_ "%U7+^ʎ 6֥>-J ~i |Sim36t,جÏo:~DtD]Bq-$D">E?M 0c9l(2ÞIG(JpϨ:/3 ۀwi a:+c,0e:wAUVhh9B1~|MA`K&C E H4)ҼJ` ;ȈG0ce?|XGBDZDE1\d/ T/'4scy|Ģhhp6 8fV`You|^.#<I͆u$r6Z?B3lֱ>f<)V6U+{(&vlb=1rᅰqf4-*i4/ah Σg,P(G&ΚyOi%*LLT n8.ZF"aucI`WIx'%/4:Z6Vq/-Ôgn'xq`.t i!aݵGDL/`mj,l/]Ld(~x>(/iPz$Ij!a ?p A}+VL1]|,THw:}JI 0eBנo ΋2#O4pcǻ~hhXhfPTIcv8~6'MS^h* kr6)z>O1@S􄃫 `[{бP]$Q4sGEb6Kټ;yL!>KKx'ku9ڝ0QV#_!\a+B[/^h_)݄0Xmla9W(Lp2b)-BfsgAO4CKrOxhhsl[4-AO*  QxV oozϽڠ$Wg9+CMؾd8cոƝOy%,[}d }BA2DB=O؈)WBV6Kǧ2 GhnY*_ M`iv3f^H$4΂ީ 2H'zϫݩ!䮨U5FwFWv֢ ~UVn|A{=V2gkuo5%B82tbm̞Y6F*!.x罋T@)T8$d/>y؝!ڑKBC:م(/35e&t&YbYрVdJ:|&2sʻv~|VfmRn$>M#Ɂ8*eL`v5úLZ9yײ]}rRJ,q9Q9Tk%=iG)nc!_>p2#b-mP`űX3nn_ucoq.܆7B*1Ɓ ϱ9 \nHxp]TRo;JM:Q%xsC--<(`5y<`].C$hJgKe\yO8XF__"z# @/ptUx?ų:B.? YuxˈHTQ~(96@ʉ}]N"#_,'əfb. [P:1mzn膋BRy#yO]9ƄvNp(Uа,eͮu'Mj֤|ĢWow{&?Rh*ta՛ [F"]|jmRc?t-a Ϟhbw<ݚ՜Kڜ΀iG4N# IQ۫%wcW4kxKw7/Y[@+HvxuDA 6N>D_>P_^+ר=KMڙz{'K+C=Uˈ@q@>E&Q~K1<5d %Ƨa[d ޶| Ȁuށ arUhd\E6`|l> fR qWj]-ԤkCu@r?L(uzYͻcU!g;Y8U046pJ!&18T4Q9p+(uhȡ:2, fPuP 5 uVk1Mz'dqp@9qCF#SNJ"X N8=ۆwY1AvM!Լis5o)EqHlSn2p]g :D)LA&O}wH\y7,~*vɘs2ںw c٬ē̅/E&</9Je$8;W7:I*NΛfOW) "®RUv?c>{ Sǃ24fCWF ұ+ fw[@zc,oj#Bjpp:DEH׀b AcW_~n Xͻ W]4Zr0/P,cYO8-{;8TzU K60 vT`?'`;X8' nm'IxxxIZ -z%fv޵q{N~[僃AFhˊT1Yߞ# [J_QJK!bld)D_Pg\.ltTOσmstoƇz+Ff!f!ABB@z?*ܚMAi @y2VbAkc=J&] K%mXA, j9'[{xW~o=6SHb/{ kݙC^Fp[ƶoEȾ }+[q-{"5b|}2 HWadc"iuPב)~kl5ٹz cXU F:,9vh+bY@^6nU}XnSBIO}YTI&+,I'5946)\SOX*IC, פxoCQj/*Yگ S! _N_n6LkI-!KxU>*bH)H" ZMqt :U2x e۪6JF8= IסkA5 jBG)@'5QQs]Yނf/Eu[az D`0T`TX#lk w ۠ϊҾT,LcӅ&iMnQ PK@94rVS-vX خz618gg[;ܵ/kLA>&Rs͟OsͳXJcmZژPgN$~:4Vq|4$YJRnP{e»ۉ[U"ͦi Au#U#SV\i\ >!m( e1cMkgq%$'ؓ:8qѕS'T;@ 9{{gxrUԺo o YEmjV?e`+x!z e8cga2Q#-68ȹ9W]}Q?72Y(,-rdh_mǠF뫟ҊuNqD!{kVuWYfNn`;V S/mc$S牳1NZ ?4z .9bha2:dHᔝp>k&_vI{' TY*J)yOg%ZXoeFuL!ҥIٝTWS"HԶ:԰\>55'CuϠL Pk,ebRg;*~mIdJ6# Mh-oʉ${ xXgٖT 鷦E%j&躣 AƼj1: :g6Jud6KgM16P]|p1.P-,.\$|DӤ! K<>n(7#rF=fI[XR7b xIBO_ӡ_ǧ03?~Zض u2^6_D31ӎ}B4/Zyo)!᣸5ʫ69!%8azS "$;yxF|a) `2ә6Q]Oxgv6 Ȧ0nahJTL EU=j $Bhę evty|QϖFjlQ&G8[CKY_eQb?疡[TeϕWmX6(,iҭkS^:Av] T8p3QBߓbHk%n!CT+6%rB]V%t־P?M`2t0J0' AŔ/ɱ&نBʯf *$xj(f#]ƳƁU,/b1%k#$qJM%,C;Ta#\)HF]Dr}W]: #{Y(H]ŕ|e:^v|ӎ˘'J43CofLYb@DsDî{z K >)8._;|o7< Y{Q2De*tΫmkppAS}ն9JlDFka)[in۔iNYyiJ5QҊ࿒d(rnr@m=.-du" UQwi7 qˬZyr7ЄAS}]#1 ;?~v? [Q'um$`*3\@Ay C{sW1ae;*Ixuov .I*4Vw`:RӷZ0sLh[{_4% L~[J)N8C@_j3k] oq eTTHįhbyQu-\>jB;طX/ke:atB/n?Ӱi@K*Ky#3/ s?3 w5ԫˇ]gsUkK޳nd5᎑m]fFЀt)2df5~ ,GgE`1h5(;[&B߯x PPoG 㾨 T-2 H;1g|ݨ li9$̅@P Bk==yUP6Zri9֛\≠|jI^CI b3qpUq90)M8cx'&\q|{l4 \/hI'o( ee7< 36*#Td{lp)Otu{Y0f-\o'a4HV7J'@OH Rj`1Zdul,Ճl]m n-=F`əYqpE. ?Seۢ 'wyr)NfwuIPDf#@^8:ro')l +Sd3Cڃidz֎@߮Ruń{X_T>׬ûDB5 ۾{L^`M?I۪A T,+P*`Cm+$VZ [@#hMczB+䔧 m%npylyvLdSo|df$z\hI=X,tVlN}Â[MJL-ߒ!De\B97c]P(1.e Az2y24+,BEJvO'P)r^N0fnYL}GN;OO WqhVF.B]TEatuOE+cmUrSt<&->` qy=l,HY»='.f<3! ljg_2-iӪ@ 0%ٱ摴`@9 k{1 "9W^q0FlRn (WrUw}Šle`Vf6r~4ieaI1j! W+ۯP>A,Fo~fL-= X5aYEZ4A7|57 |^9Nv^t?"ǯ(Mb̨Q{_#.6Bѱ&P IDs-&ďfӤ 8O͢sGx~$uH,TtSdk=[ԉdio8gMЉQ׋j"+znZ6%D,X|1k}Fu[HlU~E9r\be$+ʈY1YDo IB;(n7?D ѷD0T)Ȳn()˜AkE^e{1fWoLf A̰:!-iz]R>Ccf]kX YlzF1(`O#/A;8W0C? ah^wk&+oZ&숶CJw9)~8ORIItc/GΙf :3p!ցy}8A|V=Ҡ+a(\fC!K2 X:Is ^lh\yڀTog_ n$I)C)Z% gwߒ(ʯ{! gP52C15ȤuEor4w$ pݶMW4[MbBBۯ|gC>̟bR 5qJ|3ܤdN[uo{DBPEoAI&S:Gr6ӽQmCj>g1ϡxn +Ѧxjtvލ jתEiQvV$Zn;f;i֍C/d *35uW Q=c!U}AwO iܯ3HFCXSng-2]{E(֯[ <~ `t)QMιuGn,o'>2D1KDh=Wڝ݉Hsz n5(٬q?Q61_gQ1!N?˻ZMoSjɥ+"Nۂ>J 8*]J2 D +]?gpFG!avp,-D:R ;A4탭io+D#T2F~Ҩc u׋SԶNm)JA쐺s 8 * I$Tɩg>qS®0lRinr 6U>.Ul/!/&,13OMDOcb|2|3'ރ5tUaK;9`QTg,f-jh YQQj59l7<N_[y~ezE:˜12A%4dGM\ǚOlf s=o 81 o}TV8 #S3rrBw.jhQyL->>i!.~?56sßK+h:%&tע T]pa`1ݕiJ,c&,σ :!XOFX=TTEA""sDPxҥ 6\ ECY FǹeJ2_.!cBCV0^Z , E~ƒ/chw+O#Hj$UZ K@}{9C {n g @ LI;dw֞Y{aK$ӳn-9O9?Ӥ"Pt9{T&=DM"/Tu^k^&@GKڲyNbrDzxݘc`ew,e5ja ZK)akSSOP^v$:0bɰi\Ƃe>Mu,hr2">y:kKT d\Y"!L|gN,:Rq}D[u4E$W|B6vGn+ lԟE7-\轥ͥO {'&c5 ɔ]}Ȇ哒 yƆf{&C%t¾J?:9ۓ-eܻT"=«Օ^tL6TULvbʆ4uzVcd`k?Qzr/W8̿PPN<'BV}R1-XXX~^ߝʃ$ R9K J0[#֌yk nD R#ppg=gzJ)G{42H?oYwc/TH+؀55^ic*lٺIN_~h2ulKf;Qۣ0\8dٞr3כg\iYWJEi 6 8 )7)ћDmQ.Q:|GVļ|څ?(=S3y|VJ[R,__αE*鯴{^tɝ}@Bn7m,0M2v6|#pA7Y1ޜHȪ ꬻ=|f/;jk(-[JyE0~y<\?3jj*@3C%;1kLMG^4ljTu܅`Oj65'M'$/.l_LS+kبx0YzUge;p\cwG)?>|epAqYrL~D>*d7mg2) 0U:]80ȏ(JD\i1!⢈~`/Z힤)f5G_WF31R>5(޳j))45#!RD_9ST,rԡyj_)btВd5i{I'H?]F C8;!7!*H|֩. %L5.X`j)vVtb P.]ٱ๦^pLCUtsn}#fnKu5"]Gf9}D+r2S-N"Q1s4 a݅h8Z}\{"si]" |pU\?ҶJw1~~+؀JʨV{aV؁Ҵw `FLD!QsmI @Ss3?gMQ$n S'=(Kե}![QUlO|-L6F+r:5V8x+hBMȑۭ!]*q[/?ޘٷ.eHh#Κ(摪CMfhA|ݶq,*9s1wH)vD6f+kfd~]w Zi,I u 3kx.}9R@J|j(*,^7WʑbKbM<{Z}xFbG\ ~%5M!}0{BەLD, ݁:f2Cgcẹ}'K|.T3$ iQO-ZD8vV~=LbI:VʚSX>?+.KT6A%ۣQ}qXBriz98ȭOEVBipM?R ,&)hq )pBiPb:qr1P< 0axƃ>qO- .$"/=qNfLg>&C JЀ׸p~zq^x[d!XbR5M|sqUkw45w H2I#w١q~ce16ieW~a8ٵ}.{?bnOqR֪Ct 1p kcLCm!^ci Ewԙ+jx'&Q8I\xf}8_Gצ8GB M v$21q+FH Ov![wLDdZjP;'caoʻR_p|iyj`e7#}1{fMrJ#sSI0Cl>c{-s{TӔX\_x7ưCk5F!X}1!Zuvȹ<;\BzH(^v9}$O}'dK7dA&%,w6:HrF<~s`jOXgEtT(^nNyOn6'-7-^?[]β?T8- [`;u\G3bF$oa>gT$6G~E݃0F(+ QA|=s=k\&ALqA\eT4_tȞ 6q.-*2ǟZ1v3Yśe'oy-iDKH*C۾ٟv}ʻIN^hZ2 tOp Rz &g ٛ4/E}T۫;Nl AF$" gW̋fu6x"Is/x±~QsUͿI>~xDĆPxuKEw|Mm7yH4üޑ%ÜH~# Xa`&8Y繀."c] :u0 }G=,gMY2y={9H>|Hudж̦K/fsVkFuJĒ<$a})jt%T\aitMSf`1G`r#)^$/C>4XN=nl#}"@B,: F)y`_tȍf ,M*9AT.gEM^?NjFb̩V/~E !tPO8& PpbDNEwqxձ3~ 4Pq89R=57u̠):LU:]y0pK8vP M4rY0J̹GSm|#X ڴ',Q/r[B_%%nA3I&SxLfyb@S~`4N?E㪂SoQq$ߪi/3tBAS=Q?X5& 5/(rq φ[7@8^ͱF\:u^Z:;ϭ7M0 h)5mDXB ,әMep:SZ{?uCwoР}Z> F[`5-gThch"mX(;,0bmt1fI1&[ÓkWgn0&n˞KAs6~>.3Fc"Rk CyxVD=[ZIyԠNJT`h8t6q#'c'F`DqvZc#h{}d烬3\ cDWd yٷrqΒ%~ ((>jC94].t'rŮYUMxbvbbPqȬy-pQƏ4|d<9;@vFC\&(tXr7óAq.EΩk ۂm|stc~Ù}k0PXhέI9H=ͣ[}KϢB҅f@([$̌8=R:|_y ~U'/Ly &0:bUY`ۥykF ~Sr1#.-hѵVBrEt]GL#B?UWp㒼F.F;VչQ!ľw33( LPDQ}>} _AD.L:J C+1yC ə8@LiKdmާy 3gyXajW@4G*퀙TTdpϟ˴M|&%mj:6u~Ǧ:6[?*g@Tf `/(qυkG4DncE\ >JTO'4Pқ PΓwg0MI>zsXOXʄK7c`^I{|l*Ԟ Lz@QIK'ruvȂߏBԔCx7g+9iٙ=C*5";\SioUO<,Vj7e`p3Pd(!Lbx9o[j.Āq]5H R^4;~KAljAޯBo"~~Ɗs{DƑuoSxNmG3sFc AUI&S<3L[g̃oV W'\!@ U#ɳ" $c,>fRksJhoQc"oQe,RWJPt-0li:zġ/3a}64 :hzvWU#,wm =k3϶ަǪˊr:i{90v=MzZL ?ΙL[49GiQ9WWoB% U| `[{}_RBf"Ic3@`#Y{vxч0<ҕw9~9jݯAGKdR ٔMSr 9]vԡӗf2CS_mb瑶&2(M݅-͈k }bz b4og>A&8m#g ZPi͜+n#^PymxtYmʁe_Wu4DŽ~\8I3dr;'v7)/_.TFGl{ȧu=\kf`E&D1)~,˖! ! 9Sl ji絘Ȱ_ :tp@b̾ Y0g zC͋ɺ B*zf23z^/{:E շM#Q6SUn*=88[B΍N?`m[N=S\~g |47cGk1* )pK/a"^IG ,kPb-D܁UUrzw=B?T<[ؐJ{8trk"Tl\k9;{ 5ByʹsSl)>w[o0V4||CW9RzurahrXωP/ͱG5*eQ; e #y[OLu]y|TxW}[.NvF`8G<L<:5j;>)2Ȫ^ۗH}i˂P;++9jjxƼbuU;{pBNN\<[mK{`5iu+̹lϧzYvEqIKI[ 2DmVNUآ;[QgڢO 92 k'lq[!*e BﭛBŋ0\)vLxHdx}n8EV7k9@H/K;cHPNAӏ`׮S$R2l7Am8qi6oji(sE3򝱺ZpQ ٖ׭ -FxY # F7{F"qks39 &x%4;` ӓ+$琣 T_AESd?.c!!q$8w?)1KfQʩ?v5$CXGSrnVp:il2jz1Xii8Smq>@Θ-DL<B.`t8XvSb;s B ๕Slԍ"<Vƈ' e˂tIƢуBk} ;%zݓL=vۀ? q|pxCՋ-Kf̐gI:-tTeݞ$2y e}}MDG˓1,Cf!pezl/*4e_:>}OyR:j+zP{`v(}#I2w#0)>[~kʚ7n/k5Sʯ'Ȼ,=,Vz%p{$+7 (ɵ,peiFNd$!%0#=),+zIz<8gua3Na5띲*Mm*W0nMRߛlQA^p*n"0OYjY#| f5 ݂&}.U9ݑ26罞C'dhыj.<$.e &tMUhyK\qZ̬%I<ЖKW+V"xGqqN׌<|M RfZEћU&K_("gys " ,$?.KC6"u`qd<{eT{,E}3M7mr \3-{֍*=VX0(ٲ(*Wn/F¢q4|if 5szzR=ά")XR*;HCq:;lKmt cQ۬2p/*9`g<J튠}(LhF5U$h#WTұz`׾ .6X'> 9hoW).(o#tti2upsVYg"frPy`=lwM˟!G5+*G|) ƏAbSOUı,%RS‚Ckӊtns07C?v۫-<o[KW8N^mܵ~YT*[oQ&z[gItoktg Qw)@/q{HU}It~gאΊs@<#I]6V\sⶊLv.Uf*&@lwvk{bqb  Isj`o 9h;}e -e-As@?..j!̧$eL{tuw#?FL[Za-> ˼#pohSn#X%0~)WB@ñM'l@Lըe Uڱ{&j/*̾4~Eaz?װ>kWr͠GM1}QVG';$lS|,s@#!48G'^Q>Q 0܍j}q+JMTk ;Zh0Ԉx5BN6y n;bjT¡4Uhi]Ɖ9̄XcFY+'K(;;Ȅ 5:,os$y_*h է v 6Ⱥ1. YM-ުl(*+6`P=Equ~>C 92JW켞g(!dhSHh[ޏXs L˫z$0ϙW?5. {@QwG?c˙2]F:DVg#`߰b:Ӄ=Ԃ &КJy} !Yt{Rsc -pu5 H]63y᱒$q%%3ivu)YLzˋ؇ 9= (\-jW&Ӡlx4*EtE^γ'Ǣzs`?NUc̷EVR#3Y)M} 9N~LTԂ^K&޷;调iL7KAO ځӶww}{z{u G/bvM2Ȋ$K'*ZKVgӽRd_;hl(Ekd)Bo!]6s;N8;0ceFu7͕tC$%*¬OpK F˰{-"mzQ6o>R#RC t*vcu}Mmj'Jt")?l~vmd(hs85οW=)0VN# 2hpޏoy=kf1 ?K^yQw|( QƓ..f9]>YBRlhZg2ȟUHMԁɊl^բTҋ`}Xm^.@ZLucch]jrb/uZpI/m՜R'WG۠4Z(d=^.5/4z$/?EHYdq5z*WR#48\LW_b–+)yh|/,'ߤUDFËqVj7?4vx?M3$əSfq\,Gf@l6 x۲"RW]p|I J0|XAdۧ&ɼDzEO@SdO2`uq̜v픀Ʒrζ(ܫ-zbcrNV+oyfy<%7UqL +.Gk Aޟ70hhv`A$YK=$qA5]+`>[(hwޛME󛖒:ſ`3g=Lef/ךJ|ki Zn>$lk2Q^(oD aRJ'fZ\¨]FxqѲsqjͯ\ǡ NYɷ2/wH&&" nVGJ7&^_6j{m+M% L!'nquGotR,磗d< [_ξrFBBr_"=vJ ľːgۆY &x<욁@lC6\9Lw3QU5ThCPce=Ɵ{x?,!kgeL^Tl: Mk]cLW.4FekTQ>l8(-_)=Т{ރlz?u BK?hjU7$ F>_םO.EbC]hTA-ӅS\,yӲ8ؑ (Y9A`t?4I{&) 9VqTtȟ[OF Q{ L(3lYn6/NO.[L^956,&dD@lúBӪhPQ5v*aȬ5ȞV|jg+eTź(p)!ml$XKZ QU/Jaͷ)Nv 53fWx9zhm 5IxZ {ܪ1c!풙~LI ]6`!Q8>AI&S]xh-M޴a.v-s6aV?S1_.n S-#'ߔ {q97k%3?_qޖ=RPG KU9S$boc)Hrpp UIDFy̲0t+X 쥇ga׻f8~e*#mbo$KZK3{H0bYɭNWfwCEپľN3fגJiܪIV6!nhI:#h4ˉ9ԴFA\?k­^ RG8Rr"*,M!OEtw i[{em|R]TCB!G!SY+q!^W`]; +o\@լWQN&黿T n1JUyȤ!ʎ VjO>w LŹFzH 3;e\gQz &v%jH/<^=(LW,>j#^ VUKe+wr=4+N-W>zr\ki^c%ޞay(bKR~+R2O .ls+ݷɋ25wi$T$вaO(qH{ŵ8reZBmX)̑=@};^Wx(T\$dwQ)_KNQrM_evS);g#  h8Q& [:+="܉0#< V#8bKQ~jtMA:,dPEX2|*0;]_~4,59j\8;GT]{[B?áO;]-<ˍwkI`թsE~S MSW05=p?^JhpSkmD7*כNբx|ÕPnA1B9@#eͳ`0ZE+:q@h\0g_2{7ͻncMvc$uQ*qV1)nj^u1!BAB:GTͱh$WGlxNsPRJ!ѕ4Ӫfы=LU=pTe5Ӥ.qEcXz4^S=9MF-aWT,QZB`|D5pHq&IV1bTO.܄x(\x1"iFazX5\#¹Mh\ g1)t염F ި^fa7vl2N Dw_O>`bA1?iɣK5 k"?< y"= O oX?ڰ.;Gsr@⺑ x &\`3ƟY.rRzUcy QEMI_eaع+k:;";qv նs˅NskswoĞ2%*N4mn_#JgdF~y׳oa?;VH?o> vL}lTWu?LF\¶kJ9GuO3Ls4'FPc6ɴZl"Q5Q\|FseM [7Y`F8c@u1J,Ú9QkuLeu ±W)w\ B,F d4 Pİ3e 'ͲV̳h2?H#Il|v-Lc =:CL(U/Rq @34Bq^Co,ǥ@ H%6y-EE;FplErP#꧌v鋤`G4vKNG3$,uUA 7XE&5Ogjk\a{L |ߜb:~jbK J0-fJe?j.%"k#@ 8WRen>&T4\k,,c$yW'^r tPn4D2#_DS ]vx@8W2#޳E(4f\ሜ/ ϞSi}ly&Ǒ> ?3Tf't]I x } {M% IfK\yqJއ{~|p{\S^ق_{DߑCCZQ?hB> =af}3郇G^ E(,xܑkbո`XSlr^oA- Ebq@h ?nuClҍetegZ;0D&F@'\gI0C ! lɖiƕcs¯fյx\qŌt5#1B KHP鯗ЗNΞrs hpz}a559ӅaҚF?Ol~9Z -\BGcCsms2hX#Ĵuw 4MͦB௨"G2b|M4/U8aɃ-*(Mѷ[ 24hyFI>JROc#Z\g]je]_[Fǁ//W6 X('~CIJI}M81\.4gWPiK- Tk>Kv,-M{վ(G#0tB9z}Z$zu _ㄎnn"q8B19K:##4h3wz㋨PçʞBžr,GM,T9¡lXy O?S>-2Au9/-:Rp}O)̘r: 9 Hdŏ.QsqӠT i\P qa9qmvG 6M j VP#U2{<_GHR2ƪΟ][|lBUnJD`OdlQy-Qr~3a@*lI'qﰢl} T~J_ |{G&Јf!g/hz&@(. Iuf~Æo\d1h3Z#cj[ fG$پ![uxeJR/NLf/gJ΋l;,_~Ӻ˄b>՘,Sy_Cͤ䅗3"{+Bʘ5:K>l?1Jªؤ؈fCpBsN;UD&"ړO4 6FXoCV6f),$Ww>2rQV2ak5poM&}LqժFnՓ3y7"l'k tܪ%oI-fO/I7xE?wQzhP:o4Q5aqi1EoN(#x(fiw?7_U("XBkU?5ٕ ^ 9Cñ܀kU0Ώa X,iTMbȦ+; 7,y=ڍ1+LQ{,QU=85 ?[LP|ߵTm% +5ψ9G$پ{=cêXH}S~ajsn+Ҡ<qSQ GMZ^hPi6By/W";"m-x]rrf 8s0 u{NR5ll:i5M/XD9qTulv65w0m[k/RSHuGT=F{'I]~ѭd}  +Tʜ E -릦"*3/+R2n gk\+h$- -3=gۗ2]{݃,47Obn&{TnL? Fo@ r=΋̡ ̆3;|6bCgfv}=!b~A<8fR TQ0ku<tƫƂO 1m?/.&+=ɥ}^ ?Bp g7XaMT:m !vV~FK8~xD㤀Gc{3;"beL÷V%Wp%%3>tU~ƕ=i_F'?j4>?:^w|@d:ͷ2}7L1pΗo|jp` $mԌ0Cg}{w-*^,v/7K^7ߛN񼳷1@ԘO(tgPzWt"x~PLE-^ O.-çJ.DA6vkAЩIUD^&2Le}j};cBGʹ-v r:P-Nnp LtҸMbU?5Syt*- d">(ȝh?{#+^ї*s4R١D!"7,[H}Z}삏/KX"g(2.WgǥWV{ɚơ1+&>D )2 [Uqfq&C7mpJf.42b&A+F"m93,w/o 8m%@Nu`X 6$_~{j~~=?fߟPb )d3s{@D2[&Me]b>phR _uHYT~Ԍ" ׅ LRU?u[D\ >g?:%rUJ߽IH>aw*9uP ~[_# \9Q=]cFP7lS"Dx{-sM+EfYOPoWGO0a F0F3K5)N BX\5$bޜwY~MH]hŘFv>Y~OϏ BL4PNU$["CL(/aឨh8j?5cKd)PXU[h_ԿA)%q_?/f;mr*-pXLd3Bz0t!O,!wAf~MZҴ`?HPHGz#t,k>>/%F&шBVoN0KTrYzR fۂ{'ol0= kQD4{~M s|ý546Y.fު26."=itA5<n{H p.T!1bR$-/xTRo cHK蝃᜖%Y&H(Q \WLjB~"e-C -uE>L eg+y8[4 VcF굀oUcg1 )PT Zi4?9{4k rV >hޏX.K8'K5@ )Ωw}@GI蟗QCCP7=*-ѹHtx'eUrkf]?5C;JFL\1Fڎb$םl'Ɣwr qk!с@B Q'`[v\+{\&,izƜQqdTl0:zCLLCbfWWWNN,͔mMȏV4oY{ N&t(:r2@1q` a= 9B8'[ފ\jHzˌ_=))V禋%O5Ө~(b8'GDzX"/a43l3.[& 7whiaA嶂-*&$=ױS* %s$f=TC:1OěTPt;vʻVۭ2/>},m_gS+YBQV S(s~=@EPs4:[hT;\(IOFRW?,mwLWxuKǬNEJwȘYcܓA\`fAцw`*>ҚT}106{٩yGHd|-:,=i.uA㜦ڼ &ŊhcXH[{Cu$_}FSz@*QZs~=B!4 H|U:RB3>dR`uI*l Q DŽN?nch;E`h ~hݹ@B2dkj3(„-A.ł쥅1K iXrfBAI8`'Vkxꔺ| u?`ms8?9\V<1AGzIZsvTwn&4.gܔuR;^͊D hxM.l\;J㗔UI(|4X}sy08 a:nͼ/KAnRS.dc^K6,G(" V(7 {dN?ɋPFz]*9ލ,vwcgϐBӓ+reNfver!͵g`[ tY$Ot't5X $=$4U SY_Wmn q+7j#{ v"ZhEV RАIlyXyELsSD0}`z^CľSe D #tF@W4є {IehLc _n>%b/sCYAdNVN$e:;$9xbƷ([[FdbO+/L:ѯS%(Q|qQsa8)bRmNd<0⸬5#&wd7½ZVe5לL %h2 QatpDƋ/+B½za{ra5Z-vVpPZKMN7 %2ZqƂ褅i+Һ3{O.9, b$1%l;JTЁiZZa(f 3ԃv\Rl3<$qZ9 7S @4u$@/1q'g<KqD/ eNM6/uӐMV,QGx{:qHVp$g`nsؤ56$RzQed ѩY(РK10]/Nӹ(ws}ɨ w?:5Wa^t|Ѳt YזaxŮW^V5?7N`R=*lK怵a,q>Y3,Oͥw.¡ؤifm};؝ʲdSO/#*LK/߰ uUHHj$s_O=BKª ChóB:)+:𶺚Euȇ肷OaCfM7"~” JD8UӖ|E&kKlȺU}~7 }tMl ȋJf#[ f*b]Iϛ[˦c:Bhi]NtR7ܙg38Z@ "8|I-oŜLI ڿcG Oi~}$6lhss6c[pFhr hьQ5U4ʴYH܍g6_d/_YLSS!JL6ba(4 ]aPD+\#w B:&5k>dQ nʡmB 0ݷm~yb}(xJC?>; Pq#PG*)4G#bþ<ʘ=æ JUmӿg4p` ҽM{0I;K&!MYD}$w>r@]ɑuѨǮ%cxICblǕِ|ekzUmuӺ;s:T̢o;A*cĄ_4)Ηk;7hs|cTuGu҆J+>!݆;[x!*'SM(j1'NZE̔94H׬*FYzoߞ 4u~9F+«DUVK}LzbGl*9]veP?;{"pnKce(";j)p\x~-?<_뼢Z (~Wُpm\ 7nɂrTcb!vxՓ1*>p_Ois=EI1w&[QǦ c`߯ erk0DL!'M; F>WT4ݡl5қF|bׯfuNf%ˀιh0ǡrMy^NLoC& Hn4u k\A!39C ζ{&g&",cDCw+3tH xwZpM')9Ru@=Zmdc9'e>~pAz}}Z= cInd N,V zpR .`go:%%(De6A uE3ց~*pKZ[GWnPfʼ,PG5җrtgڣv!4"Z%F$}uMqAX5qܝ]Sʪ#Wzo7Fwd,bdZ7tꅭ.lNMfʧF8sokVuٝ=g^~xGv&5([iҘő\{8ie!+MP38oGg /cT eF~D",4T%UUϭ|Ykn?NtTgK[ۭ'Ū}ME>lFؙtbOB ?s?wT$Ԅ$Fˠ:vˈyP.ZH_kJoA(?2e`@FxVXhJ@x?*zȳw9ԭ XWpR7խ$k4hY*R<#*UhOt #B7z!S)Y2R)sA?)!zPeE?~3TI#ؼL%b@Vd(=͗̕Q#28t…7H,JLV,on,MWL[C%mFJZNοq믄Ttr^ ohm6h'P_d`/&k V[Lq.-\ M@~ L*3B5 j6T-JvmcoQ\'C*CY/ P|Ӵ;|41 ǂKEf9QU#B.4fL?8u.&t7 '#r,5c>Nђ9ָS+MV. ]>>So:gmy@[&I\?*-:W,8xL \0gMR:Lz'!'7E[òP<zʦ"gHR+1 Bk6U zxqN3L;&e"T^={'C(OhvڃмTPu¾X0a5KCCʣC@Yq9꾈axH!+-} FwL 0 I__ܟmŕ?OZDZ뗮_Roi;_)=c|# hڄ^ 䠱 aovpĝ4#3h|#I=&v2c_4(%dqH='GR;Ɲ(03qa?禺~\4ٯYr=趒,>ouZ?֭sV}C C[CopD<)o$&?5ѝY_ Ⱥ4ߣiX!HaJZ }oPPi '͎Vl};3޴K|;(!pW> cA$j1Gi/7֌e"ڻŽ.=5 ZH= ՠN=ڜpiwyޫjtH/;G߈Mbq%\-ӕrK0>:[ D@I πi7c̭Zb(]|%hxAZ+$>T#mjHB鰀IêЏs1 3ɦQ( LɮI\d-}xme]klTHܫӷ-U92guhȔ0GxIw{&|;%w;V:ѕ`k8mb^-4K4*`w1?`gY.Ql*qQ/cc)$2N 9*nv1l*ǁJ`ߋ~ɉO}PδVa ȟZhCsO! >ؓ @LvLI4_Nl ]{Q@ g KL[4`0<>fnJW:mJaƀ;a V.Cf2o-PrhA 0_>On0R[Q:WJq^%?@re7r*h̠y+v& 'DQJR)pge}d'vJ3t9_#tko!3%PH%cGn[dPxA,^OfXn'w)LlX DRm<~C?\&p `To|3P|ob0(-OPE;*Eon~ odvpt`k}m$tV(7ɀpX/WX7o9tCeGh)Lxrbޓ΄])Ʋ-Lzb!X~|ȽieGTWʗ"3xf՛j_`;<";lݧ>+%&/cٷl+SIrY[~8WST=z}k`;#֦NBZA4}ҸD,#J_oiCPDǀ% ]j#+ 1M\ZQWv"C1P'[4.:K/ϐl6v}{rtx߿@&9c@ pL*]B[Tj$=1~]X\ 7E'Df2$+ʿwzۖr괇7/8q ~ZeԊcΓT>@s4Ȁ4TԳ.!.w-ڈ |F:$t/j[yƎL$M̳$C!  g-UkZч4Lvwa:i r@CH WhǩG٨VlLuL/ӆ$*p`?@Ј69jFm6m2Bg0g:>?i:ӚƳmrYIVdiBZ֌fS85]<0+y0RE"ObXi;ya|(I+:8P_z0 ft4nAwR&McQVAؕ583dnf|\*x6W<:KOq5|z0?3L]WcMZø4#RiR(ǒڢϞTlpx(dN%ȍ5s!kOk%0Ƿrwt␉x'PDΌ>`G P%+_Ι[kcgd1 #:~˽4gFټGxְqDK苨˧K-s(E }'Xsr+#4/vgâv>葁 ~`밒 MAMA W=볜W'`Ñᴋ_20a㙱lٹû59SMK#ӏ9@*:b~剥BkeB.[](m5.0E-R/&Dyen$puHm=Hc\7E<:DïKsC R3KKy:0[ю ^n~CT0?XL!51Uw/8[`2e*] n߸>1>XhrA(]lLg>9>z2+Wj F^N|JOуT8gj􆌖o懔Jo#&,6R?VԲh-.HU$Ag6L&O˰KɝXi;ä AsK$ ,t.Rs c˂`MoV0SEŷ2 +P콈S\؎\biR נy 5HoKG؝,gQ\?KH7̏Zt]H%_&&S {ycyĨo%[xU4Z&zImӱ2נ0}UHf&7&2bK͛)Qu62ص!fnbM Gf WkS.zTyIO_1+|&+Yޤ&|\kq)iE5 tևDg:3&963]m ,j(*;y.? rkM].;ֿTsIf(mT SC8/Xl˓v7@xm$#Z}@) _^-g R-ٷ1Ա4C DXY574G\ ߶x*U^o%K"cTo!-V 2t. /6D(4E`T>| +"|.&)s+%ݱ (#5;nRZ wY&Uk+4[6׶%ڟh)(*1.?`?JTVt}hk2S kvv%Q^V/ɻKcۄJ@?U0s|}mR^lĎ6 GI7Vk=!@'"QO 꽦R,'phj ߐ-d"c-*F5~߄ LDfJ?0UOx}8õc#‹>*T]\ gi5m LZ y%k }l?gEd#C9w}45ӵ.X8Fz*PE>#}?Tz~ݢ9pZH?ݸȭdrî Y5^X+9xô;G6~_A"lB$*ɲ3{"@sb Huz3$ӴYɋVKIbJS2Nę1 jmIkݱ?LOR]sc*DB*BL2z0j~ij J2gߗsļR*/sv)HMeU 7.`!#sƖĠjus5W?oƋA^AŌvC=ҽGK#ՙ:>Il+n֚23}ܰkJ ͹J0+se}f0~>Rna.0;[8w=9)=}q%n/X!Y;=bYK>9(W(kKm=;ʻF)s#zb{]CD6EjjMN"ďC$Ƶj?Adȟla \IzIaNVQ>0ڮ(;5`y} m/|j"qF 7'Ƴ,XZsg*{g=ESy\'Ś#(;"nG:h 9WSɃ %KH15}PJˣ|חzՑBAyمʗDz93YLO1 |WbJ ͇g".hb8 oeψ"˲i8<^&baѥLc+ZĴ0:@.gyY7p">1Zcv뺏P ob8a"@ӣ"(15CMY")9mo50+u|txr6 nM2,jmndP Cq^̄JFei苉u")YZ>9̱nC3~3?bj|e>s-KE9x}'Jcˢsr;1>;!-H7sS7c7lJoO(]}!AbPz$ڏjr'XHO䂍Ap>^Z]Yz 1@hln 7~~oxw ⰖVN#i(Bo+ŜU0?C ֎ ̇OJ+ݓ~TϬ{a[2 S)" t$N4TmȲ6?/t7x9Yͅ/@VU  Ad0DlڌYJMnFA۶*$)]=);,<ēA냺g rc6ԕn@Ħ԰Q S+h@ܘ#_;GcG6g4dӌChx ٿ6 kG$ӯ/9D(B6V Gȴ]l 0g// H 3Zg(뙘-$ijQ*k6$C F|. 7>|%(×Rz4L1߂%&*c[ց~i]v=9u/|=G(s4ʛ#\y3ʩY@MeG^~'/p qx`%4IɫwHXuÅ%t"A|~3 '!xZ`QhXAM&* ˆjE{Nޤ=-I?̢窢Iߎb t cbT0"*N8|wtPSV EڀGYb{ދPiI,‹5֟Tx;Ez@Ӄ|w$׉ ֏"(ٟ59 ;.g.a#ְf7ῚhJ_ iF D+;KF"[wmk~.>\0#<\vdP[w; [Ŵ[Ef9 >U+3 h``cA8)(QU܊soM.j Wq)4q(\83Sb-B{9ƍ#㻪϶I,&pNůEy3uo]  'W"͉Q{:QC`-A/!2/q%VLK#)3]ýzҫ4@A٥#h̓:!IY\J!ܒ MԴrsHpm@KL ͯ {jlQJ~3lf~J?G=8WZ":~ÞkOcYv_iSb=#wsŰrGCiI_S;jwtzv9U{VPu¬Ԍ\r s#?S*QXT%pݞCW ʘrô;U}DDX-C}- CbMЃQ:]S}*ڽ+=GfnE010DA(2ٟshl)+`̎I'}0wߩnF"K,J:cu lw<#PtnPۯ- 'Oὡ6n(;|4ze7gDfv][s 9 Jy5+*u 7"7cln\B'eNؿ8̍Nv&ivG1EmMr:6KZ-Fpe!7{ܡ}u^j 1rNby_wmXU ɹ@{)@BMCM•tKgK#2X,aÛ{d7ojv/cO+ƪ5&U$KcRM(0g뒏U0쯖9E8n93%EA+ڻsEdw2是K\̐,.m%uLú)b}"7(A{3Or GXtonr^+ʞ j7gTϛÜȝ74Y֮"۴AaU=#96wX;o'*)/*&3ӾmET<$/ dh>F"F(_S tQev</Cbs4 l%;ܒ~vauߑ v+ -!̒ =qm‡I&a=O@;Y;၃Ϋ[}LO<;#hN~XHK}sQFeƪUX-}j'"$К?Ho;nmH"[OP8Wv*(rD_80)k5 u#By/j羺W]9RV6QxGށo!t'ӅN9"04|0Zt ӣLԊ)RiGemZ]Gm:J#&a7<+6z\M$ AՅk:*Q\ԛ4t^D&޷f3l$qJftųvGٟmŚ_X%+B~k /^Ucot=CV𢍦›1.;_Иn^M'og&)Cw T'1{{6{C4~d׼p ̃K/*,#M;$u)ΟdO]gKU۸Pw~* o7Cn~Zӛ(onKpd7?)zz\Gf[(\j4mB=WVTݬm#I$4n5^#bC;H"{0~DwQU1lj ZMF WOlVN ?E m䇙'@`4pvTth>9IU$6ɋ,halCo¸`0lUA7m6s6>;/a)F%HOF[&-[=b!&(ꚁ0Ra/=ohH'!cFRް%5gGGF ̻S`|) ?g9^.8|da=`ϚY\YhZF?ssn(?hw#_9{& 3dL9CA"NSb9*[ V0Cy.'~zteDӐ~qSCyh ;zQ`%J1~so]yijщmP烾*wO~ڌLp4_n0r7r@Xh Hh?}:P~Hfo]y Ih07A t_4 *ԬxTmͪݸNx8FZȆ\^2*zqS.tXt۱璮Plղ=U']~3.VD~*'ZyaJmga]BNuDU'}BQ*Œ)ҍ]ю&cXn ]ڎ߈wH"id%$î`,g~cą,O- wM.}%_2! i^j=.?<'ۻKX0tơE`lNU=Qh2=)ø2S)蛧TnqfvoXJTe'C;sL3v2N'JGoGZMNxϚyKCK($cM†+NMdO)A}j}pըy}կƽZpJ8tq5{"1u_$Ix#fE $ørv( aD{_lUU]=* YHǃf5<85e(jVkEذ=|?=a#{h3QG?Lc3Rw5LC ̙;$xty`c?&%|^B+,* ׃j/9DO˝ ˓7جVZ~ dtV^5C^2kbt7PR1~ܳRIJ &jrIȣO#@hCyjTP'u5Zn{\*/p1M$՚AĆ+Bڶ2cR`tm<=t l=" Tk9)J?beՙ{ Zp FiXeg@.$> - o xWK8eTCbz}"Uak W*7,] mvMxy4ǟ!`ÝPQ5 A$)VYXK4b͑ ?hk eoh 58l)TPQ6KGS~ סx:OK'$B t lx7K8xAúPvJN6dr.O sk)==9]=X.]9`n|DL<_Lݍiw4F&n?va-}e[8a.Dװ]Ŋ~ ttˉ=6?\6 yMs5ǫ+fA+N!Y5ܖ:3q :2fnMFDǟEkn{ukה>!pȕ+}(6%%5i;cZ1E j)M=SL1\:! W!"oW\{"jd E9Vjs/H5_ah)L[S2(y LqSe]-,;dtÒDľo d|@;s"Rː[Ho~m(Es0wYo ,VFį)ӑ`)VXQMX/dZZD3|b|b-_S Q,yp 5Δݓ"b P4W& w$7  -3 V[ VP}[H?/,PӢcNlb=6Y^vG8u=t6~}솻R3a;D+X)OO(drw# !&% UԲDaN[$dQ^gN2',F6G_v8818F4cXn|](AdI&Sn_Z ]"oiQɪ~AJݎ9L`>o~^M Q}VA )DOz9 g70pL(l\Ȥ'i_vO=dbP]("o*=:>J)QR[²6Ŧ^~CP˾cÓF)%C&~3d YC56~ΔՃh(iM4(J|)d2c9t{-ğ3v{!,ɎQG.;ϟ+0-i"Q;Tn.I& X BCӺ_c<o9wJ7"kŪDKnqqW#q1:eSXܱQtFcw:/f$}ҥ ~Oڊ5}0 V/T`ֶGG7A- !+vupe |hUW.Sw {YeJ\?%B\pkO0 Jx]ƺD n%,7AARhqRe@]~IxEXm2VyWʛT:weIJaϾĆwN`Z#CJx6uV%-A#f+pG3X WfsaOiR!'2'"qzOtM=+\Z0ƇV˜Vf!6^j~N tp[d^όN8yQI C m@gs,3w/})~zԌ=\ e`&~֙Y:]~M/HUN'"!pu Zn&$BqM0b4׼d \O_PgjU8 )`$aάYWbv,[_Tj0b0p"ٔ !uFSa }!cBW2jе$[?~y+czFrB`t+* R !0쏖ix̱a@r?˦ zqwI|WM$ #qR c54x^{-񓰙wHo!OMH[wqUM͔"Tέ_m-.:bKFȣ/[Lge@&fS|יOhp=ܽdjoF(ڙ65.qw8vDKju|(wOmqy`$?W񁫃^T'aul@(ʉߜ>%}0&/8n EMuww_R a0D,w(}⍾~⻗lGH;MY vF1, 1[= Yw.ҥ8c.<# H79Oh+PZ'51:c q9FusW<Uy!ؠvz+znd }oR$ʺ'm؍Z,|ŽJg=x)qY.+ K \'ڎ5-Qac_7B3Ed!1v=Pc>. kQv m94*XtlVPW9 :.+Jz&>Oa!)ړ[0"Q=#V:4 Gs,FA|JY{ͫ? vOwK 24&O{ wu1F㚅̓lK/&kB!ʷk#NyHP棨>{˶c>tɶZ'ER$oJnņfൄjoC,)@X?jxßOڴGrl !YX>ةz]Y*z2E-q)R\ƘרU9[jE7H>D. HSdؘVj4-g<(ch`Y ajDf5싍d$@в@R ko}HS.Ui,7wRӐ%dˏRnJ:a5Y:ijMhv[#6&BHW8gI\ثޤR}d0 3U<k3 z(rlJM">ur"ӝhOt[2 ?{D>َ;wc|WXU l(Ys΍mԥRmY~P .y 0>juyY٤q=(1CtNpwi d.VB]SfН)1Td5q5}c "OWZϨe0hӝ/Y):q{+1`qZ u q%h@` u}XWN+u(sNe9Q2 NF@!luT [,*!#Fv`ޤwun8+W珏@Mc;S%mon̞6y D @ðIg6,o~[|- UU%\:b3L2ip[ V$s}s_THB,[`]zӐ.ucHT3~3$bevn" 59 d~j`@4 ["8uT%8Ra73%IBk:\ ?D䖨|*͔7s(~l+w5d &H<wQ<0~4v 0nnde L-"郉rF:)tK-dL4*ax#GaAN\JHט2t;l  Oր%4Q"=;Q{dbiO]:t.j=AA9YA^$B8bja֨S7eQwJj 8) l>"H}/ ih:+~Ϳ8Cԑs"؂*TXupG!j>=S&q#F2n< ew x3>q͢C0,xypPQOr-7QHD"n8K2.;~To<КnW bc)+FgwgQaǒ]|֋g5+BMlU7{Ғ#ܠ%&_HVUDv`'S~ZE7b 59,_'5LlXtÇ^@u/NڠőmQ:o$O)m kf [u$L^] )֤*> qq|")VS-oOaFY Y>RI.(w's Pl=2y忡5%Qk˪cMW3* ~tIpRsBӗV7 !sVUjܬxL Ւ~Wt a/Mk6w?w0k"[3(| 6]{vHW)I =m{֙ڇ$ϩ%~Y3?&y<4?ѡR|},$6r3p)E ͠ m`R[9y>NP' >X֙O=}0h7M'A㗹v1q1Ezn9 %lxIuK>/-cuUN>x;NjKᯍ1.46&3py #ɢ 횠 ëB{y-w29/׌)HD]C< $Jds|W3{^Ŝw Nхˮ_. ~3 QtW7xd\D{6ޛy9@ylWN&DǃW*S+2GBgWܷ>%7@w]N.XW!P͵cgkB4N@{[] r yXj o\D:G /a捭1Mt{\\ mqHp{4 l%L8LɶAmK5NEw<iJ+M VL(d0ch*$50 1U2ld zW@ Z86./.P ;b|Iq t5=} 0Ԛ) IETA*bjqӎ*ڿ[bV޾04NTudm?*g)#hFx3a2.NxڮJ7th+I7+ZԝXD0lK.MlUb+xv5AX̋&  }-W {q*rsC!x5qV`obNvnq6[+ sA;Y3Ŝpsw ҸDD{yhT/ niBF4]mW4 /(J~F@6Gչ;76d\mH_VwA1#'eU*צs3_`i؃A'4 ڗ)[ #2,Wu>lbN_Mc )+uE(n9j}B|ܢe~b::@FA_ݿ4DjEhDڅjjt BDbV d%>C@Rl (iEKH%Lla(Y*|^;娉v;Αo-xw1]F=Cw_`X.:=t^{3d 7)SƐQ,E+MsTv(#P/ 7ޖM5Z<V񘤅շmqLի0K0)ǹ+!6:RXqau{B %<.z/k26I'ܒP\7#V3s!EzQK c|r($>/h.#D6Ah!YyQy$,G?#O,lByo Y,1-%CAlS&y_6p"ݠ2ti/z'f2BV S$:UEάˑ<.$ssZM4` T?ɋK|ie>T'nP2hSNLDcH_Hܡ}sv]RӺwHWNmRMD \Q?Gg @"3Qh6szEHpAmؘ镍k҃9T돍0,_ / t򯨃T &"QCK@o?XAF~I[I $`DcPb~T}/ uSOu+OvFKM5~ʚ鋀P<-PG2"GxM b<pSήON !A8(ߋXekIcm'z/}C5wGyR+2PIuvhkl&&*|,si =BcF32_5=̾~Nn]bUK{F'E22CPFCT _t~[sju3"DiD3GyϧX vEZ./Cgza$(7FV0-8p߇W>1Ƭ41̚ J7$Œee! _bDUz֪T'^R2uԌP0`ʬ<_Fb4-md*ԑ]9 Uذc/_XUW\qj2/E(u.:;7et#t/@beSYmUNV)]d&PT9A-+" R_:J (<ޣoWAn!z^p-[MB8TTݙ”c`NSfn+lHC_QHc^.JC\C 6oqM$UyqM_{w7DMC C^IjSP7<I~CuK\{6;YPYςpwRF{xķڛ^ mF2`C)M4Mvzy_msvwbpuVcpdU%3bRW%QCm7I>֯&f9٥XD댦Gѝ6>p(h{(b7e)b޹L6865]U~ڬT}#[΃4Y>HA`yAd\I3(܇{3/M,@fA6L)At)˓bƞ9_#}߶5}; %,/b~LK udC&4aV@<WݍQQQQQQ_AI&S"~MՕxԀT0C4p]0n~-ubd1:ʅUj&$켣N^Jb7͵|3萂yd|g5RĜy #`ܔ&HG60 ӂ-.3@d뾕<<j^k6; DC7{G]Et|Esx[=nxqHlڷDf׿rV[ՂyJxRG=,֫z jRyWc웗N*t |=WX^>CoZ9)DۛA%0,'L1L!EXJZwPua60ARD;Xg6'1sLt!5aB$~?8xx6 $>ΆIP4߬!R@x{Y|8+.z-_Q;)*C],?HeZ;(nfɼUp٦) }YdE7XwI;pg#(5_臓FDm@/-9L5lgҷ  OWμ3ըiDQ%묂ssT_=Z^ogsE/V7 H [d)P/42ȧWr+Mddӥ !z}3i :A[hAҚ4}{,i tfY

\{AxNWFm#~9ok3TjD~XV;: @0ϱ"[ &Zv~jV/ڐe>c(!B܅A< D0Fk8zuF cna`(ʄ& e=pf5h g$򡇒hB^{5}\ڇZsےL5B=#0̸]̥1ܾR,d9Ozy=,QgdFrKmrLiձ7REQ˺^"{-)GBݱ pNme%8\(CeYhD5d+zqk te1*+?oQB x4ҌPk Kw^E]Z)g0NΫdYݖ L2JXf|˵ڮנ"5t0+?G6cx9< }&-n_>j?n!;a{_Ep2]g6xN%d@j { !cTBhߴS+YGvBv1HW-՝(,0_nXOA#c+?deWi>E|J;qG.Sq~' UVޅ\g&> Myѽ\b"![uʴs[Ϻ5^m _~+ȗ2m1I^AWJճdH@dVwA}#4Nbgp1GVq !9;1J`EyT:f^@i0cvV7F[kK X2 zI K{1!҈2V, ߣzow[~)~'+04m׎xW>WGS&=?tXJf" Wۦ^T\(tne4n758_}n:,Kl!ofxbq-e70H/ E: _ nJ3'7,L>ċ+l%/*ن-635o`"9hbzcS Q|0HG cAnN(i ^%{ZgTVN(? h%曂g;qɳ2$_OVb-FO&<*5|>@ d'}%3_ɮǃ'BG,b؈T@ ¸$ЎUu(I-vh&f Z]-wNݲ( )40:-c !Riaш>T%u>$2<ѱ;eL9ż4m*]J߶փ rɍ Pa:?ClK*;-REiC/ d#L!c+v##v3;")b2Ar!`^vXso 'TF3+VeU'ɐy+h jc3"6ՇFB8ȀupX~ylߧІW1<֘~nwB7iÒ"H )|ᴤk J5A'r\ὁ9"3`iєлN6&z~ShrY7RI8&1(-T KlNk#T?Eԩǁ4F]tvZҤ&m 3FzvHg&eZVF꿊d[~7|͎MHWx,!f{>OfgM 35}Meۥ4``! 3LIG 26Bچ(qFC?5sZ _[o")O{ĺOu UWS#?)> Is `AUFVČ*֜b!iM@{<`.6DilcYFp _2܍NwM,+(odwΙ)jne5zu;Aߠ;$2arb]T2_ѣ(֠&)HgmպJ.X (*`VM!]mUDD%1]nLc/pU6좪>V&A@0'5#,7j^ Gc=:}VyJ`ΚZ6϶zM|$bGB ƚCme)aX̀fZZC@Z1J.QDpsk1S9<@X\LcC;7u}߳ %ͦz[ΐL?AJm8:2w)w0}%=~s3CPQBBC?2\iWȢ( u&cUFI퓛pfPccye=+R^6D3@@- AI&S;=hk eP1V1f%ػk>f<ք|ô;~|ٍ!ZR욓) ɧ (f{<Al?/p>euW_{3YΆl !qOS ^ioX*2Ye?rGw+5]:-df&<>)>33*8i&A1r"Ʃ9H_)(dw҂thYKFoglWS5%JnAy[9ߤj$hA%rjSFwOШPy:{=ۈ0~WZQN5ekc/ӈ:Z6ۉ%a%)^78C14M rkr r?HW/UadRHS ,n=XL;<ΉK1{RκSYҽ# Cv#gL~h}ت)-{%Ơ{]祕jb A)'l "]Spȷ0JYu-E$!(a] F2 G͇/)sJFJ^ɢ3Oκ6I,iC“0ITkp 1)GU.aŀ)~ #k5DmUlh:{=ѾO{k?hz*mU+T*-IaS=e zayjrD*Ӟ*/{$Ufuҏ'&5JaSiAw/W&=fMSf| ߀ܴ%ZD<:s=bU[O "1N* *\\|0q6LxX^Ŕ0|ñ*IMJfJޯ XC?ߗۼ&5_G bmwSB YMK.Sx~<T,,hQ~U?DqI?ѿ:JwIJ]PLu l sCwM|b8wQD;gn4!ب,O0)Lp{u*m S/lveQ;B_<+XEĬ{>~Laa`:4 *&xQ.hwI2o|:Ňy(`|q` eVi .HW]rb¿E̓Œts8lxJAY^ZGncis{T7ഫ?׍(;樌$ ZlMOCqb?BQd}<;XLͯAur:.nFFҭLW|xkhij{˰ YQE ?fz>Ӝ!|:\E&aY aiBɳ=I}G9Ú@C0`z!@#:nm%PBL%ffP~d3q_bXC'IG]<d%ć-BD.P UQɳ mNTV_0P4wz_AN.(~ne4^Q]NEnJԠ"-ED0mgttw^s5f_杻C+Hͥ'm#n#Ӡ}F=)e5"t5#2pJJ4B&;)2`k T]dwF5@K!D?Q=6-ebӂZEdPF>bhF (ʔ[*@~L\ֿB_m (2qN'`)|{e~9Q .-m^#زжgUY}^YCɏUu\MXRkGSgrnݞ c|<WQ6o`dTRF/)l%^x\ `3!hSfWrtFMubmĹ`´vփU +B~wKa"yo  Q:AMwEiЁ4 .D>g!U- ʴIh,{/Ѻ2U_jπ5Ki`C8kCS]H=Qj$; us!c.'!H! ͍sںOŒ`qnAb5y&.҄5tBol.3 g4R}*}EEbL8DwtQ%Ig1ʖ/Bz1{sLǢ[ eȀxpakoM?40HĹW*Bsἒ#BG)^ !W.ZH )MQ b6{0ʇ@-'t"J"v&M{9OU_;W%u%WS)F"E%1T'nluZȋ1^ :deem&-߄`З&7)l,KR*b.p`c qi 6@ AI&S?w")o} Ȕg1Qh\VAC/@ GOXn< n4KqO2z&$yV$q[^БdH.L8fR/zkۗ H>&LlJڳ^R% ź> ]DؒO-|ܕAzLOYAQYM0(ub`M*6E/ pEVlz׋;WT;FrS 35UtN/@iHZV7ǂydߤuE#{OQ<E;^F,ߏU4ƃ %nF$[=%wqL}UDNx slhͨAUyzDƲPƘlheݕ 4Їh(Q&{I.Y%5<ʮyWWcQ&"mRxvHk!.EX*ӎ/ $(<9h݊mfT,é[9*"`@YRGZ:(7sDlq r^Зc2eMܦ֣BB|f"peEIR\߭NL`prg$iWv4{K]]0>qRMq0tT>lUp{+o91kf"&FZV{qs۵K }}3awZ|_#ZHGvRkL)8du^綈 \Ȝ]t?fZ"ĸw˲t<5?aF(WybPd /Mm,g"-'WڬDhpL]-y,WWat}$w۵꽸ejr]h9?x͡:XAƇ 8nt[=Tj쒸hBK?>$x5Bv(1f,z@}C"ocxC)F'#v0Е ftcHg%i $ -MLVbXMTL ||n4{pcBjԍpVNUX.X=w6quh]O؈hɟ5ьm";Yȶ P-<(IT]vEt2:ni$CuX,h@+bhZǸ[|>޳z&O9X^T<_WT50uBF3v!hQvhuTgo%ZV9WhZ{|X ˭?.DW 蔁}`xpuŨgV˼e n3p *~PS XFs/>֙V"_ tۇjwʚ/mT)k\ Ul|[lf}q 'jΩdJnRVJ!]~]s!s-waO,} 9(T^,o ?,o?(h,PO;KdM@\:B40UL'|"cTۆZhy@Z()AZ#Ƿź+-x<'k,+bnKvwdDKM3~)lН'5=p=<;,n]Ww&Be)ե5ȞX>69s XyM fx=ƻ5Oj8߁*O)L{ g~;IF4^.p3zwd|PdOuyh%w#]>Cy\IúzwlI^fYYQi^r3_?zEF1B$ bo5rn Ɔ@$*9x(l hW ,P}$:~1+XC!748._y3wlTn=,9S =UeB/$VFN³X7a:g](Hvk[8(jk&\D޹'ʳ^QfYnݔ!:3h aG$ۭmk2Եo{eۦ"f[b[-?&tQݧ;ĦO&V213sivl8ߍا(5@Ez>Eh*3ȧ[bv'yကF<ޚچcXy vqPr?kHxft0ѥ) | :l[l cR>YMN<,P6Ҥezz7h~yhRYOP ~Ǭ[pKwšD|k[,r.So9 6,fM&[Pq Hrf^b̯ 1{Ynʲ4 ƶ /1)!e+{"`[kI7L*Lh!KE:g$SKˆ3R[Qּ頛-օ:89VAуï荃Ǘ o,cM' py{9`ZqE㎃:A CW!O{o|a&?yqJ @Wd3+0ٴ7W*!3ΉuUqwvˊ- `v܀JڝƮ]j!%.bvUZnG"PVj70=j'B.qG_|NrUQsS|c?Vtviޞ4/RQelDUC^zaPuW;Um!{! =iP(X<'եh3I#12!WQ4P˃ m8Xy%o7(4ɖiSKtkY$MC1dvb۰̒ǐLn:̚\}μVOfI!^~ת+[ɲXm`~sDGhSf0y&őQaSx\w P_ hއ>rIA:uGA*I&S_g 5>Ny?%=w*o~;dѰґ6\C$}mo !<ޱQtռ\*jkx:1hkd-G0Z?b x/v9O4H!3>< 6+U&qHZ!r!|X]ubL37M?q"cuz㒹>o 6:mCx*s>KKn  Ôt<ۋյ|7>Lڒ"W&zP G>G2nq]poh@9ѣ@J{'Ǵ@1ma؞|VtJ ^|&S"Zp [h:R&a~^V ef,sL,rG8Ttaj?gx@^-,UNjZu|~5i7^(8P8 30Ff[̖J#P, KVkÉje$NG&OaMAsvgu}b?$i$1,QOfС<\&Є5z(6Uҙ?_cʼnDZqQ]$|,(t q$, =[ИXG'{-WwǣDUV/+$-ʦsu̵?:c9@GH>-ՁE/-V:5˥4TB{e|l>0S?:+)NᅨnejײRxVxp<!-,7Q̑+Es^$!-KQq/{'KP"!rƂ =u3_{KegRF7*c d/}]Dn6!w (^9_ Iq:4#[gxUcCS5Gbk\?9bKtr?3o9=:^0b qt_$䥰ex?F|pQsbdH" Up']k94ObP+%Zw`+;~ҷPb*U 7~ܓ2Bk4ѧeEX.UisQG4ba]g3R *dM85݉fX='Yn-qê[h/˔ٜr068ch> iwPё7[SM\Nxه1Okg.>^!ݩ?`nBW7kcg f1Mيq}+- З=Q;L~\QQCCx[ꪏM7i.4[Q= p1pb5 1_X9h>8fjp:< 0] =w#itT^#1*Q0|T)2TZ{$ɘճ~[dߏ@ޠ=F+]gl@'N)7@>O~_>7\=@!o#d_"^muo%SL И*`h\ gBL#+_u|%r#PmtE*8 Su ˮU {l dHJES8DlY#מwe{FŎM sA&DE(gPOVfC'N"{IxGnz/s#VM,9Yh]lQ0`vU(L2V4/[ɴW$TuXDq3ϼt%N7*.@lY_[^b ´ws[ưr`mY!GW$(ol!:N4twwM=VĖoS8z2bUGv4Z.L,8$] 2.`/x LجR=&-6u\wdgx@Riv#3KŎck#a Gրg2gh Uy-`kPk.+DN 3Tg7?=HrᲚռ@ A /OI8䱌OB_-E1$&-p\R:ˇ` w&&#/E ?LP팵w)o i&s)iKn|5{9u!4i` xpxv{LO h>7f H M684ԟ܆cp%+SRY=tSY7;lo 4`_]#G b)_'c-ili qs/Kն"sWR_ÀhI*5ur u!4{i <2vE\ ai4,!'bd|j#Q"|ukVuY&\ &1JI^+ogۍ{h4Q8D @gt^EA wykW9SGRYu}qt(E>uBi:Hkt Q Fr1fQ\a$wKMհ4g?Fb.bJ7eQTl!&EPiLtB(΋!YG6^hM~y2lWhz>+#$2moX՘0? "fC2};)H`5CrSl~CRb֙FÜMa4ay鞵 r3eJ #;K2" Sկ&a*,jKs.ld kP 0e"!lSٱ,f2P):sgvN#fnKj85j8rvhIU 1LŘPCla0x,0$b<.:>$ '; S-<6e7A/Tp s8boEȶI WƇrI(c0vagXg~yî]Me9Y:t`) y>4*'ѱ,o鈱l~.9T0( !ġOg`P܏zY0nH0ޡDCbV~/a3usw s.:tG* 8ͼrgxْtp&3`٘P3  QvGiRSg3=ђ^"xrV\A9e4G̷{Isݰ/ALI&S8-܁ wjD$!NZolMY4˼n݇3$vN*Jʝ` MI)a֭XyIf G Yz~¢\:F!C^RiPg] c$>ܚ_<0_εfYmK~_E[M2X6oxKP Kk(fD븝J܊uXSf"V7Uid| i1׵p] FJlaJ~\&XCJ0p@]$KOոy\ ڬ7øpq{?퇔mQ( 8yiim<ͮU F?]rZ],Cs:Ox 8k]TD8Dތ.S`?yQ#M䀉CZtYkXY?UDž-8fh3F:n655޽fsx@Mt@6P3B(I]'-\@-J'd%i C4jܤÅmLE@=%26ĉ+uw+9h 8 xY)}Cϻ{4BTL.[90M)q~pX+²pQTqMCl,Fl R@7GwH_Z>|oΦL]\]cL[x 4#E"ۍEb+/^!G8 mveKaɑRɃ-(WS/&nJÚBakbauN}Fzsb7dEgyYYeu@U-99@3O7W\M@XdEm*˘Ȃ9?%CD}5e^Mݮw%f䩶QɐFVڥV7ފj c)L N)s m"E8*`\jP'MKر,}8Wo306^T-Ğ@6]9aY{NvU;L{cv'Tr=zTwKZt>]M P>t{"._D"j!5#M)@$o;9:U{X|J9^L(xrs@;ȑ#2w|LHDO&%tKoY%Ux|4":QU3j 퐵 ǒt? Enhܘx*uJU4'w٩ rSyX퉽<ǕwWqHQԒ%[N1&װ]İ8Tt5O&#_!V5ۤ7 ;[B{ŊEr{ ߁>%Tjy(%!ش pP_d'x'L{\^iR\# $] :EBxDN +-Y(#֣},msxY0s0uH\" jlĤz%DrA5hPkvIRSoB(5r.?4ӝE3.Sh,'"u5gJyZaH$rsy>qwOnȨ N{0t) ^28f[UiH `[>wv\ZNdB9B tQRˇ^X><ߐՠ}<B[w6[}蝹=1Dt-Ʋ/>3U%me>ߡ!dOBT#݄KB7睕 󬿝{.iQ81mlO(LQL9)a7M}jy"#&:l7xG??HvY(b!Kh/y6Ѥfr1O} rysGaW83[#>;(_.wZ{N "?ofjp?M@f/=Ÿwm*ezc\?fLeW,!TL5\a@ƍY UKWMK|-$u'8ukJ_XH:a׃o ʥ]!%kD1u.Kat+T5sAX~n=6~P>?@bTtY~hdN2QUE(: 2K@XlP}RjZ]" eeVav bAPI94'd]V\28GOCHcn |Sx-@f?nhkgJ=c 0mC^MC؍%. \Z}{g޶G}Ìp\!@ǔK3zl#K@Hv+6V垙-WĆA㙷HJ&` 1d^{/IԋAho}HzhzEaБgR8I"hcbKdB vÅVeR:tq*}ro#sn(9QTNݽ H @"BW/j$y|eUvJ~``f?5Ez>*#%O5BF図Ywx5)w6J1'_+2wܑ^؏_SO{d6+;ѪlYL_\nPWg9^<}:'eY! kjBy|m d /ƪ3*.k;LH{ۦ lW@qO|>Gę-?A˙d? *$̦ԛ-dkf7|+?_rCY>GK^vX2c^ a`twz |2?CGqO^u͕nmkj]? :24Qʤ#s>A?Y]桸9qWָ'f[+G\SWSI4( v6.'.m $ ,R=4 G-2N@-Dz#'j[i8`Lvȉ: ԀД#2ғu#ó,Ͳ=xL ՇŎ7iff<,-$ {agܫ0|n@kv dU `zy=x.>lȱuܽ_L vN܎qXd#Ĭ0 s>Bz:~!nx+6rŒGJmKHe0Зi);,EH .ϯMш_\O\idC-}~}Q%)h*5Lq5쇾^>*F\pݴ!`(qHN'D˺Щ@(Bhz!m/V<|Zx^) \#S}>&˘=JJyd63;0Os} ݔ.Yps972l{ #tȰ1*ڌ z`sKBk/F:>H5J d΢;+\hǗgk2`{hR4#(J?+wU84Cf I dȟ~zl@q Kg?Ã%OHt, l"i{'`{jԹqy?u:sl&?Fm͊ vS+@rٽKT 0ja)8ĺQDb4PRlxh(ȿ@@#^9CٰoN]Zs'aTmܥFLjD|Os[?_l9hP %8|\Zp^vBS=AuT:n*j(nJ0_W:pQi&4mo΍y}Iӄt6ib ("˦#&|I#DJ:%fæn 4RkwT'r~ wƟc99[)o);Gw2h" ܿShLWi)Pjk{)9_FbDa]TPɜ"ۤlj4 (J._ ufUSS@{E^V5@R K 4HT_=Q s S--R, ִhOJ6F!&s2BBm)v5~p5s7DLuvW FǤH酏GCd4i{m7J`EAւ_@t27J#cϊun8=Ǩvz:kxdƆ[حZdLDy4=ТPvo)Nx>q_́F{GSo!}^PL#f^SyFy{hȥ%(XO_!,{wj1?4= n0pֳ]~n#l BTd:sn'p(e-O'ɖa ػsK;p'ue¸e*1ڰ(')$ &tj<6s\ʥ?!y_58%YS,n?Ը@jWrSM4eǬ~x@Vٺwo̧bsHiĎ݊}#^zSnw'k8|_E,A.c ~` {Ep+P8!SAJ4'v 9v-C_AҜDEYl$Ͳ9gr2p4;Ikdǩ̇j{ d;O5g;΢;7'+t|>H{K۪F;ːB]Rଶr{gijyZ f^C7ISdL_\M {GlmQoF Sd:EX<_JW{C"8A PQx>hވ|Epي3ҋt v>zѥ8 q@ )Ԇ:hWZśep_Ӫ$BuERVV1K@G/PB@۵9,-a+u6ǰf紑ҥ?Ԩp䥢æܧ9䐓ӕLrX"spKwq.>dDRdi%w ++ o^_ oCD%$nr|鮻2 " @kU O'v@̑O>Vj#_}]`O.ցȺAy=#z9C:Ofz(~a?IKKlg0>>J/5 -N>d{= O%U $Qy /<]ޜjWƀO&oPD]=p EP0M?W ˀdŽ\qzshaw:I+f 6^6p?7|%GijJw \?]amwqvD!LCX}Z"!Q&krL#J;b܃]T+v܄=LzgaEPB{QmP3Oa{'^idKH_mkVDX '0#' ZqV_fܵsMJ"pzQDP=<Ͽ`~!Y /^@]6F;Yd .#'o5{hִi!=% z ypY _g>ϢA@JqEٳT*(H]!ݻɓtָ]Q Hdpb-}*k|ka|#w=>"iա'@7nfv[>.atIcFNw-\ }HL<فjUPWmztul= ^t+OSMvejb+OQGx}Ws֩"sIJVkP%]Lr H1q | $h\ [-9q6;hև͒mk)<^)^0ɰ"vմJ"*.PT`m ڲW!s$ef`tFƬ{y .N?A#DEmNQSG ԵzCum³M7z5S]H|ی&pdsNUxc,;y[`z=ZHx,Kssx1˥1[U甦T(Qg!?ZKb ϺkkApsFW4<:#]jwse߾?ưתN/mCKrM(3%_OTu9hT($ qrD-9 ,-#]*sSHKXcjnj} 'Ej`:뫓K`jgRIv!Ƈ}//㪺/%'26;|kĬ .DH~Kfaek #,9xOuˋڞGG@ONJioН?5mo :M&-3u|j;jO;al qZO@QGPXh9X:-l:LmG(5s}4VY Q@#/^X,w&,jE dtƑe3a9 _ʾ2z~VhXl~'ZDqZ}m^y!`~s?Z',һۤ*]Q*bt4b?k嵁 n̖o@`!}2R:Bc}DoҶq#SCS;OYmK/I[{ o5J+=U8Sz*FA/!C| Z)iVz(RSիѕ[# :k,ƒul] g3"I{awkfEUHb d萯< `o 1)?Eex(>'U^ h`׶N>0Eꍉ` !v8EzB}1F ~@= V2؛(P5g>?v;ܝeΑJ\淶~{J6)e;>O>Z*9W$8sOI0NUvYraQ~!DO/Z ?; )ejI7/HP[1w1RMƛMrc)ng1f/[*8C<}HCFRW]>C`xbAj'q=zUgGG}-ׇpj30]]YD|&5R IIKKL-C]/~ AI&S8@/)9nt" u6rfZw).zA]ZerYlRrj+$%d!Gy֑Ww3b>3'ɨrUŧ[38]⃗ffG 6kzi5n$NwwBd c&WR)E$ oҔ| LKPD6|  Tiw`=-)9z;7yM (\-t7K@ Rx>61֗Ӭ*G&u3P%\ӻ HEBxDgx&ƚDz_R:%'1QeaW,vZ7"&CmMǹs8Dם5Bry:Qdv!gyW$[st$.x~~ٵM "IGQ)q {Q꺜ҭ-#׻vk ,K|@F+Wv 6yT/Qt ֺʒ|iXᖛ@_6A;\(P w>U/-8qNh=c|Ge% ֧ė<Pw" 까gN 5_C0~|^Bѹv>?yo)h&Dc@MZLYޏ .bI>W? F|bc"i-KE0 y iYQՓ BV4r l`{NVrӅj>px8׹BXQ&<)q۸#[L=C<ꕼܛK8XҊu־$0ah.njEJI؂Do(vp< .Pe (.a5iI^\V_tIz*h2lBq71d*Eb1Ձka'.d/-'Z V"y&[oE6o0OVj[@QX_{5Gl'}{y`Bxv_pus bKXuT<1s+;neiRH,6g r'*ހ$I[гߍF I*~DȷYsj倢b#E7EeXہ76m72фz05G4m$ ݉~#qUyeyb1{u ̙ V:C djdh_&@ d('ꀜʋgU#y`d61|1oҊ-InDYR$V^e!ZL V:!bsxC%MIiG8Ix8j35?#8iZM PfKZJTi7(~@k4f\6 dȝQL(ZU"&lϻ'udw&f!DP⁄ζM<@JE".Omv\e\K4ț[{vBh&HAAMrK<n0?s;yt `1!$,Ljde๬&wuy|KAi${㷙yذeq aNjݱ%2`BwHemM0bpOE8\|YCAyƭR&CLP%x_rS6Хz2:@. 1Da@l_Xo^\42xڹ &ztDo MdkBZf eJ{|E|:*4m=5o+|MX3(C1C 3kgE}`uj+N*VL构8/NP6<'kWk[ $","k9ٓe -E>NX5MJ=ʠ[u[OHE&Y*lU ϊ)H"`giҗiv|#̠AQ EF%dfE2JIM܉4݀`R.Ҙt',xOF& m䲕F`_!ʏ6f JUi`h Z*h SH "W14Jvis-Eyg/EeOL0xHJMAI&S%>8 2p0,)5aoRaW"6*g΀ Ayw_LWg jϭp%MHfPp纻p@Ԇ2<V,RLjVMR;[[s>XpVރEv{4f r4*${+u>XC]x_xtǵ?V og0e5EB |WH@psi$P9PlIەf+!On̪ pTK/P3M?L!#f< s>e*$S4/ފFQ!sSySH(K  _Цh dXN'ˡqf9q[9 eN:,e(-z I)llQmi{Г];b(鐑f[E,K[n@㎡a=捀tmܖ&/`L[Gk 3dIs5ݽT'O4gmws!1ƌr3FB?]8 ,rT6iwx&$b@ŷ}1 &<3J_U@k@_n0Ƕu˝9g<\{@>=]_F #׭u锁l!b{t~<r &QlܓGkm03F`[`Op#qKDH8 ˧`V5+ߘm'Y02Q͕btf+CO$|8,q\=8ugL|U14z `mI0y<}+#?ڐ#Tظ-I?놲 @su@m$0)ZaC` C[wP6Qϼ4RlнrJ9ڍ/>N[󝚮*AtsQ<YUcMWldy㗿yw5˖$+uv W>pL {DeR5X [H~7^VxH?{H* mVI؏*xl}ڌ(CM$3 \M4_8i1x8ĉQ缀89EzzGRE)RC ;WX}̶9ö3PF5> @* U$sɜ`#Wé2 ,x14b4Y!- ,{0gh%u (cgױ[Y{\ ѮN1g7*wܑ40GѠKpdFƽ'`ta%}X]CCܮ/Ym5y+Eaw 急0:v?wyP(dx~L]I?VczU"5-8'9uNfw5Q@2^ʯWZZ! #V U)@T%ՙv?-F}(,8~҄B]qA7g=h3$>QDOi8%g_n~DfYg;n2#\Qc7"HڣF''ֹgV{[Ӽg3 "8e+Q pU-> AI&S8@tMֆyoPe'zr^ؾ%m>T`$UزxW{y!xchR2H8yiKvW6m"׻53"#W° 1]Tf+ B!C&oX R7pm A6# 3LJ'↵>R3jv@r7sѝ4)A}:1]yEߧ*ҏUB?O3O"ejFcfV^uN_,\>Y17.pZ6fwGL(db7.jo$5fT5; 2'ϡ 4X\[/N˪N>1f%8GiddthF(^SUː15Q\0%ۏD _Lmnlq\-b-,5H tʩڿxu /3J%eGx|Wgc4\P?0Mnn~R(Zd{MDJ 󇜞 s&T\.=~m1@E2/k}L, ]yXwt.(H*xw<9 9 Hb Tnfon<*Njsg(}-'&X5=u%o%wډAXuQ tq &Ac;_}Ŋ t!覶vw5l;BFj<}O6*[ ~̹ +d2Ӳ|TMrRfE< \ >Sel/Ɵ2gլK" J{R3b&^%m>XCIR<>*0<("z½x#"F@C)S5[7%wn 4H';YCy;g~•oE|#˕A",v0/db'-x ?>rpT!3:!!8׫\``](gW}QGfL`Yi0<V׭Hu$>_(l.ȃODYzф~eԝ7E 5Mb\V%6I{}U!GXp޹FM|^0ZOo?l>2}FHD)ū5G_4WtЯ0GzZ<>ԽPU+xy=O:TfS^ݘÉh(Dؔɢ[/c#@J_{1@ؠ=.:E.nK9S\PPM0c$m\Oid\T@7-99ZD*Zl=j(wf4۔\`[СvBY&զPdS_0/2܂ zam0=u$KTp禤r">S7pl8}O(]1'U3̯\{-Ds杖jJ 5q J2%\%?|>X櫎Q8+<>>[K&:J$1 $3e i' /~2u=wF=rlC2AQ- S=QrRVu 2i`*"Nسhg^Pl F[x֜w3=( #'mfcg;@y3"R^=u|*JQM+LVhFuG7l pSsP%'6"ל^4Vuoq*59o/Z {K9&P"|%{@# yR}-I`ot%VLF7 KuLx vCZ fxy\27 v_fjN. '@b$.T5F-Ϧ~hlrrϳi]1;ӨXFk ?WY|%Pv#  .(wn憺g\df m&w>@q k:\x+Qud@;C>@2wۤQ]iѽm 9NePan>P_(Q9&|+$\ }`с`N G?EKIkRݺc))b)߹~{7qQf?&1qh8;wu?G%B? ōna*fklӋ^ )vԇhp!9-2{N Ӱ=K,!eX[7ı&},[37jNpodaT:!f'SM}P\0 1Mj21*؇yH4^4-$q!j_|,KYp~:eB*"s U#ΛsDŅcy:b#^bE({r]Z7Lu6k=yOZ#X8H¸fBz̈^8'C.t)'>wdVrs 7;% ^JSy24?QA|+A${ܑr10)%,m\C3L#a#W0$'~F/dqjt${HqxGS=O/~S-uf  tIU]{%`4Zw 9-``z=Gǀ)%)ז^eĶ`2"gEғ5o[Dd2-ň}[4\!<$Ş;{!J8)bM- 1_H-֮&bPuXw& sM'Y Ч+C&2L[A,C%gV_Z210k%}7{b4q*mO5"ܛ "c@9sp 034A a)6̋bvm~!vgZGs\QgJz}wαTv ؼ4͙.P叇"t] *'9 RYh@4E:,;[ec4fFzܾaڜmq3{Lˈ=;b++iXeER$| 3Z[֔#;&*^muƚ(OY r ' WX B mM NFa+Jaq t^g.5Vi a0\38QG]npېרf+L*8i^5,#[v!`9~XCoɥIA`wRtx-bSy*)XyVQ xx(b{ZICH)یɡvfŃjjt^o\J(qևl3mUC6Yg&/K7+H,^:2+~0c <{&>\vK|֭m`&T&˼ނ}-)徠ױdwxRi(R/ꭧOV5BM=3N,|~MMr-Qf<"jhBҁZ[7=fµH?7oҕ|FG{W ?˄~nm9=t}g3T#*VJMX4ffR2qP{ϸȃ_ T*ցğg &nei}ILF1ՠHm2#a˚m,ZR:?SM #{6&|iJm>ZHzAZY}{^r'i%KC^]3#juQO?d '2qS.ա`yԟҲ7"J[--0:ơmo9ۍE0b qhiLNβv{)Z@eBQ}>Dq]|E Eи振&UB^n[TP n~dd-/:}A(A6R7QI)52$ w5/@ AI&S8@wԋV4*p{*ؐוl9JQPQs:!m@HϹTS~yDJ:&%w|Q#[Ƃ50JRuvkY繩:ZO|?tŋtcM#39oEt`֔k MtĎ&Fhdt«AOp!?5|tos%oCSVUsOSX]Fpg5WZbg`0k#8?PbK+yrH|/)]1GIcM+b؊32-b3lo"8ӒF 5Nyx:s-`u[sdgm2ʌ JKXVR-E~WZ֢ޣ'eNI_(ᑊԫfIP>cGWg 0[6:e Ĉex3nh_g+X0Yt$ SE\ 'B22[19y*oMD~)@Hh{6J*+Kw$tRmGpS!Xǯ}g޾ZMf(5/-Hg>8Xwnڱ*RY$R}9hBF\>yLF-IR7(6zĐrR (}(Fa̭.@ћ8D ! GgWMD>rjƷIŚrM.)Uxuu_fqDg/g/UZz8(PNe%길JJdճFv^en^I_N;(6}8iezsʖ_`WNU2g)g@.@7ח3)J!p:ak#,\粓*R6R KvKI@Nٱĵ7B˟fXy5>Qb(0&Upa .dtyrTL~P&  ᧶A3r؛p[LlGR"`,vx)R (hz>J9 z@ײ>S ܏} d1RȜ  KBWjoBNf>#xPLfpjx=}D@)Sܴ5awT- @: F]ێ vKB 6/_Htj/r-( 02F%ڴrY{6Š=mvjncV@QB>UM(5Dn5ZǤ" 5 ϵ䉓nǂw`ڄ3{i ͨhtD=&:}vu4*3P=߹|9[~=&=`J4t7iP΍ 9}~ akcRSR#zQrrJin,ܴmڶ%n0E{ ̳W[d"eNhw$60XJ]ew\ETl(j۰e1,bMQ77Z)cz+'ܾ҅P5!`1dsA]Ę덁EKӯIȳ^ G<@jUD&UCr2m"NĬ n_r0u&*0X*&\,/햃Ԉ6-C?nő*,h4.#ce7fL2S&1aچ$:dBȬA6C$AM*R 3=TrzB ֪F䀳nyYbsߧ40iG҆B!z:娂3 e2r'n|ңjgU5_اntUv!җ9mO}lƋ|"6$4q7p(Ļ6Ҫ42kA$$$ x֖G3QT[B}Vϩh BO^>>$` 7OKMxyǖFEXFMzG Tv&[쒎+M+ʲ9HQ >ES# v&ZnM` =9,b>a">-+ Lu$, X.;;F(+.Ge'qv{yK)IM 5s:Sj+/C3L#rS(6jc:7M~2YHRyJ*˟d;-6mJB9BE&Yggg83K;r]]Ğ"'>#VA&P&o!Yiul'żE\ncH}t;N*LqiM;k& ,"F,7"-a}2pwf3*\v$L؀׮8_d`.\Lu},)Iܨ5pQěϾ0cEIb qX}Y pѽ:q]_U< .(=J#|h_Z‡老jB/XS|m@IqO+OU7Gkbr!zA3I&Sni=J 9 rVi)ݨ.~/[sPJ=$"Y^ZyWA`qSΓ j!u M/<4qZ4pfSV"j ! /<*ƀD#">}ߕ ;AjGLDn"nMc~uX,ψĵ34,k]XGx G eT$}6֛]DKw9c죁,dNnK"Q0h;AEX&=;54&Niib/iȐ7SzSMsuvA+zO0#|}GA'F#_!Đy49BEN迋f^jobDc^*Ҽk>.I-v#K *.dN{YLkq^HT㹈ZE:qCfˑ&0k{ (J]DW  ] PyrץB'0ע ծxSBC(:ZɾwCMog$7hY'BT,Jmb'Rm[7z'K+ux }nba/˵}?!gK)C Vʩ V ~r-`6BݰWa(lKy$ N_eӪrGj[=jI#(uQ;ї(-#PIη&`0R@9M})RXZ//$/윏{8?EsӅgG$!pJ?XtPmt^Mn !cQh⟎;'Q`#2A+m4wЪB3O]Xۂp&4IKJ%sg=1 STjP1/cX#~Z$%z,Rʼn:W0ۄ`#<:y^BHp'Wq+Xj\T^D8${;tF&Cpo kpjH)ދ/8]f:D+lY@"cEqO]2goHtg/a qv_iqB_e\z[̍tv<M$$zK]!e3C>16QTvx]zɴ*D#Nan(N%zGn$skrEm\q{3h7>9|U|8Vr^CeNPEhjjnnS#x&lygM F㈨@ɠmq2|h0# Q~хT-6j贽YIفT/L@0u cIc#yj%` Kģz+8擻j;ТQQZX)sSlӌjX$儈L+9ȺpJorP ? )N 7yd! A|2 f/2HɻiLUFBx~M |;iƋJaXuwP/V[dMgЬ(?}Ygv2ZC|,l>=Cy+ҷ.#Ҩh~3F@ v(Yw(Y[#}v-V1 ZCgPҡ_V7h#M\A]k~0EPnO;W\"K^‹v26N t=>gů8()|L0DQQsNԤ*VbT YS ⨚^ Lz ܉Ǫ"g' 2I wRsB?;t$X<[*fqEZ'*Djr9^fBJb6gt]z z[X0B7?P1 #,;A}i^T-:ZC :#჈ W>Au]МF;>/LoduI5jGd&:7U)HN7&sY{st)*)5z_q`pO3tҘ#%H XMtqIbnEPV)lD,=@߮DVJM^{ س}77?*xPvN?G&nUNF=OÌۮSWxھE$a0G6Q"5(߳Q}? u)IdTrA)XA3%!I_/?Q1J]R~|?Dz\28{%qUe0@th.ИQB/ ]Sq*07M'v-anxd$vwߢ Cq|򎶸:bP`| v;Ѱ8UDCq \6ŴʼuZ~J> PNdc.L6or*WLO5%% !N%ApU G'xa4LH> 1khx~--^V.E^}u;޸}v9MI.С\ Ef ^x. #J{-EV҅#Lh#vN$[ZdfsRN-]h!OC}ꁛHBx[LѸ=^GYgNHB)5z RYD6&*[rcђ\nW1ߎ3^x*+N}~+``Lgʼw7R% y}#A*>Lie.BjG{MW)hpeAAuwBu@ޤkd樗 O[u>e7X_긘+BL+%~ecЎJӮiB[atg 7JPZGO'/pdA+W(aKnCdf" B1-x349)un~TnxvA$^;f{)g zƿ*:4Hɛ85|^p|!f4w +ua]IIX=TL:Q#?$aL8!p#w/С:AvI&SF}O;aj"QW_"{5l`,+uz$KiXLdQ0o>h4Ox% mk)F6X1kZ !ia7Bw) stUdh|#J"~V9ڔÈg$Pj0q3'xcE[Ƽ &%:Pi0~,?@"y2% S9tn0>7rFɖÊ]ܔ{1 ߑ,$OZZEg R#ZB{B,ʟEk\=cxMef2ͤӦj*}V')Ḍm`wcي]H,C?0K+8U:B?7U>7s/ eS%A"/8ц;e,$Ju?ڐOeΑ^sm6Ca4A9tj"aV^̲B3@_*(v̼~.  7q(Y`8Fp>/r"$ ,Y'4ͼTZ&.ln6q!R.#97r5fdV.ۺۂ@גsϭP 7ByC!5MF,:Xӊ >'L @C]#%Bl}ۘ52Gb 3&FtAl"eƭMp]AKfoXeJs\ 6)/RQbKUάID߄:70'μn4 & {o"f)vu4f⩲8SeU=YoKKښS} 5zEZŮ\nFychߝ#0 rfO݈v۩ 3S, hEmFf qE"I+AYMd"}KCY ==w?X"4b2R|rb; (iaLaN_.Pߔ>H1&zmk)A>[z8},Z%G# -`L  ҆ w6;ݲVN S}ub(31~#%X53B O8ٛQ{`; ?MsD=W&,D '-03!޾)M.:AmTcrvLFWd ͧ{: cHSM3^Fg8JVgyVf/uԵ`p $ &.lK-'78ͅplu"FO#yd/?30x?AT^!/ ~vq u}8oFy99؎}F.(n1uFCs=e|PF{'ϹH8MB88S̚!J]wtlXwY4ɔߎveJo\5dfJZs'<T^jJ`PA0AÆ:~k'y HpbloFߎf(NgDj.,d-j;k~٫j]0aN>'"[F' +j^oX:. =چ=r/;..}s4[@Ջ3^lGqv9DyێTD4ӝh\8SLA "t@7lZUh0kCb&Z{RO8HOOeϰ&/C[%ңɍ57jqKE[OA).F1N&y)ɳ9R.GF!2#NQ4ȿ oH* ȼp5@ȅr`lwOjLUc<>ս79/N8c561SSf\\A .#myܾ[ig4ei~ZsWOI/F,I#>x&L|z>ɿVwc'B#B%@ƧH=\ nRֶߋ`"'(eYR>suM f-WP5hߟE T *ﳠȘХ y#1plcɻ۵MU1LCmqs&b7;S1='%gŸ\ײ̙lrܙ;8»EnDqSeO=; 0Qp܌Zܫ~x6 C!rg=ܱSۘ0l&IӈͮJҧtvHZdФDeJDHtct?Qї0?| PjHURoMyRK$Ye<ӵ(pWJ-$ -+q"|zMDըB^P&n^W Y gzSHjˢt5fTAmMO- X3fWY\:P [G,L2GĶGJu_\:ݫ1ws;C;ClL1w%|-T7´onR p^&א9|JZ&UE%zܒ녉9 ˭nkf<7jW5Rz~k?ޓ/1yUjs۳[a-N{Ű=V ál9[{}=o5dUO<$DߓٳWS7|- IV>Nle~(IPwۻe@;<: к甇9 v/B .̓C9gElاe)blOB rc)\'z-XگgYta42iU`cNVE۷$wգ#}7$E:gՠd.UzfR?nũ%0=ZS-K~*\ rs3"A?iKB@x}Hɾ~@IH%?iO4~OcGæ)ojvTê^ʧ eX•oa'WlC߀ŕ:4bEWD+ F|wU"} ;odHs2 I9:P:8fdR6R6:jxlM7{K5nq6QWxOJe~G{łDD_ !÷h ^W6s",[ҹF͏~&V&x]}L#\2lLs =4qhqEii۳kGf>v!UzlE|\J&h5-i]^ S,ee6!2s_Έc=WR E㪴m[0.YHZvJ$'ʿ%h“ԣRYJ]Q =2pQ 4jw؊Csd%#<3L)r셰59 g\˪@B3{OKTy|ɱޯ_ґIcxd6,ZiBJ1.7&p4wY]w@OU;-{qe).(58ZYTS4+5և J^({4KͶt aI(CC‘YړЛk%J%esJN ]m\ph jBCi/T5-j)5i+/F鶐~$vel@?B4, 5&j:@Ouf]fRx.Rx1Wk юmHgԴE,]_i--35]]v:)+^&t7YUs6Qatv:lAf?ȴq$τ؞ ~DG!&=38~q'yWjAXPAl"!Z(re$( TbA^A?r5yy齛k{PWD|Badܒu2s4B=yemd{>2a뾆 r^| fkjKERo}~IE#N]$CzN$/XӣxQpM)8R$T .J^o\F =P=[޸ያM 6 `V|SMC׳)z=*(Gbt4c? 6,Nt?,jEӼ5%/Ah{sȟ$q{Fc[F3%'a7߶mR% J@O.Vt[J"f(d5 rqٶW/0due. =$.BM1 io3S mNfe":C> 3AH'4o'6]iqn&], ҸWNv 1ERٰ7jx/x[0Ƅ}ɍ+l'NԹ]}&(\〧Y,w9SjبE9 6OThu^#Cųx\2KzRtv߽T6\s:Q.p@$"oXk*ᆤR2߻/"VoLGX^PS 2F4zrA,qUA 9|[Ex> n2"}YơY9kwLe#r _-**[+ivdx](M~ 7Rw L{Ga*z"15!BoaĂ! ެMهd 9vPd2HO R|"/`//>*OvQkԂXIƼM1"\<Be,0/)^7 Єڸ]oGFga<%EB6 7/G$)3QH u)1NV1uyidpM aXP>7//3댫2oCzv3u e#_8d{ԁ%c}Bq 9w/UN!tYqGZH x VY ?yB%SQ[R O@>}156!K^M0ت gX2wiH+R,&uc#OL[ЮX<LXGLZ>Vy?nsg%={Lfrvʭc#JO^h vԡk&ebb4 Ҁݯ;$p4UiWz5UO@xtW=S'Д&+ݸJgBw~ɛHΦiLLO.-g3=%7u+ˆIi.J/xL)`PCʀ<:M.DDyި m}nzP1x34 ?3TK"Ucm~3dZdDN7OaT6X,/C'n=+nz0 r8O3%>7uBI63kpW'ifWqCFuah} -P&(/Ώ:8!/c@LErt n/LiŷrXozHv~9, υ̐Ic+f# L\ɶYbZ4*FCo?H53%SJ]pBāyꗆ]1:mLy=%hNyORX X AI&S& _J `I{ (S=<r $5bg.GR!Sљ*pKq˭u =0޽ ѤLܾt DvĽGLDLf"}Ja^ _£5yc Z8E,ԑ .^3iD;36I3-ȯy wDۖ~H.NAQӰյr{wpVP|"S}/L >'hwoM<ʁSy}E NW*~2?ޚcpζMҧq2!Ajej1k(;[HSnO^̽anndF%6~ R6hFl>,uL7>mm +u dOѶlbdVZ%b4j܌[N*Tעt-52O_Nx `INK0h:/]2#lc$gJ%D#a,a~ҧ[s :c|K@qJjV`Rɀ},?1Mե\4$o7 8En~չY; /E[p.@E_%pm$v}EK_AWmJfwww \ʦ*`uxYs-/8]MޚBJݘ֘(D)m4f ,HFU*`i:3(iJܤx2)ڽgPlt:3%:ط 驾ns*?lGD\{c0U3t6Y(/C:7BS%-G̣:e Fa]%*lrZg$n2vW+GS%IT$By)T1'P+E,xٖ=FsisZ-TǕpoJDmyfg8m#yUv}c?;]<8##6Һo5A F.u-<;CHP:4y28>ՋTH%.oޞG9{> GkNe^;I6l;eNad&%Kַɶ_'$$cD\\NauɳDR3e7˂$ ?[D&+*L?d"~eoMNLK-yړ"ً6xjh\7LUos6h1COFBţсyY_ ~ќlsWEuKfA9v}LJgbOjuYc;>"?d ~_Np.g,k';?JM f/Tt\j_eT+ uF(sUusaM+W("u)9ĕm~:@8f9͖bFّdE&yRceFe nU4__|xrW2WG[YYb5o(GMa^ZR\ػʃӢ(bZDMJ.@(j+|*4}qbŭq98_{H)?a\xŒKUY#Ϫ@}8nmvor'1G@k@`kB[z\pl+}eɮyf@(+򯝂% yigD@lp׶:PQd]tO]¢Mpc |ixF*TC&:89dxu{ '3-tqRP>OM;$ƫlYUVvgrj[d272|u`i L,\W^76I"vy{p^䷽tDZ]ۨU[D vȜE/Dވ A۫O WcgIp%9x`rnfNykBR%U8ʐ" :4+ 1p;xF`XIQ{ER"3}׉bZ@JJmhqS!cv֜#O h?dK,M4'kFXc7WPМm5]~jՙYU*:a ȼH{O548CҋLCRZ.WAiO[ T:wѰB3FݕFZF{ qA;9ݹ8(YvsE=qqm%&-f$GE_ٮ@:c{ar ›"jț*sECsI6b)sHٍ*EDc]lvXn_fxwDTD:a*; ̪>ztv·vkҤks0$- Ož8c͋Ca&ȉ2=xv@Z: GIH}0 4̯_O-u­inFN ' n&~u2  %FtGR:nTщ8޻ɴ9N/J?VG~>`mj$Žf/ Sܡ &۴Vj40¸ř.|p-Dm<:+}{{ibfUgd.} L͗`d"T➎2Q|fL,z] #Mцbжt:ۖ{v,ҽ,Z3lَS`eQ#\K)ݣ6׉UI X@YA5 g ϐ92m3AArPe7McChUˑL]q` %D2 ^. Dfd4 Pg?% x)!gN' j0$h"р-w2^Z[/~l9톪x;i@AxndŴ`9c ~h|"UyORJ<laAܾq|.UL?ozdҷhЂ"nC~~erp%& $P^jӹ ՛6H_})oa1@,I7,+G)w`GؚsOo0B4g4]p0^?a-K'5 ;eǵٝѝq?#c؞ L'xWr-m%v9OJNE?uHTSQG7#KBzt~ 7 p'RpӚh]5}j{fyѯl_d[Ä(6AE;bx˧Q>Y>ݬʵ1 Y5_ٰT25bH«aEK\0꽲Nb-&{Ee&D۽ ?bf@2lZ|䶀]%nW 5]/4kΩ! N <\ɂCxU 7y(,K2H Dth6`S_#AQ=$9bW441pck|w6f߅#(-3TʯKp/Z[]'Z;s%wکlmvrO4*u ߺI1:N|~ĉ q鶹b6D䎜bJވc_!Q%dh,\:/A5uz3? d='טx8eIiXͱZ>h)Z*=ş~ ^*C$lkX)w ULoٲ)`Q\Y`hnz:a{}nASl8[} J9 Cm;S 5^s^ 5wBܻpKͰPM|JL!LniHY%1ob40H)# b&H>9vW_;W(kٲRJ+ktY\犐Щ1Lop91\P0ZhG E<2Kf@'#G@o\ ls]U%j(:ӂnK5gXOcZ5aaGzҢR` *0wElXhV9q"&J?D}at [ *;d|'k^r17hf|`L"Nj̚+e>r{Gpy*ߡ[Z8›tWE >țP]9GvI>%sW[m5ɐQ[\'(LGoуn^lC $*-Դ o{o<QH!9|E OD`/Q )*FU8S(4RžmE)&"+O8əck 9?TNk{Qq42:;ΓX΁>^:^sɍ h mO`O Nhz19pP.p [ږ٦@sfb@#*g}y{ `gHzU-JJq16갅Q:rk#fஒi #v 2#]-~_w,0T2?:'һܮ==*Yat>G ۊw_<)&h&3nkri?7eIJHMa@g68%~fv'C56-adsnې03.4r$e3aRU{➋a%+؋^ȳ`H dazp5"]Q2~'=mMTx" ?kMB-9F N*({K~;`a񟸃W#R U!p I -NǬ|d+mq>& dgj3J-=W6ls}DMP'CDe}.>xDg5e| r:M7%~)enBH=) Tf0 Ż,Wؽ`xqײ8\\1l|'lQF7w],x& C%(E蚏hY!+C*>;V5]b'OuxaP^9%jzT{+1?gjTdvVN`2!Ëp`xnOcZW{n8vmCU&.0Y^<1_;O.14v6[M=@^;],wH&>AwtE= <W<ZJqKV13"ųft<<(~UR =Ii QP 0ϏϴK S+,H {p",V F\8U; 6$&.# :|M5^)eo]t.# CW𚮐ӜM&Z+KSrvڕoFxyUIPz yѢ2i`Bbh8w#[Vߵ 雎!VWbso$~F ?e$^.mV^".YzZ =R~BEhPL9A7)U?9ԍw } }wR$"ҵ K3iľ|f+we&zk@9uAFF 5#N3պY;%QL@>Uv0Ηm$K`RtV dsZEbҴ}g''I`ةqǴ.)qLS#^ܭ:~7j`9a_U‚9vuߠݱht|n+hm<]M&OTciΠ"fmMzF"b8 1/*H xnMVxM)$+RmtG)q.U6],Zۜ½Wph'^e\q%99k@-Ͽ{{cNJBSo_T&QSwA5;ACqm[GXLO0|WX5Xe]vbݾ ~9 [d/:>]9tWYp`+r鬼<&.'Y ɧ B)FT4l4OXΪu^6X?ZCIc;pqoN5'ϸS!(Dz*,s Ŵ+6߶hFo8b$K A{X:UD$:He+]Vʯ~ Ffm=3+,P]+|Rw>h-#~TnI}v~Q-6VE4x ̍wL o`QdK]EmBvz'>a ެ^@/ڴ:FJ6\8#G PJX_yɠhs6zر 0%N!re.r kC\t}Z fj:`XW2J~F-\ 16x`ZOH;, G\Ak&=6B$r{_ār΀#fu-- |8`$KTjaQG>0q-OuF.los5NGX%Dv,s/y(!%(ϭg 6FHD%v<2 xVyp! 56pK ViCj1$7({(pQ͕/Bdpr`{idl/`N-:яRkcSWi?OwrcemCb`? 2$*5+]Œ%\wl3չ<14eB$_??CZar[l Ƀ[#KAwwO>WYmb#eOhS7:Tzޜ*)bj!]ST~`斛ߡVMXAd"H`˙"hr~-7M_m~ V/OUD"鏬:]Y~hnq$;*(yh!CP\~[6xE\+aچDΰ4߼^=>~r_f%-x:uǵ={µ6Uoר^oj7]>=U܌Dno+V6C2a6jndXJBLNLv!3lF_DO.o>s;/{l}H6&f`Mz@CLC*"ʊ7o"eZzqUq'xEy8U"mO]/({Yncӫ`k{mA)r݄AKdٗN.tg/BpѠAuC4zI7>/r=_q=F͜X׬Zz|*;І-+ emjIM7AP̧.x6M E7?6jkep6?Jhc'WS; D$+jOG &s`q#;Nv@ܳ3AM~K8OX.ywu/l( `kičr&)+Թ :*[EFRd 6ʁ3Ɓԝ}ZJI+wid  ԺIT"6_x/Չ^i:¾1JhBS!Ov=,_Wmp">h#?4gZ'״ `3YS!Kl~$+M~I˒ u#5湟rQ uj6oq$T7w@/ GYѨSf; f <#Q}Ĺ20aar9lA]4Lװ-B )OAHdFCzCN ޏ)Cl$%U 6;Y}fLՠM aCfNr=qu<׸YMba /ifcP!P$8%-Ȧ tWyZv=h`A&[MNqRia$㼟c,Kt {X0kGE=BWyX)_B<`앹2!,We@Q;7}KD⦿k:vTN-MɁ0s{aHY_0bouϖvi !*ɂ>NrqC nNy 3Ѓ IlG[ œMҨFFB"@ C@ \>U gZkH"qᅞCx)pP\Sa[̅?Nu)|o9iG>0VqG%wk"sq/@,E9?(nUeθ.9G،s+, -pFv Ҧ?.\d=qSͿ. 8;F mBHgi,c&}%$SqFg"NJԍFW.@1Y䢭1Q+ 3\[S`fݿ̧ )akv‘ExY=*}T@Y'5z:-_Uxl%aT0_ ݰEGbۛX\晥+-}Diˢk>KKO} 3yQYyHZCw5ԅؤ+5=-:|{(8 f,B͚?gqn6)I2ӥNƾ9lx-L _2HR"4eؓ Bk *DoT$8cB,P )㥚r@zy@ޑ+b'Na ҄Z@q!肨~ȃ+ؒ#8W D笥ùe&"Mrd4 c{"5i@P]nf.R}?gc3U189˹ !/rHw O|T9犳oB$ӗDȲJ_ G#o IrKq<4>ጯ1B|˟gG& a,?=*W։+\4.f%TYʎ =}MdH1U4='wްіIƋys QKsE+ Ф ABiP:( H/i5\c\AƀJ΁Nz<1)V rظ:mH䯲_Tzim*gUOOB߱?[wVFrDA0* H&niy/eG; 7r58OluO}Z+eB|'M?/jb M}MGϢړcm$J1v,򹍷|xl [A_"M}XF= tcGՍPgJO6Sf*tC e])[kT$q>Xc:+M\&gЖj#=}a{m3ֲba8V3v;y HK_Ĺ]K;@)怅97ONZ!}.۩^B/e/JC%$mWyե(KD.Sc F5Nie=k*8T .h?y;IJ/A"ba$[| ߸K7v,qjx8X9:S9ETT|l+4 0Y3gս4z:k:FN\ $+wN(k 2JDxgedCDG۽=hev}/^.-9Rkg"A^x3TS*,br2e1s'w&V2[fx=qH !#թ9xU4`쮛sZ|ƙ1YKsCk 'CQ4i6r)2A]dV37lzljIa2uwhϪ?eU=)J("i_ t]ZZdf"6im קok>]^nVJNY ⏍ ρ c>,MJvhB6#߽'o @+YL?-y.CuTc*:h@IS/ H *2PX:wƀmֲLl6CYRak >pQ{$Y-C߳*ǾZÐ[ xMtԣ ڷ􌙰ϯkhJLUw Y,3!-ʧJLAr[O\Y0-dMtP`"vfQS c&*9oxKxljÖ%e[ҥBlg#scKF$-+[7hKIj E Mrѧ` drOBD]&L.kM- 4}eA'TN6{5iW~|++hU&tBQS QP0( m>F`9G&)݀D݀7MXHat?PaAE?K! jlZQQItã$hv:kX jOf̡֫̃DaW.~rbmzx8oqo2J~?ډ]i9'06gHY\,a=3;$~dZ~:@&VP/sYk%= iw"32R: k@i\p"o4ˆWȥY#WLw]/!/ gU%(^z+Kkc;:K% ۦwt S:$4k,u ,|TSf{"ܚj{v xbTº;kT4mP"ē=G1Yl񭻵Fu:Ȕk] *Í;~Uxܾ[ȃ]VOcx"ag#܏W#^ubV'QӽJ"_dhM]O-! lu mZ !O$BY4@@^ʟ yAB۩iGLJ=WL xbqԧmx .0fhժ#L7>fo){;]+# Z, Zp1 # 2I!;Dy w-/\ЏaAR|6$s-{$MhLy|ƔÊ'|S W}~_Mͯ @")Fw:Y<?30P #g-XN$J'}9s}?\A {_> H#!Es;1bZS$8N({8WzF3q(H*5>06 L qmM&_.;/fuv:@ȶ˨N%KxΚpI>GEė.qm@@l{ɶ )etϡqIC~ 91AٙJk_oJO#̡Sᘫ"˟r%͋gSڌ( -Jw]Vl}tʛUL綢$WPr$oJ_ 0e~zJ1Ygx.9;y?B?GC,7$E_") B0uSx8"JwTKLl~B2 1\ pv4c[H?ӯj EIʳbO|grc5pY,hݧ@gzn`%9x'lg*7 2?Sŭ!"b̄4`0: %l=0I8Gm_6%}ohڨ]Cw,ę-A]#zܬGME,%iş]zn؂4§TYpO:Y.7&nc% S `6(pĨm,wŏ:0nx :m֚k>m1=f~50zMj;TxkYN߹6{>a68UwJW_(QPD1hյY2odTz"iGWI}^ m3,(<{G(j.h3&n+6eݦw :V7wr_{Zm;YlO5`~GcڬL'jagPb^W6^pO׻1FL(\M ΡC@ю;˟GeZ%XPNY]PD͑Ja˖kogePk-G )m|F|s>TJi.|#m/Ye+3CT}^$bR=&uAd,m&?xrG/0 4[b-g ձl45|p;a[_v* J Wul=ʣ'(VvX-Vv}PˌSw.1d܇otS[FeMπ2NivX9}t=~+Z$|Q^hMK=ȂѱX$\,/du8mo x9Z`*;n9odLɟ]#P cĩQ%S}j@pZ`H!e%1f|7f28BF* qsjBZ+JaFQz b!J"vzUQ09oT4L[scMڣ_|?}OX0Y֞Q+NJ)wpwt1V eF!dm ^Ln%oKu^$58(-!f;n;]Hq.HԲQE彴;L '·QȢ?_ xVW %|WlSD\;χزT3T9~,xV]LPwu/̅T;ӵyʻ'4+?cbc "ES)Y#9V뢎"!׾xo,}YYB۴n,&j0R']wH\@7TVwp GKOg`0kOVKd?a.7fPQBv(̘-jaIʎ \҂KPx=EgM87RBR;`b#/,˒MJ.e,V5n;(Jְ쁾[zRUEArA88?%]T ȏ˒o(@h^Q}8IWy2t;7v6=Ҳډ`hx~jKn}.:].`|lߡrs12= 8=3J®Lgд.W;t*r6S: Z7g@R hhyajkA r,Z^bLge}-)nj_P3"9JթQnWKNͣBK>TTq4 qn+jO`JAq)cu!IL+- R, C}T6j-{8n'1 I48Gm1+U:=^W [wm^& cRH@"ڌs/^ȁ!MɃ0ĭ`j6}XtߖM1XVw?Nd|XWPJE%A;њO9UR27cLaʑEm\a2R6a) M:¨,y2Fbj(UePUrV)s(Jʎ !MAI+d*;1 {QG^yףLYq=^Sn$A?̩Z*}74{3裉|3+]Ff.Qt飌oAW"CIRmR¿-# d1;bO#%SvH;&"ֱ`CQX ӑuf=ҙDHw;TħFAN9F%+wc)L$X622ؒRDbRT7d;7* Dma^t_vSJ*e [dBkB.L *~oR(W. ~-DKuӸu#W53Bmr,YX?*04ң8vii#K^HASh?m=<[YeUCú)F2Z]cJ}/0*f$m9I߄GyA|zNZ44E(O9e`6"ԝF?(DCkŎkl~hehAGfB9Z^Q!Zh\?d SmP'eaKsD)fMIb0ax?Tbh2C8)Od+-p㆘˻ b!%2Zѷ/pdtqũb G" Y Mv/pO%'pC1>'s'K: jY_|*nM>V\C &QLL @' c ͌ `3\}XgNG Ω(_ qZRgw ɶЫf&L~oJ:̣`o7.j%wfCxp'0f2mP)䤌 *n{r,2 l[L|@mԽ'n)|᳭$X:ML0RԈ{̘X/p2Mnh?׎,^++&dkU1gi/{Z a H3G'vI[ RjarTF>s~9=;+s wk6PKIDn:%pʠUfFF;c["۸)qh0plLPFz#7*asHHP09X'f-@X9ܨѩ)>OlN΁J@.kfS36w,a&G+kyA0쀀ݚƢV+Oæ7}0uUq'=Ğ6+LR͕l{j|TY]ȁ3ָ:gیIKʉΜJIeHЪSt^O^B| 笉b/F10/Wͬb , '6=֦-5(j `D9ɇ.Ev Ȕe*W?xzJ!DDv-#0uLc8Do_IfׇKU;Vp"ƈLn Urd`duc}(ZVFYV\Oxn٣|.-5.`kc1Ωc7]#!{pt(1wx42haW>׸+/~9s (t\ (y YgT'G[#S-lʲ c+7&y&ܤ$P_-aY Rku2Rbaܱy 7~ٔ/+k4G@ 5w`PRghIyCjBߛ<`Uj wkSj2N@[fW|WƆ ̄W ]Bkwҳzzt(q:. #gPtr2H2i=|GKgjym8W`)xi,m ^=ɧoba!M]׽DesW1L^y7^ŭE7PPcl>DLlb,H Sx6Lv%"Wwtb29暽 wWQYr E=IGVLvAA통ӭjMl'޴ݨ&cjLa9+Tgp`'H²dMLtz(tًH rMkw^֬OL-ΛX30ye_j-TƧ=[_5fdePpJd,h&dp =ޔuZ<h:S NaF͕З.Bo( KxU]sn h`# UA{pB!ep4ҔnEA<"Dパw:c ϣ@ yr4{(u -% VH(kyr63p]d| 8qwSDI`qbyt\l (r^b"Y7W9!! x:?$>=)o AZ#﷿AfI_@L3vO-O^@ďv].'3}U\A[h9A+ŗ{!s|eL2!;I`WVƛ֨ѢDBv6}6 N0N{@0K{5d;e "+|&^ޥpnAYz\.OK/mtn rlY< Q}( HvXVc2Ig`'" \t1ߜ1uㆪvhȉ6h?:s`"63;Q?ݱښ#oStxӈ?1Tٖ+#@ju|k|TcGL7Ј1"/R֋OqS&TO>b[I_Mdt3|bYRyMPk//o<$Tj|JִDӳ=;[9ͩDxiM^nZYy)6a$QZE܀, ' 9& 3ódl0g+%Ks(~+n;φJ5/fx*$es p|+RHG}~"'Z`چT H 'SLr,8*[]WfpqtH#>KdP1CLds9u+JMy񖒳h$ {:[ EKXV|fnfZb_o!M\tqhg4Ϊ,qq+'9lrˀWUQ柑ە1#,AJךpX ;̂*UܥvWtaT)iG\Ip,w#JU,Hv^Q VZ>:"?|P5$qE nje![N]3cQCFGC$_6x%5({_` uSQPA!lBoN1٦^0{{{5:˙,cQbN &xp!M"Y dh}(N羟I~&XQFq0A"Hz^&C~+-W?MJ>;<" wiXmOq2ଉW%2PDW>58vzkEk7?*H)<./tމЛT,*ex924n#vts)fN뉽?4F4RRX嗳+tS6aE-\`=YrAzf}ZDX4d8QswaDPrT)K /PA*  OAm1"0L;;x\ eiE2~!z$6=FȦʃ_h TǭeI/:T>Dlel2֨qnHf 2-/!p TA Ԑ&8?ߊО(ϑ^YOUA`ƒ~F Ph2J]3]?-Pz2FrhG)) aYhA4y %@ @):`f\ކ0%;S _K]f(0Pw_P ؎VA!CeI06G5DvVrN1T5tT@ hY ++HϘTh1يɉ% 'U : H/ 3`#~hx0!8]\DK <$GG0̩o=UnB"zM]ڃ6Br64ÇCX,r-j{&f4VI7U` F1KQݗfAlA0n zĘ>U TW֑&2[!9pѣ\._Zsg*)$f5dvq߸V$ Ҍo$ex g(]ySTvYi_BO^ ĽG|>@-d`~OI4X|o !da$r{- ƄpwߝY_ВdtlJ{*#>5m eQ@iQ $PPicRȠÂ/Hv}"m g ;4y{tBs!4˪G.XP |S` cEm4/ѯ$;X]pK`"rO+@*P#|XdA(ՌUB5i۹rdx\(ި.P &KBl0p뇏Y4ThijpRb4J+`DÁo"ʍ'H<, m/b$b_n[ h]Y !WfVaKV@f'1XE$+9Z/3Z ӴSi+b)1-g"2dkfze.% ؉#ԧRxBp'f<\ f5q,!wO%E-/,97Q$ON]$+I}'MH\bz9VĒ^!Sx,a$>8z`.I[p;qR'e6^F £S)%Z>-st&7C SVg = ]Bzcƙ%b4aRSB}rʶ#<<*h:~ E<,()8AB8ۑ(b$#w71%g &ABnnuOus *y0%WКRE;ۗJ'brF7rP=B ~bi!ox/Y*q[VÊ@#Bఢ}tHB̬L5} Ng|溂k't>*+ۉw 4H^T| *1KC\17_a|%쿖?ܙ]9jQI |0bH|VŒz6fi}XvRr`SKa. Wh>{AA;$ۇԨ*__MD84ȹ޷(Rbc)VhqwaO2{˧%3;Ay&>sVg ̙ˆMi& 9Z0lNFv(FZ2タ'2"<8Ey[_л:~"ڃwHj٣N)gzԈf:]Ωmc;M4L`aN,]~কKEG0#k^<wv0`dl]P؃iuEf૨B좕-ԃ|q?Vҝr=>*gjn^弽Ew߾# 'HFŅsz5Ɨe^r*u%K dY\3\#9ot]y{zT֘\}D^1!z0jEZBfT5zimi*RGEa⡴~"Q+:OǷcùZ ncN?w!Bpg]FU5xJ8GyA숁c`Q\Uz҇)9a-oIƒfP+)3Wc?3}cRٽSFș1d$r>⃦jDh"5-=l`wMI0GC,%K< 1Ցk6D6swGDM|I_J:ND!hE}F6y]/(ojkLj̊p$&1ݰ\MR@ ɻ_<̸GcI^pW}bԤg`6˒2,|FAP%QJ&qۓdæ0W_~+I ֵ@~n?z$8ܻhO.h>>#x.k8jBtEIgXEK(-/3.IxnmdUbv$0kp5ˋ8@(T',"}Z)س BbvBp6\W4ު޸fΙXViF7#K`!-DAOX[l?,i t|-yH˽ȩ蛶/He762CO%^ 8(gOcI=qA$3Rt$hgAdk?DIISļ;2ۑdtV:b+2IALd cE1}qt C)7 [)!KCR~?IךTySO:ה1*poɷxQBԤX@= jdT,/ +۞!#3잘ԓ<:nK}?t? űZ3AoAql˭u,<M "GCG_A0^ihE375jZCUrᷔBL,cu ]lZ Ѷ -n}T:;gVAF~8g)XԃY[*S m_gmo|ן9_Rס`p+Fyp)q/VSwĮr1f|+[e w7w:ep| IM0. '4 v/tǫv AcI&S8@QL(qn9?a؃NkH>M&Zhj2_n'bS]ıV4 _kG(?' ֙n?OL_wyZNN,4Z.l8 ɛӶ@|>K!efw3߆OǮzHTޣF eb ݮr|'*8(C#`)rvB_e=540 :33,z Xz4ipb[H¿U `~ q! dl ;")mz34QgvQ* o$gu;ZՔxJ/ 0 YC+2_υ>h_;bct |BIN[o L+@_AT\Y&sm|(M'.=aňGdht~:9IݠA@|13M?6Q]z!wӡc"SlGlT;vpR&nZ +7NeD)~ `_ai`d$]ֆLXKPΜkˉ6.0F *jv7ۡ4`{+b(eN~_r},M{P\()16G(}\ E *'D>BWTFWBu.yb [ϙSAm !w#p;QdrZQDk`VBO7_2e<(&Gߣ7^Pt*[zBv^^J=>KHɖy^E `DM'>$ ◻woX@LubY9sO8]IHY_M̚2.}w$_ΞlmYOi}K^؊sXL$V5?t0;-+-8 HMoP{>|2b7Kjtq~MqX`WOa@MeqC)SϚ4z׼Ͷ$D*Z+';F58؉Qd'?).`OX?3h6x4{}9kTӍg GL5up ^'~ H|ـLj.eLš_d!5HeJ=+Sߤ+:5ŃtKxkkEPKKR@";1me`t=y. [mVQiT^ :6m؞;XcaTRW |:ɦxM$:0oN2A gFb>U%j@ .^g[4 m푚wG.Etb+OmݢBUrClYPJ.\2AA\<0p]LK8T $s@8D)eB.ɵ`uEbO|VCW|oe,Od)Φ4htG: |`V_R bJ૞6;[[A|?,.]]"M/cf`qT4Nf6_7]YwJo8A>s>IQv{j1ر7'97#Q|ϦtksVJoI[.r5i HGY@; K]ɩ:y9IkZ];MV3 N&z版>WiCdAp|cpt[sT&θjMNߟu,nH^,}{g+! 6f s!Ώ1FýrV~?۪$ hM[:!v6N6vJVvcUsLG1U pQG HOy3ҷ[<7|U$t59p hu ^xg&Pm*iOtmmAni c@uv&25a>7J0O?AyWx2I&Xt$dLn< z|aGCts7 Ia)8dD=hq_M_`gtg!Y 5K m˫*]lk0h9V9~IF?F@T=S2h_gjN|o&/ VWFJ^^<^IMhh2) H}j^&E "Jf|İŘX:٤Fozt }:!`gip0f(XsC[\vÄ${ݫs~1csSpiir" k8jD~qX9˾Eq jϔ%$f}x}0 pJ59Aޠ$2RL5OjOإ؁YX.9.~Pk6ρՐL!dIxh t1b8ge⃓h;sf2BVcnXr2ַ"L-*^i V۪C#3޺6E?3.Uט'g=ifMns&m+q>l33E)h$ Np|fy4{~m4>2#7ctQ(OgQ;{~P֬4 -Ti#CO[ne "@&)Dzkh1gjhB [y,42eE=Ɵ_?ZWPNI0ǀWԱ4Er\E'@Nbq`SIl05L}WD1_D7(v\9 j[Tvh\,ٸ&iEGWF|mSߥU0vŤ{`%9BnKzNZI bQ151TEb3lJ 4x]xruAfJ2D .,5Z|.x|DenQԮJZ撂d-Pk'"J}kWs8Gϣ{,LUV7+ΞZ,s ¤gli= )W}P,J8sK`=@P&Yu-*Oh.q%TytƩ |ʹ8S֔%Ǻ*(h'>k0{7**濛*BXh#ci?oqcߚvhlSbPAż&|zCA04&g©ġWuI9?q2EN~P@~WkvgqLiƙh2 Jg n{1QT ۑ L?eiV&/dS !cy?))-LAI&Sni=vh~e4XE} 31?1$ZUWbf9f駄ao_ F+;|&u`ԬN{d?*z4Aݲ0 *`[7z9}JX]MPH_4W] $R $k0n"^Jpfp1 Јz?4i_6D.򔘺3t` #PG.g\}ڒ*egJM:\֟sui'o+[L MlQcyl(;5PRdC陼SxU'SNܗ +9%H%|DLʮ9wYlnXAopxa:sTΗ6G OcLHwXoL^25WG}!44u–;a VwW^OD"#QZܵNZjZaMm LPu!Hƍ,`2;lo4bGjifIv#Ri龧0lxZCHM-%= L%pH8:V 'o WxiKCzWcaus慧.)P9gٰJ, $5Ƚ3̨R&T!{Krj24#񿙍[MR^xک`0ײs%1ƪ3$O0b(H3oLXL^"1 rA}YQSDmNu 8GqB|XoKIjXݛ'/c4V R]z7:ϗNGab.)[3:Z=܇ozx!練$\0X<#]BK ,E2ϴԬПqv\NW !RJƛ!$33cV%oT&P3KP2#迧 F7XAԤb}h2.Y_Krzxf }h3%{R9C8{'eV=f>jj]fs slƃ,׋ Y۽E<-)g;x„Pat"UbaPu|h(u'cMN(H= VA^Kj;~]'L$*"&xC k" sMċC.dZ27}I[W/!I-Εn!ȯ Ry\{X8 D~%@Mp!&_sS|넡QFBEӑhߞ43 mnpۇ`ݛvhJԺUpДnZRƳ\~R0 p^gz B P GXl~g0)j.59q" ]/8J\<_.wJ.R Fg5)ϗkcG Vg,:`(О_C&_Aq$lqEUapN{=D9Z?#AI&S2|'IS"ޕ-3]ϫ ֕@"(s1`N AGٖ8~K-Ϸ Wn0Zl Zj]GyOpmmJd7q>U^ɖe(6WKv1c4w%tQ2MqSWV9ǘY;T 1JrO0$i}`Q v@}Qx*Os-|=S>d$b裊7`G\u[Gfo>T*܋%G~IZwoȋ) 5~|lϡ~G7V":ݞ-ƒZM1k:A$JxWeCsL0E BvHܜopCDݮ)6fSITŏ>L\IHi$2ޘr#V> `NɖI_ى)?RQLelZSqy Zf迻.lfxYS# -R 41^H:T}]ڪM_}줊=!gƑ$G1& %o$C{0(l(@-N ppAc#Dz)d7MۯdW]*IPF|}w-mMDtv}{MD(4/_SԺ E%ܒz?O ib#(E"F!:AEb0 gȥ:"TatI무z_"J789fcTc$2 +Ԇj%>ZR0|vGo٫q43fICL,l:HdzxWELjM1w`}ɁSm/bU]")^|!$vJ֎zȘS8"S>g؊>eSH":$%MTb r=('tߐ⽡S R'dc& C| ,$TOY- IYbc7'j9w)fDRd)I6A*HCv8aSb5tP =R7$6@Eh? Fχ"OqfdKer:ܖ~ɹ/L "R973K}.Q9s꟢OHzX= &@2T$}R%zkme >U6O5OA!ѠOG=8*Le{]#l^Z̘e-~*fk4EȪ*$W9otכmyK6޶. D+m L[~f #\Tw%زjM3*>f~lw(bM 3^v:5xc߬PtQx3C<b<&0Pir9g, P,:3+ĝ̍8bgXSt:(4X{~yzpeA6~;_+zX^m/*jL g|lk/UƋH-Iͽ1\P+8b(oDEybRL[c`$ܟ)M+3oQ0I(8] Kki}&֑ҳGPE;*p<`Th#n YQd]S {, *u{x%&?! VȏFS5Ih]&ε.kG)\ 9ocH•.&cQ/\.߀ܪ[1K hACw?:+0;?;CR*ŷ_0*NDPpT^).? *7>_j5I5m'ec(CrH `(tǯOɎ (B }WʕšMx_bp5AQ音qmmYܼ.{tI-V7EK Zo/=QOp3äU-B8SGL+70J)x0Ĺt?g4GiIXC^-;N>:ީM'US7rA3:_LC`zBj-d;vܨp ҇%e7y]NHJ_f #4~S}|jBD w|Mnͥt1E^f hO{w!A}Pfv`AbA u*mbqG7;t_Pg7sI ͫ.`9vB`3C*3H-Iz$|WG$y:Ɣ*H{ɵStjHR`}88;p/k=El!ZsgC.޼u@W b4F%|n5uYTOn'R`58#J!38[8mF00K<9/B< MΎ[caD@<c ȶRwq@ȖF:V%׾ Yc׼C'Qt5iW]y)ݠtŦ#pcwe;鏡Ia4εܜO <{X ݸ9IptɘﳼS`#jq{5KL>norڨb\qvۭԇÍEӋ}_So{`?&w@5FhK@/27f +\rhv0mdY@Ͷ,XCt8-MVb'W4c[f!(F a0X%$EP9aak5x mfv@& +ho]%p'HfXDJgLTeAI&S<+8@G,klZGV}"YL(=k{Þl цp1 ]JpH% hՙWR6;_Ӧ⿛?;N,[p`p $0=4`vSڞ{n 7b( lZ|Ru+ߕѺG1<з$CI9LXQ"lmU zdEMYR7~()>c탐#D0=#e&lJ! K ݠ(}@YWjYoD=EVK V6NWo#0|ɼ*8a&z⣒Hkeew_/}$"c\ ھL3T&ӎ'[ҼC;upM;kXsSjpbzl/ fV[`u)~'|`,Us$Ӝo[)Hhb;VΗ6dӶST7Z~@[IEwymˬѯn Z_adjt/v>^9R=^p]!'}&?wl6Ϸ&o$!%9aں=~“*!8'0ia\w0[u)ęXf|a"!S7˸: 'Z-9x@zL!DJ^JU3]ޒY9,`iagF[5V)׊E7׈f٬sA~fa\(^U;<A1O b9Nu%[s)|I@UjP15GiIHHj|/ FYj,gWU鄄&ݗ?O)c5r[\N%vPjm [hsv_ߒe }a7ŽAױD"tp$Q{% Xkn7Y0*llnZrM2hxY s_h#շB%4`ca݀ zaAw(I E`U|4Η1b*:wAőȆ+xo #̾Ίӷv^ixlL+KzGq0lhjؙ[pfJUeܣnJ#L1v`pa1(;к0AJr:_S&'MSux *NLNͰ쯯U0>ȶS=WamhBk Ǯ!3F[@rN(p%Je ӹc:lPMG5K2=$ͮC';E&9F{?5<5@t-eM"*ni@$̈U$w\IMUB&KOv p?Aҹ>wن~>E$cwT lkY =#ĈL%cn˾b*H6&-/u&>6v$iЙE*.$cNam/Q8gTS<?mNr aenM`@_ @f-c2-[ulL`r9VxS? G:1 YEf?8P{u~bK NyRTGuAż>PQÍp֒9$0C*cum`4]2{)^6zt!ųqQ 1!wݗ@QwbE$ M4BKnt ̊z%*Xt=M$A;d+Kuj 6@ 6- z0tm8[p~[6U?sxg{֜Дq9I{zzxP YCyyGL((M(u4ah^F/^u<+SLk3+b@YHnW֊.Enpi3Eզ&n9s U; ob[yGxBPe !ZNʶBuNvƈ1sZK5%×CmwaDܧRuׯ?9VlF輙.G!8i_OS=3(o1ZSH|_ieF@Ht1m4{"4-o"ח ܤh^O$ o5sw(L#vi1hXN:t8[$D?M5d'«k7I^|I('w1gݿTgu! G |D OHdq8,D>?Hf&TKa],|[WkΓ"z^ret:(lmn2|ad9p4cו}02RY#1Sʙ(pfޚ?W8٘ўqӂEVaDCHuOQPQ9̌19g޼AeuN J ǷG5XrXԭAv]r rL0/`̇gߒ%r@ֵu1oMy/$1@V(L cb~7kYu-Sѝ6v{ؠW#^ڥ't* E)mb_׎T.O K @M( A^Y͘#y>yDǞb>qE+K}ΌlGqZ^Gv44vÜz jBQˬ1o9䚉-rxcy6+ ^ѠA<35vrKYl߮lFz8a6oAf*2BaJZxvm{|y@tdj_cn[kq['2CbqoW=sDrqF|D_ a $L4 !h7CXfp%ک,7\,I=b"h}buDH0-c/\W(sv3ĻrS>HtRpyVV kd&x"J  <H'ŵsK.ծQť,vAz"wO!,ds)Wp5&u>weY++?Ui*%Njڪ&A0˧S!-{ZahL[SgHn'9mK.yFD;RwQ3冺̞2NQ$msy ݟI+X>Nz;;"׽Ҍ9Q=[#U|l3obYm.?BK̍n%JO#K_y/"6N4! )]|8 T9b˽Hll&cºz`U8ÊdV⸝2k)Eǥ &҅k^}[~dM0zqlZqXN1gtoLb8mFӯђ$ŻYQJ%LkoE4!zLqU[)t㚛dGH3m),#͹@\Ca{bT\͋5~ut&E㛭Kuv@{LfrY2VySR3iS.&~C2 OcSwX^&&Db;I e}3u z31]{X@ϑFKD_N~V"81#ݩ>Pt=!Z*0Puʥ F#\f*JdcϘ2YW/ H8Fo|۳U */㫓^>U=Q(Cmڥ?1$h'k-hE%$N,Ժ3R=+lӕ/}N7*8N,H ;vm{m$ t,F 2hi>R\+/aїjuΥ5kSZ(!~!0z݆U%;["a>`,u(G)jDBm(y[~zBgc8tI[h\RWe57L gr,$TF21I.;l+翂NîTy0Hxhw8vhn\k_j.^EMhʹF{ͼ=ϡW^QJ*OXEbmL˯AS~vļԄR<0ĞT]l}6AgLyF}K"0b-]+OY(4/ 'zvńxшnܵ <%BRWuKct_-5p`y(\YgQ-7AYvWur?wd =M|%)a= dZ/gaD8 z$Y>3Y,xK IX)@ʈ[υJKBtf+7CUYs SHA|N&,x xNW:nrLP z%x ?.`pY]3X˾:(_jV>l{݉\ZA:w<.|rVQ|G࿠P)Z0 nf~DŠ VռG\sչ|Fc~cib࿔Y܈= {Y&u Vؓ*QY2{ /c?]U}bgGp ;7[6 4S5;]RӹT?mQ OI`-3$ 2duqnRN1ws!2\1k51 &ȱg$/:Ioj?s'67yyؐns[,jeaǏDZ,e3ˈN1"};KצּV3+t;ג]DV XgKj ɥ̏J9aw~/@-e췺*S ʜ^?]h #YˆQqZK:su t\bPbv,[9tvhQ:@0 N=ujS9s^(ϲ+p"&p8Ǥ͗UIDa[-vS5;X"6Ib|/_].qZ'j;Wf b"\mGX1"_C2BPXKV{0 8~hdhÑ=rݓE7c7ipxY džN $F7g@s갃ϛz%d^!tM\E^T9d(eߨL3r !":SL PBGU}F9SV/U0,&#pyCߘF^8"Y;-- $nER Ju`amae k顾GҨ1{~#O^s(b9[ܺpPO^s4?+s@q$ͻ-FSq޿=Ҳ 3FF[IVLKؔ4چ7 btȊybTX4@c1A>e>jz\zZe]sϥ: =H T=;K O׊ :]ɟv77iK _ϴ$Hxj,Ew|uY)ʉ ܇ؚwA7>aSY )|C}EFni^KhpBU-D-!Ra󡂇, "@U'}͍4v}5 )#\[@}F$s L}dcerBigRW~ݶ# sltp( of j/t4!iYsc( u2S' n[ҨT#q0Ȃ23 ۵eqfiTWa]>^C q| (x~׸ZP)Di '2t$<*;L"=ZYL+5;:Ėj8^OA{-MWg%۷?Fa Q.dwLJ} ?\ yR<> (0ƧSٞ@Ǹ{/^h]YO"R^jF"EJ9L`>Gf)e | .c\?hps0K (6ySvAGQ/؅|Eab ި ᄌA#{B%]`v+26OK_1k~>S){7L:`: -1P})¼c;}zxk1θ+{]{6FWp[;%Ow;hYݻqLX=zR+:y$֚ A iČҵe;aژi&V=5/cl"( :>FC!ڨk+.6F\U>04ݼOeCIihHZ *5u2G(rU8Kk&E@3ʮn !@L5*\fvkto -RH:sYt:2-{}*tM(FQ Zz-̳wvH Q5(WVp6gLjΛNc6l[^a_ •-#հLXF;EiwU4LأیB?`$6n"|?r0bm{ꖳ'β5-~4}eju¡V`Y&u%_o gfa[־lծll5 t 1_Kx,gs{W溝}qS[8;6`Uqd@$aUAR]U+} $aX''tI*ȬpbFDNG?| @o@--vəJsqxn' Fm L/j4 jPOE=$6nxZ'2۱TeaTOI,(,-%,q6<hh601 6u9p^~!!Q_F:u"wg{JFh aAjaȪY\Jj7Eaa<qF6g!h_.t KVNHdS6SÚ; ؠxqnP&ו)R1g 쟃}V3^4l6"oh+0b yUM,5oZqm ĽQt_5i6 w-Mz}yW_8RNMu/*d@J5=P"hS݂bVD( E$O6(~ N(# \4Puxµơ-^7Szu OT:D(c%4oC{,.#/5b*^F.LJk {'7}ǺӕM$zq׏V4H{*vm)BZKF,i|%;3{ 9*_g0($SH XMBČ 0O67Ywd?2ZEjs>OŲH~<^>C|z#%7o|u&oɂpPp\ GL/j~67wy% %fg+2vX,Ikz<:qf: "=zf,_ *Ve'9Tm$ULpS{_=,+Xjuz4bC5¨`V[Ωe3 'CƮkVpXYc\w;ަt[>.xwg!yb0WJ"$JOr(Ԃ'c2{uqdTb['Y8ؒ0fKܤQP^c@CژvPڡov? # tyB 4hXZ6\%xV()b1>U/q#ze]@$%L]H?;D![S!S~NY[u{>xRudHQ} G\tEA4gA6~;T7>Ք(ΠL)bB!ü5Qcܾhϐ~obMD*_gCKӊ 8lL<#I> w^_ G*$=j\::D#3 Լ[|i%1|1x I{85^2hvC-?V:LQSy'1ãwb꼨̓e['daCl:㱈[>ɠMrT jMMvlecV숑d)=;ٍ!F2U{#XDoN6ljEsؐ%c-)!nF `'f/ ^"xjzB.0/ ڶI=6RB.+&q\;: /-Nîa"qym腄\p_;5KtbryJUC@(ͫVU*{ ɈkFkt2L-}KzujNS%sC5 fA*I&Sni=L-aA⼉w á~uүrռaGQJDA4BՠQ縞9>0Ǖ A̰Ju~ F񹿦"$ z`aX'e^%j#\,@F[|SjpDo6Ϟ~TO:edѹaRvz|tz7uc{'{Ko+aB%;VU]mҵK rHՎ@%k 6 ǥ^5^&Y.*xy6W=G9v7Q m"ćNZ?Ps۵[5$=qXdZ*(w'׼<xK<UvG]s!pf#Q҂/(U>D9E鳆ju58Q-_ĖINi3\d̨'vÓ`]ʣ6 [0pwAdE=HaL?lǃT^& LyZA _Oxe"f(]TejAk.c1Hy7x)0ܠ5}LNf!yҐSj$# 77LF9/M큆MF^fZnʠ^tVlj4| #2-.F3}8c;Y 'fQ^DP}Xهבm^?+DsY<+I g+ȇ#|1U"sdpI@:KRKYIOr5MDql8iͥ?(bۙFT< Н7˦34]PTᦄ>9āI>""#\Z'c8"v-s;s"&gWd^X@keEO|2!Jљ9nJLLk#$+2^ ^$2MfUvSQFȥ`'ܔ׈`9.^u(tGe舟+aAV PjQ?9'z綹%pKhB Erד>MrlU-GAd رm`hIsvA#]/ AաEbҭ gN<ԢxLp#Z!U҄B;jaVW}a_Θj"(1w&HqʧؑAKX!&?Gm';pXo/&=.`?#.l'+-0C8olAa0?lf93[C%9֫xCkv9OܲHDNUkD-j$^+9q4ΨfG=̂WեvHtPife\o1ѫI#J#cLś*mm#NwbPda{M˛Ǚb6}+  Mu+v?uWjEP N۪h_>#rxb_DF `a;v͏Tm./_ʁ_9tF_Ρ )sB/5`/R3тFZ:LiW~k?2jTA*`&ؕ,|۠5Shq`ޱw@Rmr=wVDXs&ӥ;Va(l"r)ɢogePҾ̱]M%uX׬oPИ?4?y(^]~?wYj/pho!Bߵ&uD eY5Pl}+gz&hk@%;t*:)183M{4R4)tC]*2gXx8HbvfLvh9{Icq@5;u,_m7lI_W(ұܛK-zqs+sv?9gٽ!Q}6(O@,Yexak {Zf\iG.qxKJn{ъk>.).Q|x -+7Jۭ.QGƘӶѸʻ"Hh#ИH]0d9HJKI/K 1X;hRѾ N]P_%QSw(-."GT?1,yyR:u!F5]ksqiBW#M ~R¨)H%^\g}?֝EU梟dܓ .nfyqohX1 W!o{W06 xZ.n(>cQ^3Ԑye0ZNB@b!D|.DOcCHE'oW7^qUw͍m4N :>`qნ*m!F3l|^sUKmTf2ȌnnfSS]s6I}Ъ!\"2fb/G0œ'GΦUrho[7A7nfdd,6i *dE: . ͍w,﯍tTd0TYPm5Y'0SnmMr12~eݬ-ޮ` ?cc?E92ܗσH{"#\4랩]½۸hfz!-d"- _F Psf_TqX)UL&cPj@RZB/6Uu"ĶjV;I$Y?&wo(2eJXПq<ģu!ӼLwi_T&*ϕ">+ȮQBc%e=*\ +Xxpvs' 7>%j*colG E 1 c~Glj*79_1aQ?fV_9,[YJsFo&e!] W)EZ(FcgV<9}Ӿ-[Pnbz|ݮr[ `h fb}:  Yo%s;han Q0b-ݬSHWbƜ.'-[2b~}_L|ڳ0wjpLg~d&}D٢SĜ$P kI]~ViW8Au~uVY`0]}w^;07&Co& "rDo NM܈=˲- b-x8,{Aܟ`-`[GDmoG =n+^ ݆{yUwG=&R#`C:@ ҆j+9TAE i5ȿ=Yg [FOj$$dFmϽ0F$#ߞܽMf`j^nZGc ()¿&|Gjh:V7M,IN^N^)p* ;J{<2][ ӎӑSxN1skmthl̙syQFq}tI{nw¹m[{"9^}:=LS>Q] .IM#iC,VZ.>ę4`#dV-~ Rh|lj'EA'KGmI+ðb'F+@?DKp6E+OlJЅi`,S}Ԣx\[|7vaJΛ "vB7 XYwcCvD..T՝*oTmpTg| QQ_za0ހ S{ޡ]{hŏ_w?['&mj>OspzD8&4'q7~fH~Pi>cK]Yl#s3uaL)Q'g73)OC"^"b]ѤcYw*G:мhnX@[㟄,ͽCmT8ݯB*J_Ȝ|YW ﻬM2O: )#(\QmG)2dt`R 8ȷ6ܦjOli׹n8|!65C"z^TM/ub [ޛJ+s#Mִdq\[iXf=oRi|+p*1?rs9߿i\ !Jn⿕;7@Ǡ]鉘f+'7WQ2nk;IWk6 {".Pe˶ڈ >*ŮK"iYẑ.\e5ߕ#m'{G68p F'5WgTat}H\0mO8龘m2ġtTI-"{5vwL<볍= yJ NYk_F*TpH[K83d$Z%O1tgDHl˘]jq aDwo#D;SEXt %6|WLҞ+; #q),^]Ay@ Պ=$x/t0P\f%q y|W#Dt)zG|$w:GJbPĴ=EW 1b ݜ:9”OgA[R7W<<=gtgm]f;*[QNva= *j}^}#8oaV{P[faD VFh?c8effo0Ԅ%2y6Lŭfe3M w`9_.AKI&SwhF&ZX͡`W Vt#"ZHmhLPE;Wv?kK4\uG#$p v$SC=Z4H=Gr S1*/yr\_2,+ԞLD*5b;dJ;xI,~.GSdGr)4[8doڎmdcZx=/ [>~CT=;c:ێQȑ`_|H8Tswgnz~JZ|n$N K4UYMdުW<g#Lj|qh#A#hn_0xtKͦ,g:>zrJ߿nj # 4mDuӻ]t+ 'nv,|kH_B`tJ7& sf kEP(%|N1ٳ:<ggc\@Y }) j#W-xD̮ 1edN5(kc[PN Vx9v u?O61sN-h1BѳJ4i_s^ Sҥ>ᢷQ0.u`]%+GfPژZ0\#SqI1IpRNQ"r%nbz)mPԪqBKi{K!ZUT/qɭ?EF!=.ȱ{ Z J'gaYR.2IJ4L}D>1,YEKA}r(Nmշq,=47w~_tczx&]f*hV>4zg0USvA |Y>7Ero.4JeҲQ(}nz}F|kWpSz{CpeG {(gEބ Z[clApvG-*ҿwsNm\/κR||6L~Vkr{Ge^ijJ,\$}11bk<f~t?J:6(u.)Pv_`9۰AFܨrN٤Eڷ7\te>2tO|ГFr9f.0ke}*EW~GCj jG ze|ѹ,V5Y ' AlI&S*oAJ %2!3QomԌR 畲.k~12?,ssאS=CȖz}άE9.sX38 `2jK@$[lzͯH*&Ug3l@m$[{5XUS.ikV>b8.SQvchghkؖ(ɰdf['yڅ[Hˤ&:wE.0B _5Ƅ~븱qj>{ݗBP8=hyM!mH g=PLnWnN["CSB|G~>3Bxc/LWXrZP ͊+s?G.Ѻ`CKA-VDSLlJo>)v|M,k14с TTa Oz|DuNX.rd4,8 TI:}ԧі@fuk$_+ѺL-{%S]+daq&";7CSUaZi ,,.06l`eM[z0i,oN!~6Nap9H1=wiL+_E'rX1TmpiͰVWs2has񶫚*N!ؐkyAe!P!+_Zn Źs c/':+V`E"ޔrj(ە(zɛ/BbArGm1YK  9}vl LbTG:nyyKK_f]z2 MjƧ eH3R* .Sk.nX L>o^}:-$r FS_E:M*l%38Фnrz$N)!HPr ZiU{.f1g/P/N`AT>;:pdm,ј%=502D° ,!zfyO#7O+}uXݔ_7Cv |hNP-)u]߽/3n h|hnnj9Pu>yǫDC}٢+9i=?n{7BG4 '7Lt*e-QݗW\9L'P=(S_^ n:^C$CA'fS.-RϒZQQ-څL+1Yq:01؟il4%7T ę>qP7ސl³L\DOFX*6 g +ǠJ0,\H"w~ M|u'3} {]^r!YݡlQ@Xrݶay5{]sG?ܤSP eq33l]4dk<5&P!/f.q^mJ4)w},gA2@v&rfފjz$7sSպjI;x̋ Ie2Ffj C^ $3ė+ʈoiDxz@ې&ȂqsF~6Ca.RrEC#eXh)߼`$HCq (gyN-"sm6"Ѳ"ȢdmgEnxWHp3ߊ8ibS-FHOH w?S{p Ecg rJQ.;gjqOv2nT5xt8#ϣ8\cЍ!Iiư{_ 0Ƣz߽PpЖ{yu}C9 x<ɪhdF+rcBljzg4JćbMB w['KOڐ{IA]%wظLGISxH9$ Tp$CɁN>i/F0@cNHu)xɕKdh9 ~p/QK~֕twLlR8{ }>6K8 ]JB#MLgjxFk{"PV=Xd3yGnHK|Ψy1hZk3K~Sa\јn M vo?, 㤫sV#"GzQ'R[ffOՀ-z 954k:bfIk IeDywx6Pn#7=2g9:A}rkVCoՆro\#Lf}aʜz5FctO ݤn_H7X"'-kEs]͚", y`Wλ зnbw7E]j=n;LXD! (Y `08Db`׸UF^ȁb\-ؠE#\t[lVĿANM- UJxへlUWyq{dW]'&T}?}h" ׮"g5/7Qo fm#|QtdćP.(2C09l;n=Yp)ee4 {dمw c΂%X=BǛ5 Z> \לp'߇W9oL-*!PbpЅIj~.m;1J40 k8v5r )Uƹl_nX.[Ihs};:܏Zm2}>vP|ˮd#4=0m v-Q>s8y£ڸA(_ @@U(iVϲӭIJ;SXv ' ~ײ)XAPf<,I Y`Jm'M"Q E՛n||Q"e;Qz:H?ӝМ#j"!?*/u sxwFqm} VB2wk8pt{Xuѱ 0H2=:d0PGW@ AI&S;Oa ofNWX."SFqZ>b |ɣ:Q, G՚r )FSXď3.[8x:?CSU|pw\ 2oi[=_=[0Bc'{S[1tflRZܵڿ$Yelx4AƆI;*=Q)/Zop+ݲdcJ%asreG* [+]p*#kN/Rs2SʶPfvuE#{t<(J'oArKZ3Q3f7PY>-_E0lЕ4"yy31v)NtVV8xo2c:E?mm.q KbѿH5Ϗ#Bm"TY׋ta\DY25w`իvBBQg-gwtx-+\0EX*{g}a[ۣFFUMrjMԇA\#^6yP4|>J R K>rڒr^lA+k9niz5Ù8m"Ż.?[&XRu) gmC%Bm?R˳MN9#gO`U@qz]ꢠmӊ)!cKZٲ>h#Mc%ge+$"Hm: NMt`;K06}m_,:<ݾa %8,HJcj#Yڛ ,ι/, u ^ER ihG8as) J".o?.e. 3mȏ~/kɥBqZOX`},S.9Vsx$D8_@Czr/Us+V()'0" nb6=w][]aV#P] ;׮ՠ4Gp<ª ݿT"Tr+%Bx_W2Ww0s=Fm6le?ў,ו&A{ ^ZH"{{L)6u'W ܝ7u#Ě=D(fM%,叧Don| {^ >A3C=Y+MI'~k&א -t+ ^P Y&KdJ\dw$pVnTLGce 5:Z; L85D o]fӣ(ڝ:LI}M>p%lYtG$v\ OJ{wSVVJuC_F$ )Gn9J]0`3Zfjwj$GVf@Z:3!\\d|$i8t+/kܢk64ŕ, K(dO-,}0oJ ۴(F) Kҷ90C07Z;#K3g DZ s̭Nrt2brDm,d`}~*̺Vs14mB]'r>V_ 5e#ckfG^T 6,7Be6+sQG/Y⣑=T꾦F~Mm-L*^2qKT~FA;^٪ӷr6gDyB[P:sfRnxGxQ"hN{?OVZhNa_N龺E;ζsb %S DɖՊ8ʡZҋG}(M!?#ho[ߐ;T9n;;FKȤ0߾zN!k k)YV3SpUOkO% BCɾjmN9Pv0#̇iHS+cg޿Jj2vU.}'Fq*8 Q9.?~3fD<(zB֟kdݓ.LkA(6=f*Τ]b+]mqGw7ybPe*>ON|`\>X5g{md92UmIxC jN9??Z8LZY\ ILyyC)6*'5M?ozcm;%Sc˿P'%Cx=V)XdH`($NĹ{FN:Q,W/M;Kki 'Lf9@]3ѝgE᰸.Q^@m)_ (aԟ]kq$[7%25@5;٬, €AI&S]xh-M޴a.v-s6aV?S1_.nTcJ(X8W-RR{{>CBzZ%Vz/$:o} 9R7|X\`dJG׏ޏ˧ ௼`+1^ SL_].pm 9sŵ yf hHJQ37#޿/2 ^Gl Ҍ%k]mhn?"`V]"'",$L?@ur93)zO1.]ˀaIFPnȘ7f%&Д#v1cY`!%0\j$= :ܱ;m NKYkQF6X_w@魅f^7J+M )~@ݷ?xjA8Nt<Ό}񂔎j$ |pQ V9&T dے$S cPsb<`pvc$bbxZ"9▤^Z; D!]r ڋ3R#.}!mAtF@PgMZp,1N%òYF ;ƺ8!OM8^j"g@ԹoZ9q2QghB^P@Vcn&|J~QrjL+ʲDƾ6f7+5MCfn@gGwN%y-h/@os XH\H8,v%{b׋P  9]9O2r7:s +s=0dykn"-ګo>dtiab7l`^ZsP3n4q/-0=E93`2oK̙j co C/!Y2Ǥ-$-viQ%}a@@q~TIHbWt_f~@;ǡѠ^X,㇅pWcSȐ+8?`)ECbcjaHLBnn+eOi:b"ssAAIgGv[7Dž }Å8Vq]ն"%_yY9G>dkaK>E 5S Em|LH>tc_/A#;m{(Mq~s] XT5[-[P6{L iFo-cL<:@cM}2*ÙYK{dJK|I[Iߘ&=cin#qʭ^*/9v-2 45Zh~-ZƮ]tpSW?"UV]+@L`-*A~p[/bdq6("35&z(Zb/qe!pɬ[(4"x+%Ol$.ŝQjռNw:|_lBh/Fpg- 5,J tA=j?\aJo&T d (i-l;WHўz@`MPO_ Cy$L!A^GM{S RF;V%8i|AO # 1xCkܤ HpQ.IբbѮ|5Pa ,R +~OaAŷQO4S`8pA ȴ|.W:M׀R~iߧrijICq¹UrvNF} ne}R>;67)8bA?w 5!3lV{`ʳ8mi|ѫhՒYs~?rZ<$Q őY.e鈯_ z3W:K7/) `cWX*$RZ08d-ގ0 -: RPscn*YCb=WwPs*K/ې>ҁ"tx:bq W"ɱ)eĽÍ$X$J?z<#}^ ?_c ^0,Ā*v\IW%iRjJ9B\y#x6gŬrЪ\^coq(;4;޿t޵P/v䉎 f8ߌj>]cd0Ք̪;Лa&W9khvΟjCY =K`q$Hk[99oh٧SXeU<$6A,|(ikph<f=kɂʏ/ j遥ڜᗝ|mܑ!UaW<c4Zb9Eݕ?7; UB^} B̵d^9"*mhb@C_{W5PvYeBgUk edNQ΋} TTT$ޓDQn Ӭ=$#.C*PB&x&WL2T4X-+Y&WRbޟGT:ϼ(6C"K6@4e/ (]k麂94;]&?c[گS@p!VO[݀z(On&}Y MkN"ES&`#[ۙ)3V榱rc>y Q}&hdN5U==_Srp: *[lPyYS1ٶcFeEn# &GC!=_y<10}hWFǏ5Rƃ$f)B#@SBe'/G_'4jVc|"(wAkd^"n8Oh>-I[@I^B Q967#8"ma ]h׹. 6_ʵSFeR^t=x^) O(ݡM勉Ճ)AR6Ѝga.G6_j@ FRM4iR6֛oHp4j[ 4'2ۡ؋&V~rZ /Q#7-9mwa\HNh1@l@3yCei38r-ZP'l0a~|TM 5>,4X}N Tʰf2w~F^墘Dol+|+x$dSGa#K v<㛀{HD^%4 8(|+6cuzt߅QYImZ,G7ggx('W)j< cP6Z\(f]6 ʽ 2z-zE:1ܤ㠜 $8Y_C. H._̎ԇreI/3ԕv}k0=< dr hpZ3}1&޹Ѡ;чQ'N\Z#2R%\\n,ieuj&Kˑ40>$!<=e mp FU|HۙL_E;+qAUxx)٥X>Q2JZ&?)r{qt4$?AduQVq/G]uGT} ^kM"5Л72cu#40&+((MUBA04>2bVn/1Jډd3_Hp*R93<3ӈ!R~Z^Ѯ#^ OOZd`E47Y:e8Fl?3TDhZ.t߲ _M|xq7! R.  3&`JB/r&0>AI&S =NOw|(>LtlqpZecG+ 6><}34ʳFd2> 8"=+}T{6m3qá+yį"UeU݋N@T ĢwwXE5zWu|J1O=s?7/V  7 s{dZ$ q%p㧫)j(iVAv~IPۍyĒѡRQ񡗒LD:i-gC&o_R"!ܘ2wl'3ΪwO=N/.3|1@J8ܵ@R)0KPRxܛj¤bSVBnX-P`BVh>o[5}`U#6`SanŌ8O:uɻY+Nf2aƤY9揰BK>jn^m(= ՏT[6+Krv. : @)H% ʃ*|y,PdT韧>@gXקzãz+ cesťL3.YtӦ8JBK &S84.Kla>v 9I RĒ̓ Ω"9Wu b"y ,$]@81bTnt :@I뤉)hc5%P閜 9Yw8% _{l~N>hM&cR3M6&.KԸ`{_$"LΊMRBzUZ Dq@$.FQĮ,~KVl.%n9o-t5,fK4{6āR* '&d8A*~z. `a)4T jJ]yXY^zO.:߷0ΙN@Q9lBVKV+#ycGCs#-[m g ᫹EՀuˏ8ysQY ǽ["QSg)See֤ú ː0j?go@/HC 87 6vIIiں;١-K iYQ9sM]V\]AG Ja)0h6 6WD YκLL/CJ*ahUʦ6ޱGH"e e.=hjEi_}}PAHXĺ);ıCa^[ ze6(b&ޟT.d]{^ЂEL=d5G4M}I!B6NuǤ}9}NxpNv3%W5FC#,Dզe@}$,fiP\ULQ s~&xR=FVhUw?-%.ӈ >de'\YF W#_DC$Q3[C"&ɛW N{ڳ%زY7Pʌ6{G;3߉0E+SXצ w+,gv \dB_ c)KdނXpgd#15U,{~ Hą@X2 SE &˪X'6D K ٻ1=|.8RCL ֞zcdG0n !Q3z XZe:S"yC%ˬ#2FK:ݾ}zdb"ywCn}n {ag+-/ (b3 }>\m;![9K`=^~zI~j0 y}*m~H&.K'grnfޙYcb@xsn'ɥd%,oN6F_}U`OʿnjOy<=>5x|`3ugܰ$1Q (ֆ>P.z˟I˺\p90G-5ͥi@C3&0(->V[-˖yļiKQ jQ%Ũns []QG>ԗ8#gScNIxRŹS]VUcp=*,q`U^)3zٟe\sÜ 0O lHvad?D06$q!bC{2] ?0jD@ W*7t+%.cD">J!pܞOs#KWMj 5!W;Bz|W&媋 AߤJl:[{G1ѓSO"vr#vae][&vYX P/cN3s4se+yFP|79KVstߕc&q* )e^u:~~o*C_max((O8'6:W,(EMye%x՗ɓ֊O Ol-#OwӼz?oL S,g$ȦO/(n9':dR"BDk,iqNHP#ߵ9^i/r+~÷;1B q>s߸:K3,j`"/ ~F C]o\ep+}0%=T4Jg+w}h<-hʕ$}w .0v)le'vO$Vo;& 2)kc&ʲRcx0 [{^|>a ʑGxl/bɒZІ ȏ;B8 i0RV}r5݃4Helv6Ǥ^ȆviKYc*?\,TF;!B4Ήl!d?M7l=w}Sy :נdGV IUs# ѸW$Su߷rce}S n ϗQ8➏+7`Dn|j5 j뻔q&^>zh{%]Ts j!90݅ #ar WDVÎv ]%$]- ب5Qt܏ #NIdކļI=#s matFl7mno /1jBQk\ۅPqU5՝&b$WU.(D=E2  ȶih)pͺ_W\9o싨15)&uCsБ!l_m~&Mr i$O_0# XW&yLEƥr hbK_x=3' ɤ@&?1wT2&D!9AU7Z('ڦ^ufy96^}D 0~0x!d-~tw-#8ӺW"Y}'#d H$`^_ݕ E[[Azw5*5<̧^WY=IDĺ'zζRՠԯЀ=R/ɟPYEkZŞy/'. =cd#!AFW'k{}ȹ9W]}JHe,|R1yHqUiof"Mb~4rWZ67\LVŀ܋͞F3; fXeMm3mL$+~,f>Bjfوf'c@yTZ3*(<_!ldګg7j?2_O'1RL^,8\q FEzZ!ԯZt04f UIŵq$Xpɾ0n6,z}=q%dz(ϐNhy6KfN W#>IC78f^d]>'^a/غ-͎ќm@IB-ɘS?Jg0}7N %Zck2*r>hpbt/xbx6L ;nɺ1wS-z DEIwix} QSźUP(0LP'8!n?TZAYA,\LcƢ3&[|6@Rg#ku.5vtBٞ즕A.14{5\qhjpC脕ԇg?X}:gVۤO:*7tz ",^_gQ(i-9P%53ĺ_Ms?6U fmSQu11 ʼ~m|z咥Ι*6s 1|4uMHH 9 & '%шўJ :٘$P,_.IOQODDz.Hl=Am+%\> Ǥ5mD%qSs-kӌFBp_>tECtmd:dNIJan9d*l*iϨCLɡ I!rW9oʊh˸ i^t_+,`M B!оK]d1?k%3l8Nnѹer!4/ PXETZBry6t@mhuVBYۑGTl @҅Tw 5+Іd xa9i?5(R7NKH/8T16Y/TU2ȴTqՆ$:1M-D{8KS Uu?#јɛkodԿ&fD݆:AljJS>r"ژ󜯯[ُ1zF\(&GX.5TiJn3s P+J,2CzUz/(?'cNJ,MWkyfkDI}btX`PO1hNƲ?<:ޡ>0)ڃ$K=7[mDiLpUx*R!<E,z;v|Üb i %ޚ k\L[s|(^]9{lj[7|{l&.<[q-4N[>6 ^;<NNe~: [=kf`tSMwuoɏwB4`Ƹ.(h\Գ z TC^{'d*k[cK6KlZ2Dn W3yan !R6pDJީ8?L(FfO;#2) ucl;YUJp\Jm|vbo!>\>/~$ߖ"#1"{C!OI)bzu|]B3Ƞ*P}.M .i )c*CxC2wQRac Abue͍}lO Ge:pVH8`:I/@P¼P,f{Kбf ֈ d"wZ RTC2"&¶) >Fu[-!6GL Cz8.~/Ms왢]'ÿQ>vmi<cQdR].Z3BYvTS|\Oj1CIWJ{`frø&Zq}"p9;$~k?WTw/aD/:hjݝzmF4;v֑,Z݉J؊DiZdILJ %#n3 (1}!+8m/q8 p:ѵޢows/޳u`$l)^-@PAr!|:aeb3XAB E>_v"Mf Pvs#PдYY~h,TVR;3<%}*dq {2VvW W x.P^*|!qn/O|8Ֆ]~l)C^ZHppɦH0499S 3aq.t9[Zi= HڮPF?}7K >؍7RNa\KJCZxr\NyI7Mz7VԮ7J躤Jg}5*ەAAjC5Lƫ:I924Q0Hq۳rpM$xVbV4@&A{oӽ\Fъ|DԹծB"XJhYSXNs_TI)B0׽9-_\a8J #ow mW='bōm7vl񓎕kNm[g "ə dMf|XFe2\g4`++>:(&KA685"(wcG,Uɷ0A|w=q䕓ZfF\ԅ@ʎ_22G#Dmҳ#{fw o3a]P K8[*E~:&yaEQjOwK)9X$n_bq޸=F2LY'o,n|R1|u6ao!aֱBX~MR [ŅہT'E@+N4­%@{wL̢H?,,D@`|CZH WAWBv З\,П}݁%ѐ8xlqd,;oApLU^[oV0ɽt24z{^UKK_JĹ  kxa%cRNPM@EJ^vʷzAY&}fUHl2~?p:H'^TLNw,^dq]t.^simh8,m.9r}\b78weZ/0WF \ȸG6hL:S&/zSP Tc阗ǁVbZ21@+볌hs͏K.?E;-#ȞpM)C iXmfq'OnhY'Ț+sm܀@o~`m'#e0:kyL*i99Gѹ/~Hk9mr6fEzF呬]('$G&Bk@Γ3*ܪܣBMXj!iv>szG+xZڗ,.]n-t5:֯H:ة+͡<{:YA0,/)ӄ[ͮT`uBx/)ֈVXi:WT߁PvIL\uJmRyf[C)N|C巈q@  Y Pַ?R]8f|߹5{=wÍ\&84ȃqŧJ46R]aϔ1Vrz܂ Z $N?Y4qP9h -C0"6V 튘Ƭض2z<٫ѣW2<8A`礂2#Hxc"fsA@/xƮ;d4bmcȡmڛodY-b_f ~!'!!CZw,7kL?+Ɵ)NY\[% ظa1MSJ ՙs=sh$uvg Hs~1DPP ;TfL0Y őn( }! +8."]sEATI&S8@-g>ӻ>| }`VuW>V+ YhWFTC_7ᩎ96JI"4X{㗽.0nuصC^O'1n2P{k JGɛ)Ectvf4iv ²$"[IMPcۆ/U#-Hd}IBo@E'49 Q hhn *)tؼW0,k^:9CI>XL0.DDx-_P #ed̓PuBLxBl_HC>`0sqj˧YBIVCMbca~YpၝaQWĆ &4t/4TƧ4֎TbW@7ˍ<3*ǡ FyUcvcK#UT[|86$k_]4n6d45KeӒQpp἞2Ig "p>tݘUK>Qf-l׃ %A<3Fp&*(35c@"{I KT. 36E99 Ք<7a0Ɏ"fxKZuFD-)U\_Xd6smFvaڄ/hlj҂9Njc* ߠAIB" ה>h@!b~N 7]H:;X]UΥ\R^݀| h2Y;@kgϻ.eF#yv5-ll` Hqun29lT*Жp7`"7j҂> DbH  foTK@jl,64GRߤkl?|TAE{ G t!}Z zW't<7'7rDϪa|׌Q;&Pj:-f.˲"ڟY 7 dl n&6>-/jo[8At?tW|q1`kMB#W-lyS3T!g :%5:jŀfrY>CL<!)( /認#AxU&.GLщha-:1.(o)$3R):SV=ЍU{s3NS+k7W>m$at|0_0D>˴;yVأ 'A3 =#6# nSoi뾥 ~KR%Vj8 UV,af2kidXM'TM Am2ɞ)4dڜ E.:dFDAI9[=Ԕv2ya|ԟ ڍ|iNhڴ]?uDřJ^sk—+}lf,AH=`f*c`8x&`:homz\_%p>J7~04j;e 5~ZXV+bx1l/IpoK+#E@fg a2:-GbZFJVi!*L8~YplaZOSt8UOk97o/^ˢI [IȘI'h#̱(A^n?@^kfeKH[sxko<]&̮q5zi,ADJjYq:>#9[ؚ],Tc6WJ 1@b!.ߚ_P%WǦ'l7٬Жf;`h"l[?3=EGM7=Y}O]Njfs"TO7y*{Ku>d[MrXg՘x$1r.s 4n?/WbpJG)3ᦵ):`` b~uO Xgǟ4[t¢/t1Q@6GpuZ X!|y s(et}}z2~Ѫ5P27(Ȟy7 jƭ"Joq~.Hօj_KuVUNTR h,eyXlU.q#\qidΣ1%FPF\+\h>vQtIA_zt#0ԶhG+J:bHB) -2B7E(70EjH?Ӏ'IVL%Oxk$ G`#چ-iC~tP؉k)F@gXcM ?y3(VpY0[[ǜa)_VPESFXoXD )S1UVf[`rC~b9 F L6*jL4xٟhy=OܰKX{|HJtX }*ч@J8z k iIwGցuflԊl2b_ֵr%{xkc-;!t r_2M1.d|ր٢2T_±)S>z*}F'29i#?$/]T,гE}x7rX0ٖO4aCP'|vW"XsZV똚p+|G}d###~Ity:+`kY $ƇD!2}L[Xx\ns!d/E X:vΕA&/oq)sµ)E:9]d(^g rl.]{vbG}jOKDt6M@j|#Cb\iWeS{Ħ]JU^/n}NA &@ sLZ&Z'[tІ `e+8@\uPN 7׃L;%52r a@IqD{h1nYu"<ZRA-;q \5_pGР>ua1yX}2v j[tOZ-z\گͭQDOvXZܞ濭qD^Ytߞ:iC,F&Qr"Ϧ-Mmv݄hr $R^!?hŔ]Ӂ]s/#.|:_e;:-j[%~pD j1*㡺fc$yf֔[ 4,1$& {J}Kn| t@>.:~Ҝ?R1泌;|Z:{iJYtuPځܫ_]qs0%ϱ/'=;p ~B[.  VZ( >Ǵ )V4ظ]Zg˶ǂ-_O؋jw ȏܲVtdjxCvN 3R6K1%UTGJ[H׎02nP/c)GCÖp̃ U-QwyV,bןbD:Do,CL'tTH֎n,e_3ބ?_ Du|Qg?`}?_Kƻ֭*jӑzЕn2'UNXL@O(Fiܼȥ!%~IZ$XpICK6 \<.R|RAceVE}]Iue㗰p~FHcOp dZR*P[zÝ0,)VÅ:!U/80|k%Mܿ[lNOަ\ڧ;@ˇ_ G:ݎ\CĽ?Es5AuI&S%>8 WmgZ3 %C9#6>#wlrOl[ACP䭥L_$ |-1frРYF{1dNl_)mI"_6suo3ZZͻ}. FV S&稆jFn1w6k*]7z {Ea*9=eWZ/3oؿS(7S}s*7OFf 0~/2 Ү*NļFMzKmo0ሞ|3k;DΘ׸穾^uqpNz hs(l@aJ0#XzuL2*s& g!}!ݑ;50iXj`*o3kLC[oq*x[̨{UgLČ@u pdz p 9 "苶*pfY [(BL(4z'Y,rjjNwyoT 9_7T&LFcm< mw)Ѻx n ٲdo&?q RǠΈ Ó9^>DKE|аȯ/@o?!IgU2t"NrpL M/7.GG5Ϯoc-8$Ƀ,ݺg|>y8U}| W]LwD*'Rϝֶw.>Rb-'df:%'I?$ZvI\z]T򆀷~CrN]75P lF Y"[XN ^n!ѤƗ`8e685&%r*~el?yr}9{F}mkgR1뫻\rpJd;خsXȜ# asHMJs,w)q%C{r1dc$.%qE!K󏻮 j |Z @"ȳ%.3d+f *_3{@B.2]a1 gb^aoZC}B|`΁f3p%oVQxT{R B{rqV69 zntfUj;r,X173q\9X9_apa*?pcD+.`d+U[U~kՠ%cęzhƕ$ &.\g3[ pD2 69ֲtfAFw)݇ RZsrx k㲇ҥ2SVgKM?h[?D. M,fD `G@7V84{g%׭9S!ijjI_ -%3S c~Eڱ LP3#7e[RKb#_4ed:y& IpDiPNCn_8s lf AI&S8@Qř}9~>K{tuÙnAF驴d6UJ t3n-goMz':PKa.z'5{h{O9m;o6Ylp'3Ų\~R6wln6CݝA&N[^y46ylҋdə0 hVј :WM.PJK~r\|bQB23 2lyD䇠cgF[ \SL }yN)~7ejA>kmHxݗm3ЗJ睢|eۓ)kO$b'qdH4(C%~@~;bQ'.mf MYu<"R\\|6]#jV_59{- s'g+GSrS]ҵ?okٍN%2y"Lz9P-Ig6rϸIosŊAO+<Qb>&Dҽy|5=!S3T~‚k! .Еhm=_f a,EM u&Sgr"^sB b>mh  7abd"]y7 ğJ9DճQ*%mU؟Q0>_ucطxD?aB{\m35868`o4Fl۔(n=3$a,Q &>3tSb Ś[?XũB}Hv-^0Ȇ{ז/qQsHlA!魍Z9\z.q7~.?c޸ȇsloOW\[1z9ߩnW>Pp]XDfN)ڃq'DpL2B-WcNkwBJ1Cƍ%]̷ yhXM.&p?$;Y$-sfI1&[2i#~Jx\4|أiYX F7g\VD`{&,FZ5Z@.#Z+׿{9aZ=.T?ZٔZ=r*UK" G\M3Yܠ ǖSkZ;NHS~++̲K2/4{v:I;.|Ds`\,)P$Y 5ј(X bF~a@sŘ]WSZ`*yŅe=@{)Vh@z= X2)Py(1aqޙ2hfĔkk*$ռ&Ë} 9{x_'\8M=*m~ +9xN ZN@VL0IYgW LP@0Ck՝e|k !4vޒ=ϫ"Ꮳ#V'wGͻZ+Y2$6hC*7^7!muV $ cyF@‰=D$A:0߻ S?i_Sv@y1{LE#ifk2fDJ٪)M Lo )e҈eb9NQ^GrSf$l *n *ARܚ%) ?YȢ{~fg3MMߟ2LhF|d*YnA4 ڧ/J?CFx::n⋦Pw]< \shGX'`t|q/1a[Fg-yADR $#RH.. 'Lϐ] 4Ji#336G3Ud(W:{jB`ʋ-$#3Vj?\DQb)D3bՖfF*@^S*,Mc榥 Rg|WJjbAAF%H1Թo _)FiSx44w=Gk $ue>4/nȕ=+ CzeK"v L,N{=-xsu0v3w7D.Rez?]-Ip63Lgl܄Ҝ?pϘ7#@Gd$R~EV})?= ?*@xh?Їھ4sY[Cee=N;^痳{= Bzk92d3!aņLfvWP8*K'"~dC鸿pC޾ ^h\& :6ZH [JU_~#{ i$6ԥ; [- nZ|4LQ" ˮP[M+BiǴ?I=T߮Hu:'fI.D[?T5ܬgr%[Iu/^p,E$c ZťvG :̆Q11C'gN Iirs*3p45`ՊUnPUsKb`ǰo`[CICD1E IUgC Ga3ٳیXkfM2(K陏!b@ ;h>4=sdm~7o>nQKfJQktd c~/ bSB< 9,UGDZSV2 :&u;<v! Y?yC" Ep~VJM eM|)[4 Bu ^vkL?CYU(arDζp,U1]3'cP$LO N՝(<_/:#%{HylP]g򯷅A_  ,(ڶ>Jnc')t-|WHee.˺k^#ctmBRx?YLMRl2:(oO{$zϮRwM)ѼHFȎU}Ëbk\z*R >.E gY6o8T8ldLD=HKΙ);L c5[iy_E?,ձ3&GZصK*;TQ]=Owߜ~,[M'H)8nm}1.˘ L絳aIַ٘PËrSNp o C~̵JxbySWMvP.|2:aY3;]H{mN\|Ό>Xe,~֧x;OA/A~JB.8fso"UlMœqn +Y +qkו>:jBIwn? :1p{$zԍGUK0YH?x`)jGY*[U톨ɌMy+,6>?:{H,pv1_6_+4S4zz PVSSWȾA eHyߦ%?*YCM2+ź`H?`r*HTWSJcP,-4/JNx(E^B4O 3u$CI6 FwM|cbcOsV%w{К Fqȅ:P^6 xl_5L5lZk灎@•KmGF/+0/W_îty1covf:I{eo2-<@zGpE5qAߏGlNy} ϒ8@2IfA{ Q.Sa7@6|&L|V0ϧWxH]A8N!!![WbY7nQ[VbYrB۩@yݕDH?Wyv`$z琰i٧fvX"|m7me McChUˑL]q`q=&` "Cw|@>p9li:<<@l(E{yO~Vϐߘ2̤uP9)WpèSؒm)#&oҹg6{=SYR B8miFy;a**5%%uhk qC cu0J)6 \޹=ďdChP}z\ޝA#BK*SyaJtx9La jxU2TuZK~Md-cr2ePK@6\m?-ěubYm4Zp"7K:Z ynXfG# ,Õɧr^,dAVxw2dO#-(ہ@K򃏡W2?JV$J:`Vܣg>9'Rk"=.Y`VKw2 %ۉ(!8~*!Rzto#>Bm%w6u S)~9   {udRC|L !J^ YoԨ&DBz| Ne1NN ) WbޅtK5 [kucipD] pN}k -1Z\ID1:fhK+1ɯUacw $DUҡ桜H )ۨ81 `>W㥙 I%{vf"t0}o.^g[ROʨpz:yҜE(.'WR(m9'8$sd}Uɾ_q*4c,;}ʚtʤjg!x}{7QQpp"S_k掠Xʳ< QYQ];i.mN` MiSnȅ ]ݷ ]e4R&%ARgPcis[ 6Ux ޮ@_Zc/Q_{KؠƈNL`Α~5arBUB7yO$rt(a6/. bQ*(\ae7P1Fb&!,0 _IJeZ@xH 㜱ٔx0;_*Y3d.ۻ|[H)B$Z(k#(=hxe#*6#eB-1L{&x~ܟ CǨlr*@2NܝK2K]4l~ #'shQwsZ #D@UcL 秢#G xDf0Bl;WnIQvIv1X&+N.]s{5gm b0li朇qd。n+42ICQmv}*E a!TX%%:<b{wxdNIU"`\9ސt S1C}iDLձVjuv0*ZQ ͰvbrӶRPk.coJ7,gM ~'0X0]/ 긮領"OAkORɄ@osB[7p9rd:O~j韞la=vFU (dԭAgɷF_qP /A'WvJ_L"7\B,N)_@K3\t" 80'UĞaa&ܪc$z<|ĮI:Kŕ02 fحE`hR,Qd$`7cw$*U8F&f x17K= E'FXe{9oc+;f8x#@9̀3sԖ\PTs<>kC?ӻ~N.Hgg}sxt޶agRWKifxOP/F2qc^KIr_A@\ޕccsÿ{R~Mޭf{Ch?l\6 /F?'Ţv3_:!] ](I$L& 8s#ȫ[2n2Ebx ڵ8yj\/n] K-' ;,&`2 hM2~Brj7n||ǫr=1.^NUB>Z9^#ePRS*ObsBuT3nUH~)_ş~GA?<1%LPp xiA t?HN\Θ' ]פK+U&N/]h'EVHSǼ: ~zO##A8 =)nfs4C /Tҿ*e6c8 gc-YqEäԣQK .WZyP%7YЏ =t7( ʹ+zChNc+^B>$d,q2X2T8Pu^I]bTċp;T TbgZwвZ~!ha{jaVlȰRG7.8BsIXŸ$g)yYy86Ce j"fj7$RL,M#6SicU;"|pKBsᢐF\5RZ)bpLBYV }7 +g{rl'457_Kz#aVf+gez`aYjG8G{EQF_kiH\z&@r㓝&;CPwl40;pbwSSO׵e~ye8 y">tl$"AL_@s3m NM/=M#/ NY: (gsq35MY Xnr@Dg>(Mv:+ p?05#E pbB; B ئH hԤTF<> XnEmXtO{l}%f Z.y)0SOKY :]p`4 f`]@6uj4%ㅑXXEJh *R@zbl\5܉_X2L7XElˡ8/  fU82X +4h%0-j*:T"^,E3IfЍR.A;(Wa7 )?qFTnm¦AXfLU'/*L,JO`|\8WSG*tfBޙiHE.*yN!\;^# }W\N-<7weXϐ;QwlxH) +YM{Ё%BC`r', {drqLZ:X4ߠ_ G'e"g8b& ۆ=q?Qf;pmP55o`ܚFj6!Orl~)@ZpTgl@Bvhd[|>4O1\?T nva9vd6]!v90ګwqxBѸ@^nXS[8 8?ϗbE2 ;4 //geY.QdJvSeܺ氡jOqԝ70yJ,1![(%*[>Z&)w ȗipU C4C7j SLsj;_'xbŎ ,~${f57J/!!W D`{B+gС.L%2;itrz"$?k\C\>-X zMx5g"{@w %k#XbF&$N:RT!4T2NgWƺ2opY+'-#FހT4-s$h iF"Fqf\è$ӵ}AVHj_J߅5F&tI bfwj%#[SZZ< 8"^ DP.N7Kuv0-=v$L!wq9܅ 3 iAg? *Ȏtb$N (x;vG5Fo5͆D|%)~]w.cO)9oZ |ba1=; n^xn=wt))1}?3lkZN/AqK_0~.50s CQ{!F.,. %c_]=SxW{.;7~VD.YsIFȔNIS4@m"EP9;^%U~0Vmޜ w?;LȘ^^{>LI,dQuk41{JQt` YkԷ1v!r.JPS{PBdG=ek"P DP c !͚Q{Ra/+щ,2Ղ5cQ}q5-5%,n;PD|-;cWLuka~zkhE^a޾˔xWn>5AL1lΘNB~|M9QQE_PwfJOefP, Nwg!w[B^NwIᓆ1E׵/6XGYe-a&_+ {8Teߞr40U  LqI$3lj4l6򮸈pԶ@"hW:,T$=Lqn[0l 26}GY [tcxi^'E*B3dKJ!y)):aRSJ8L 8s;U*kWTNߵ׬ybTytO$!{;]agx *I!j0fQH,>"Mcʲ]* ]N ƆD:'5& 9*[N17Qrh(xCm=wd]~2kŜo ?-z`5 Y_,rN+kK8I B ~a1"(INv7Ҕ.%yM7 O^8Y|-_q!X?b0 BLG9 𰳛LC|Tc=؉(cW7u,8ޝ7!M͐J* %{ J8J:myGR~**ʽ6$"xj$H;a nDw3CULlBv|TeV~}/l?ܯGJnmє:/(_?(GjпzTBp!1uVLKnh*@֗41]sCw .0OBYrI~4ӼEx2D0|>hwFǐșC*/s8n(`ƹjHG2,` T$*zR2n:c[\/`{5B> \h~5Lu&XMDcX߭RhY)?TuW1\aQms]T_tEFl.e.L- Hƶ!M>U%i!P *2~C%ƭT@̭|H|>;j> }KۑpAaCWˇɷö̺;pW5V:jwOk왷uFh֩!~ìss#Ҫ&A(Ns3yƷsH2Ș`<=Gs l{੗mV=ь]_,Jfػ{a8uՓލ\^r Vׇ !p8D/MP~pUVOrނ|i1p:KH y|F0Iai,S싚Q,3}K%;;k__铎n%\Zyi\Pm1`ګ)ʽ|g@E*w,S;io1QrU2W =2b;W턯j8͙;fźC=Dxpxby4D(t*ҏ 1-ZW$QŎMoY(!>`~֐;~aIh4חIr%:_y>XDT=- ca5 B pnT9`-a8;DJNJ]93`6V$v,nN߁E!ǎh폤:(wJ(^YIAzG<: "&a_^y0dcybzk22 7>:q2V@k=y56o^~p(i5 /+dlcEtuA?_~NoUk)iX & oB{3gPWMMBBk{mr;_yPM1ua_Ot k*>;*:؉ް‚z샼,؉g>>s[&#pB 4whCt4 I%RuOUHd],h>$VXg|*[]Dg׃ds[dD&kT k)YzOC4O<ڵZ\:")t1R5߿mIJ/4ހ\@>ڛʓu$[N[uZ$po)VkXFX+m'n"9QA;sKe < g3}g6} KZ@3w|$Ƥ1R+?h 11Iyi8,tw ܣ;dֻ1M"ԒbU? 18_>Q⏾ĀjXk3K=R>`6&S5 %W܈JEҴN|ں⺪I*L44O)?vFs $F='s L ( 4hDIrLpt/yFV2{|g*X W0y|2˰~ʹ1-C^;HR2KI42^tuE%OHBpO\Ix}90oxe'OeV/hc7.8X4>^xxv# z -)ɱkiy;5LIvY KȺ?u8ޛ8bg珫!7ihҺ?l[tpYـ]•YxˑڤLyBvK$g]3l%Y Mv_ F!ى8mT)OlNI_l#~|輥1Ne S '-qVЀ jxh8*>^{k{sG2;`q%R } Cgŋ"@} wɘcP9bZ]$Ğz}Bːp֬@.Z qXO r /$YYweU4ko 0fddu(O XšA PIEƣv"LI`^:ɝi+U% =„~n`:r @؆L]Σ?C1W__]hωy!%@\ཀ]!2mBCE!i!傆 a8S >!VpQ} yLſ?ܦt߲ ≶6Zx%ͦ;A|b]mkym@$ 76ݹܙM-qj廙Џ`A^֨U/%{88TYRqk+1.(Cș {P7ES3f^RҺ}y^aoi,~VEdad9I[q7g'$NuE8ŅSScюƼ,/zeV<6|M?toD7HϦ<+ԝhV%1Un%Ar Ø$5.<]ՙ vS8 (ͬwu>6%fƠ>Is@4BvL0Te>dzʨsX 9obowa*=+!; ZJNR2 )׃.b XƓuըݍ}!?RkXDu՛oDztBqi>P\rz(c2m2iEJaЮr*dXxKO4=p#\ݵ))ذТkT( Q@n`A6t@coيA(C/Bƅ{d"0虚x(M*98j5VC$ lQ]]sQ6 Dz{É80##NdgibfXDT}Gd{꘣bDZɺD 7-V4ɑ(,#^?vo$&Tv$ D+Q63+ pT?&0 )=`uW6碎>^pW-Coش&?A] d :̨3Q_ƀ.< KiQתYu jxeR:̀KBcx]1 ~/($V\©<\ʍQm#\ נ[4DŽE yt80EMyZ ,!-k "b=X{DtLiLC>$$ )߻Ѭ.MludjṖV=Gسn0gr:lGت†8luyL~0m]T<+#Dzir+jQ~g.G! 057D_;ʽ2dXte8FVSz MYY2" ́.bfk9>F?9 s >ԣ f.(NIO7TT(#gu02>5J -eQwJo{>Xb2$#V U`T-[.=y͵}􈸏Hxڜ7E+U\hs(8]$$ـq;Մ!5>~BwR;j۴9X[7GOlG73Ϧ6uo$8./DEn -G3cAI;- ;Φq/ܱ*wD0PXRË=3vk*(>iZ4W5.J+*W'HK31!]3nƨ$υ;NzeJ<#ޜ%;iHa[R f*66Sgv)| AۺFi'5 qU晇zѹ>Z$9#Eƻp+3߈O.z>Lh_@FCB]P u[*a55@0_罁3G๣d }aIζ£U@[ 9lw5qaG{S+wlr8v×`ki/6Lz*E2+F щ/%W}:|=H[]FxP$L"X-;$uq: J+P$UOQW)A e=Sw_"abs>@0 ϟ.g]؝;[&d/YHH fF+&(d "h2h3 ;LI.B[->bְ>_w~<4|M] :Bo^r0o"*gb#C/"; 큓jɰyKo񕆠¼Wd.C-MY z`DzB}2;meKT);*$[; %(?ݞ'{S'5˔{%Y2湺 vؼ{?98?n%ϼ$/:ΟkͷEu⋃-;LY4$itT)?5P\3@/_˩c5 XD( *!K{*0E+8=j 0]'vs)b, vT9ۇ?MIaNAVjP`2x) sA!lC?n,H MXGڹwZ͟Q%|-w_ |0w='݋\ͤXU@G ѷ_=j?rnڵ}ɇC3n)Ti|{t1ZݲMyB3®z jY|kPE=N,7֌T#먑&&(!a9-͉9>SOd2x}wiK"s†XY#E2{Pߣ>I;XXMn 'V-t}L#nA3{p* ]kjTK&9~7(B7>^F\]R{w+G͍6ue 3˛36 @ł 䏗^$8\%DJO6mmy)hg ↑cJV7H;dVKI*(ضKXh8n/#wl&Wh(&VJ~gy&# ]`_Y*EH! giOHR׌㡟4o3`Nq6!eB OEZ׺&JuuEmfԤd^<ֲx0Dd[u%(]]޲ARoawPf6\kRsچa;AÁ3WK:DE-)=3}fGU.8.qb{_{ԎnA nZ }LYCb`Jё"դC53e4WвrtjZdb>{}"fà %[1yn-+j oՍ"Ba):*P`=g'vqqvE?]s5ZoԼPSZH6VjGCIsr%tF(dY,OtqZb E;Bu.®R(:Z]@W d*[,F-DŽMiC@pE|yWv1Oa$~]X177 L=J=b2#Wyv5AY-\^ov]_ əF8Bnu`9S]]mDvOB0;m--EfS&[4X6]]\8YQǐVtj&?S0lsb Y&)j}n lF#l$#H-Y}G0-l(. 61NR9깼ʁLX/MTB/.XnVsK8}[`etej<ê!I%*7"AۑXH\TsVVm# ;?D:UiWH"Չ`S9s#+BmZ1 L!'ƈoǔϛ<\IKJDVd*QH6vU=  }Zs9jkG"^MYBt^B|sB'ׅb%Ca< 49'bI}Ք0A[r̀6:l$i5a)1jG.C3|0(uUQPa&AFbvp=#G\ruj2sZ<Ԡp*6$h|IF(ҚGb) f߉u4 qEvsS*w:OdgGWg#? 9Cgwk؍豕 xMm?3]} f(-ՙV@B"ř~cEB_n;ޗI+Fw-DL2%}/B^DH\ /˜g1u<`*Ax>DFNZ, n /= W^gH'9aX^\e^gGIf Gq|V>g[]oZ%fiwM JJ:X6` QʸJ~ %y.K:D{M{lJ±%t-z˨W)Пp_q>x)?~@ 7K٥ [.H4k܉vaKťt9D\*>_jCgb֥WQ[ .e~C2T*DͤkaL>t΁%zoD7PI2.gS*H"~FB?oZZMQK\i]Lo ՚Q }g XžCDS"5ms|nkW3)7tdߐIť;L[׫IQiu,N_ ºPÖW҃rN9^v)(%QXFu<-k΍VӞ[Ų@9mRՃ-.'MX(5O)D-&\K ߫z@ _g_($(/suԭ ncCq4am5K6Ww:ZG^ؕLr~[?L,#Br8&4!JWXǤ^6 MݠjV#'ѯ_$g" |ˌ;")r2!AƏ 8n_Sn5KAxyơPr +&)b^~$`-HIw W?)s=.(;)p9d=@0ӳ S]ٗEGkiIaN9G80P$;RCN3?+oޘƾ]kIe1q^)SE(yYԢH?O0+r S "ʈRWXG(0$ ˔ۍυkY8]Sehf]ߴӇќ47`1O'Vy[ k}XlÄ0>=)[&4*cXR(]xQi(s'te.r<[.uz<Ԕ0I"`i.*߳G`gGcCMa Tݪx!1qZk h>*= Glf@+Fcy&{%2i+gUO!bvrFLp<|Nq17ުiU`,1?2D kZr'45i}aV&!*ջ?VMaß ŗ@%3UG.Y;EF)?=es > S뮀+/ >@QmaH)\'?B8tFvnj~8_ϖ {aٖXH-|+4ɮ&y&=y4|Ǎ Fst*oLɤR)}z&zAS!EXǯ4ҾSGbKd8,R`A$/ӯ<'VxkɠVaڦ?g9N4X|s)q/O𘱜'[""V|x]M?Ao/1n{ oHu-#Fv>cW^O]P,*M4̊#} \;VWi؈@{CaxwAȔ~;^*gq#Î9%ICBJ,a,DZW;:>M:E^с$ʗ*fv!B1 $q13=W ``qayZlִZzma1I|>=AḆĻ_n}bx>?{U<:개y/mM #VԷn#WjWe8+eazN1u>R¼hWN^%쀟ӁKߥT“k@QDYïNT$o㼕 Qwcum췮~ּJwqrdr-XгAs}z-Lr!Umv-mP";^I ~P_W^sA5(](7 cVxkJ&C2;d $4x  K>oOWuC<5y.7= q:a;yJ9N9xcC~QfNgw0C[^߱D<9J8 :Ddo+O+9qg3k,P-HUE0n#0,2ȼzN}YDoD?`s`1>%P:P@LC"khnѵ2⥹U`6p"ЦCpk/SFdnWЂї߇$bYkͺ!TuyBA7Kc݋"@ݓ\E<S-`1.Je,m L_͞uh_`G7utP''Wc %yHD9zѺP`G=z,gp%5,Qd{ӝ>j$Xt<6|b>ԞMgEP'l`ɠ1,gϽcHgE<^-_G״ H<=Z¥pZzʆqe/1J}~4k5pnB]*'{`9 Xžĺ4>ks~ sb*f`3B ')Ď#tɉx Ml --.mZ744䭿`M`8.a6cp@#&RsFR (oy5k$_M: ef98;$Bws~(5$zTM%!szg1RC'Pxf..AК􋃒׮ lyYJ[gWL>HɝpM [yODg#pFkPL@;:˩ylda64/N$c5'b/R>UXq cH8Am'F>" H[Cᝲ_CK )N 'xy3e/Ќ5v4E4r+ݏ YUA$$b+B&g+8I fZqZ{Jf 70 %*og?4,{3zFm!ĮK2} ȗG%9IOY?MpXҙĐvE_P[4f/}7*C?ݦlޮJ8^(tpHeZ٫<+V5' &-MCS4:_쬟d$r`HCsMs 2$mkn "yfĵxJ &eHTV6k.2PSNOڊ;``fȣRCZA }IQ<AMGJ ʅ]`҄?!鬡/JAbsJq +1GaR6ExAӸCtu ]vBZ3 W]oTJP7]iMlO6*reVpcmdZ>~Af!┫Bw%˩*S"w]$ځS,G`LV:Ɍ㞸txoG,n13g5%R=ܺG`c}וn;&x&&wzM4CA<"/>ND{^=G5;QMP4MnC2A vdo}a$/:FwI WQ ]g[Y>y7pGAo&!p\ut]] -sqLYrJWKDCu,ЂF5܌U;@$#BTZcFRa馾枢}(vcs:+2ˀO-k:~EH(EJaGQvwe) 2gV9sP=]  ?I-'@akԝ57/B))A8DfI iQ)J8'0OGz/g4!i4CYX<- !-(47#WWDѷə1ɵ X*X# 8z'M(Nw}/j @3OAޔFm % AكzfDj vg+9{b☁mD/ ^HKEMqj,׽(%` ?U~#nHq&A6IئqxROAM+zc+3(@)Ԍ< VRwrZ+BK9nn*: b?$@ۛdh(q ϹO @-4Wk %IF%e1u㱺62lzOyG?9Z]pmgJm̈E].4FNw9+r2M' AUz~Hq>(3sBzM&GAà)kģpB5g"H O . Py0ɞu>U}Wvr#b5=Za;MP֜+-24W6Wp֑+E96°wJi2]xT8LA]ܢ˹"n:?s`~) I H~P le8wd`%}7cm|", |{.l^CO>.4QX8a^BnV]p>>j7֬jm2b+.-(p᪪T1YI%عZZV}wK@آ+_$Thd6MCn}l&fuMI~*).F=qkOX.&Z̏._Ӌ cbE5q'qp+ERER&FrB6{=< 3xq2c]+TX`5Zͱ&t&8y4z[=᪈ۑ@RYTjltII@]j+ Ԫ1mEoff䙷('Rt?'379B *\["1 G峕 5/*"o LC0f:T4KuO5YWyg[[dOR*۬6t/ՙ6נ;X`2fi"x; #MkN^@| h+I`\G`(B$OE"'ͻqBAdI&S8 [(e.˺(8AMW[ڣyi\tvn3&b/bD㔣ʀmr+0v3i2$z j7t#q[ 1IPJ9hHT&|FXFI(0r[jHbB_$)1yp 5pz  /-T Pz%Ý;a9ly8r֊s[ hHzePƀ7 D‹M ĖMbMN*O(ڧ l%S QHV:=ݯ26H9 5`vS'K5U\AXkBilQU5B1l{-/-CE |"a4d*x?JOL33S+̈́X)ۖIc8f4!RjxP3έ- 3.h"?:]0hdaT.wܚՍnĊ#ۊø@l&D\imN-@Ʊ_0JS{bu3rz%XG?|&LԽnvR3On@f{·=͡mfKTaU 7 -9E/! r 1)0(Z|dDi8TKHAWO]s" s>FÈaBKy)\.qx!E$H(D&%@D]ZGw nJ1 "O56u-:RMYӝQzEh!WK ;!hE`2O5 G.&pק8{4/oL~*l\pFvcHzw7l6 .`mhJvϲlWm 4"iqlyT<&&t9JFOp<7)â#6M×4!Ҏv;Nфx|}dU0A/":{yD4#C: oRY##̹S5YǷQ$Q>ipHr9zXV12bwL(x CeֱB]5P )?)(3LOmfO{ܝIZ H'2v_*el4=Qx,tryT]p@.YN8wIQ:v$Q!S d?[ j ʪ-o}fPf9ɽQugD3<'4UsHCϯ;2vlDL 9^dywqy ۏP T̔c̷&8J= we6)wM RyJ o`z i+MX۹ahBv?x_6w>r.߮}$p Le.a[ J0 ϲ&^"\`+x-l[Uj'],%M_wDcqnZevg+`3ðj|lmz#=oȏC&3(!E.Qֺ,6Q%tqD/|l:JĐ}ozܶU; O9q]sXmOq5#H h [%wҫ3*yhxMuW"ݬx]LD!}L8PUA"L`=9 t^hhaMzP+ @rwl)JhM,}FɟEV >TX^_.&h(@B ϯ|o\Eiyd̲Z}罫w<ݥ`Мt)~6L4b/QyQ `BlcIa ]J2Ǣ.=\%?ߜ~:L*ڑ¯B/׽w{ӯ19c4XSi!,N^m0f7FdƶINg@\. RMຫL!)9_wlx2=o7ޕ;7MRQ\8a" Uw50Aܫm󵾏$B3aljCwKKE`%Oww )-Nm []dӍp3r{Sy[f"tE[ʘUg۹FMa:+E5᯵}일Q۷"7#A9Gp_XmkZdw^zzT Qc⇋e=|/FD~ u" ύќY_]eصc-tKE#(9u ՌU>PouԪ hK:a{Zl}mYpt~ jBO>]!;5IA[͜8F5k~l',34&}ͿFġcQ` ^vtNJ_9W۴]SS ?>J [5d)d8ڑPeRuH/ 5]0_tߘV)TQ}B tXI=bz"Y]4Unټ. Ґan& ~a P,ּ?dWL{?{w"-`g! _45?u(48Ht7Sklxu1vF..ѥn8߁: x2wØd})-RbTvʜդjZK^a͓I ZNj<#-S.*RܒthzP 'Owg1RXiMO7BȠ ώIOj:w/dWZ4xE ?mW(rLl uWn#hE 6Mm 9GHԮt @jhx!\}zEQ9.D.}gz`? 8$qYUu7) 'A">gBרWD.aBlU)T>>p zx8ky3hjLFĚaqˆ.c;ݒ斠4YpgD5qI)J1 ٔ81/+u&06$K.Gx񳓹oD[sZ4CH}Ɂ\|E)'$oYǿ3N r]UC:SvڮR{oS Ք@_t8"g;I{0 o =үQ<4?k%҄Bx~l hH7 'LO ];l7$ΐFuu`hVE[(1̰3;\ #P4BYVN@U}կ!g_9@'t&tW2VΠ $MGf.˂/$(Exe=d (:lQF5_1) `m- x |W&)ҡXDC-$WqM)S&K۫ QpCK9 MfBLo2pļԋWN߄wwR/}yc?"ocaOZa*LH%,m< qEʱQC?G#AQ9\(/WWA{[+Rbp{937C`J"pK0ˣnT \/Yd BjuRC:dcfvXP/kM.'Zi+ IX' $pEVJDxEj+ӭbmHTVlzY/'ċ>`c۹\ z@qrz!vPy4!"Tm`< Ϳ$kM-l1*]r'TcreO'm,TF@aX1g`EG#XvqYEuzwvAz؅RJ w sIY内a-6yX/1gKHE\l'[3yf pj͝3L~7+rDٰsǹ#Y:O ]5zUjOuPxu9D$Fk܃UˉNGȦ`3Cj>< Kr :O4&YQ!:P2AW;=">U~jBEQ/ғ~K)qcS4D>J Nh[qDα!b6(Y<#B)ra[~NF5bW? Ẃfd_"qኩg%A#X߆;]w)D@OnN>ԭ~UޱpJ͈/酋F6Vg/0.t_:5w|u!RUhW{Pa-v>DΡ\ώn$&6m:8J^jS"ӞE쵽Q= ʇP0S}q[ms;wLpItD#zTOZlJHnbmD*ٲ94ڇ/ Erw/8M7 PzGŃ/փ}^M saC}V,gNط(x*yLP2"M]cD)ܽH6 w 'fH& ^p! 5~TCyf%jU!l~?v95TQJ/šD"|ѵkrv) נzlR8X yg%sK'koYIbչ c|j¼Z=vn*cu^ώݘ B"bHc'yDZFu0oc:3&RqT.rjve1H9n;*G7[R]Hx?2}s5䍮O2O[O $)ƗcT"sA)09[G\GF*~wTDjO4gʸɦAQѶM ȁ\vdՓEȋ;ڼ`/݃@]RKC_9f1d| ""b A/39$RdP> B& HL%䁅l\oI?a3MFG靄tQ811at SbɎ7^5 |$[5W@nbJE[z\B-]nJQ9)  OP̅SeBJѤN{y*gI# b?[^ W-L[q{I/TlޕRލ ӐIT(6g.*1k8XggS~gZti>eB-ԣ|\QFޤ6GI4H?Zc?[AB6 (¬cxWWo^q fEy)auٕؽx𛥒UD^D˸BzϿ+4,Wȣ׶!uR{4pPf^JCzP89K[.:Ȑd?4=}UW .c!;L7.kf>9ɞɦ._s~G(_=Piy=[_6j;v8"\ 4~aך^OĠK&JVedWX/"܈TyUDY4:ԀeKZqwEd~p l0lM) *x KY+mO,5} қLY#ӉPQ"Z4xpvX4i5]̲J ͋\8z fs]2gs.!ӕ>,N& Fd;*&6<:DSnG *of4`U( gOӬWOcYCp%kL?#Rc$FMd<8mhvU qnb<^ ڛ?q ^vgMB`eg qd8 k * jVJj$]R \| WxQ<, >S]wۋ\\Hz( jOg0k:_)u+DP&{|#Aqy"]ܻaFᜑd)6vn.在5;PItӊ#r6wW t\R_okHD8 dg}iEtֳݼoo,x'Z@vTړ0,PDz QAߌo~(9e}^UUөZon pV!P5{$1BLac%;D-)TC -tolh%"l dJ K.;h;dw;C W<}L9r'.dU6` ^$^ *N\X 菗#Cӣ;dPR6:_8"~/#=! na n2@aRؤ(WG~gIcaLmD:=P݌þ2-.1N%I AI&S8@wԎSf+h :Т7nzܔ9[7}z̃C@S^KǢk)TV_4MW!S\i&d4<> *E|Bbh ¶mmfZU7Bنh5Dv:b؍C1Yͮe(Bg,DԨFOdno' C 1Lwyzq9,3Hb[ ->W[_n⭟e^O(uL/=i\TIDZ{dui ~+aLb3Biub~M f{gOSϑe}AĚCvګ$qd2wF݈f73G#oʠ hxY,xO5ڄRjSQħ6lvO!Ӆj7A\vg͂;dcC}V!Bs-to*ŬO$ o7CHɝ1Gf/.wj?YUf'GlY{5ی6M &j_zi64]ݝj8pj,\%N]9C3N 3Nqb~L6 5C#;ɃG֥o /7W|iC8g^y'l_T6ՇG_.~+j; 0GOl3oM}2)__ 5˜tO̼th?1,.\TM,?ӃWz'yoZ׊{@^b~X4X*JdZ0'p{K[e<l(Gǡϸ-ԝ oOӑoh}J,ȔxSЀlEˠ(wDn}BQydHta>抱?,MzzH7s(y_xop;'zCp}FSwtȄu(a:|HR6M}WXP4*nRA %/=eK8"rQXã-υ3Okn&W!.~;6e%ylz u~Z-}瀇 QqvzW]Arc5/rZF5M"DD S=Tk1B}. Ofo`ϒվ?8OXscmVi |݈cXrVj"H k^n%A\b޳%.PL@ile]߾KE. |\]r$x`9i4A0)p%J$M"اʹbUO"T0hNAwT l&ڍ F)t=Q8F-2ZKRni|er+k#.Vh]45_Z) cE+6I=JiS h`q~f$=s_Eܿuw\p j p+OM5&9ض0xā0:JĞW0^TU M [x73>{Ћt&[QDW6:5`vatb&1L5S.USJzJP\񄀋Z""l]aWRTo)[M%'FgE\]od"frKf.ݾi4:WCYxV[oZ|]BZXg|𿀗Ǿ]/ 6ˤHWkWoE6pN;k9}j(i >91 M9y< R-A;:lv gP/3LCNQGbpuol fH3B E1^NM{ + ~Xb 7tڑH^U4> AwС, '`n42f,6=B,F`'F$2i^QR#i1%?Ϙz6몰NkI@tWMz om ʏYCn͞Iy %b Ldw+}a|m+)ў?uE@_hv(%kvR#iV'DЌp 37!u^bjThb zA) OK5ٍ|S!]&p;,@dI?#ro궹n`<ctеOz% neɨ*HyDH)Ax'0|&:!!a sޕ#\ƶlu+8K6\ヿ1+0ȐlSfBy$ge,7P 1,9woM?z/Bɣ]=15N(π8V -kx҃,f  K *4U4@ >.X$F$il lk}Q 5!R;N=)/&!-!ZS!כ-G F-Sݚ?M<0֤Z6Ra32`c^ǞTNli)9K] RA6Nwr^J(akA,XmH#1>0nAp˙% +HUEvc!A=ydz C2 bl9͠FHh.@(t9w.VKHgכoԗ"c@P6ŋX-#:?wz޴..|$ XXExg*aO6a٬G9R ;%X{wTǨ;+hòxS̲XRͨd^+lIΙfxET*tE7CyɄPpr#tiqQ 2A&WF 7Q2@kM!O^U.QMH&5PLᰠʒtkf"*z'VD h12@:O91cME^rSJ#zI1B`wDPoز z~օNV,B؟ۓ[ܴpDx} P *  P  { $@B 3 z9 7qwF  stco0 &2I;1JV!hfu2ʷts'3@LWiz0X J/;7G\%bp(}^ 6>n[j1Trv>dt&^ .+1?'KWaFsk>8>->OJUjpD}Ûb#e1=3HQbsz6Ū@z)1?/L{~5 trak\tkhd@$edtselst 1mdia mdhdDaTU-hdlrsounSoundHandler minfsmhd$dinfdref url  stbljstsdZmp4aD6esds%@!!V sttsTXstscF    "#&')*-.014589;<?@BCFGJKMNQRTUXY\]_`cdfgjknoqruvxy|}xstszY V  ~{  c   stco~S$09ITfst#>(&:2?KV.hxzUGR~ 8-9\FZao=| /yw D30Sq k;ccvГG2)0=XJ&V`mq6:a+<HeUhgoUإ3!/<`GXOa<r}oNtϒ%@'0}=%Jy-xsgpdrollsbgprollbudtaZmeta!hdlrmdirappl-ilst%toodataLavf58.45.100ffmpegfs-2.50/test/srcdir/dir.flac/0000755000175000017500000000000015052412650012725 5ffmpegfs-2.50/test/srcdir/dir.flac/.empty0000644000175000017500000000000015052412650013772 ffmpegfs-2.50/test/srcdir/raven_d.ogg0000644000175000017500000100376315052412650013311 OggSaP|>Z^vorbisDOggSaPfmvorbis Lavf58.20.100encoder=Lavc58.35.100 libvorbisARTIST=Edgar Allan PoeTITLE=The Ravenvorbis%BCV@$s*FsBPBkBL2L[%s!B[(АU@AxA!%=X'=!9xiA!B!B!E9h'A08 8E9X'A B9!$5HP9,(05(0ԃ BI5gAxiA!$AHAFAX9A*9 4d((  @Qqɑɱ  YHHH$Y%Y%Y扪,˲,˲,2 HPQ Eq Yd8Xh爎4CSR,1\wD3$ R1s9R9sBT1ƜsB!1sB!RJƜsB!RsB!J)sB!B)B!J(B!BB!RB(!R!B)%R !RBRJ)BRJ)J %R))J!RJJ)TJ J)%RJ!J)8A'Ua BCVdR)-E"KFsPZr RͩR $1T2B BuL)-BrKsA3stG DfDBpxP S@bB.TX\]\@.!!A,pox N)*u \adhlptx||$%@DD4s !"#$ OggSaPk8Mk*3@;376(-(26:7>()+&;9==,*4<+%*)8<=+;;::<<.++-*45829;16672;<878:('''(''<4:@o 'd@-DU+_/cBhosb^V>Zv,k nٱi4BmUUFLͲ\r b5QZb؄$ ݶmmծ+O~Z3F JAIFS9E+?I9̯~rmex(4`4Y+Ўz4G.$ P<Kh WeV ӒjhR2RV Rvlg?&}iǶx&@h 0[h9W9f9WU@MRZ Q56M4mRiڞzvBBGヘ l:Uj*8}ّ jDBo?'C+hFgmh@gD!^Nq=el/z`zBkI(wSF !)[YrFL&de37kaAЂ "ֶFCFhU>ՇN!bk2!V^O;tyT4?)>2ԇ\&f)`#:tHz&=)Z&60> [T4m%VS0B;UQԣ@0A  rf6ô["`}D[(QtUsvL;N@ͨ$"ejŸ*,WZZQ1*`jŮvj8'DQ 9؈q!&DEaqKjFTzB7 !4Q/H9M7T%]Cj+nDĠ9 ȅT z!MpрKZs~ůǭ:Pxs|T̐Љð(8m4@8X(qZJ!H(IU76c jEѯ_,q>&QĭR )CXZխ+^n 9㷕B$^H2F4 ޠ+FTWV44u{:DU=;jt_:vsPиi> EuTɑ,Yec1WU1VUԨQa8Vh\ˆØиZ-JKcs "q0P$8 AȒ _G4;r w^*%/xzVRmMpW[98.73 Z4{,+ +ΚX 4+y{\2P1de+ J.k^v,c~xw0X國v)6 U"L"&Tlۣm;iuiҥniSRwB.sHD'TBD(\dRpUՖ@GALr[ATxǷP4VGj@au*dAr!v-!A^ڱ xw+&1f+bjVDӂQ)8A$PSJz(բ"! :@|=!)1 -hDF"ܯ+f=$ \ epa6oip]O#6"n%(N Dyjc:ú}XR18-QRr͑ 96n vLj+ܶUF+M;ubG€TAeJQ ehJhuSi㺙c }5vTM681^\L_.Tnܯx#Qa6F`Ky׳;; tZkf2(6v,smرqR[Efe}t8s6ŘUUjE մV[Eb9MD BdwD'~]n0!W`S("IGw໿|mVNq bT1v:hӐqYpz77aA/ER+yBԀj0>#{<RN9`>*P1j|xF>$t!{ JFʋZE30C5F@ba-XX-TTE(!p5Qtr71t&>^DE4~ @)hn \g{wUf Q Gĺz@˔2cUȩߍ4Rq2~[6TFIy31> ~ QwP)RS| QwP)RSZBB`^ &Y#RUE1XZ+*TTh Q4C4M\B(D2 EU{ohMlȰ| bT7 m@U2BJSVz>͒5r +ݪWs5AL`ۻ^_9j[m$ #L,1lLyzb%9G^i:UC _4*!׋xvl`+mATeLiwH4.a\TVhL=mۖ&= Rn5ѕ̴r*IRMERo's?" mENAhBQ!OIMjJGۨ4 UTKSV~lW 70/.E 0++.$mJu1r roqts> -gNw2ߝrtN/ MJv:+Cb*VCT͂i70l 1 .EhSUM[)PSJ*S =p=9W bHĊ(4SWMUQI5Q6jzvJJWPvN)T pBim(V2к v_I \g9✙eϪQMM#فtMDm{0n8+EQ_7Rpb(JdPļ)+R.b- $*q4.!ԆVIWuiJTv!hS!*)ѱŞU@\=9*JJ 7ÃHHq6VRDUQAVTy*Lc6E?OA^WCOxy٥ZF)UJ0ìú㐍%g-1^\ |8%2+}w>f,$|ಊb+Xm VjC-HL\bFhX2qsJ?QԒ4VUi:Uk}cٳDUѶjyX1FVRTGuMR`F]ľ(i;#pPj > ' % )}q33ݺŬ'¾ȭuk 4%56,I(0bLz}$i&r,ɕ˹bUE*41Lͦ R,U3Bk18}^&i !QQ -ht$=2VuF .nםnI*-:VB_aޅ|c~=9KT 6Ndw)h&P:TM? PV%m):9 qn讫 U֧BrJɍ/0YR?p"T1z5vyqTo?ׇ[AA޲BdUFPmB!+Q~|P@/Oz'jj+PV4vNDיCBcG쾩xcMn0 *TcET4Mmt:M}툃 %ԩn[ {4W%jJ0uk*mk *m[mh:\ұQqA-m3 DՆF464`)8BiEQvT&&Mu+4VO]˧t۶4M_,Q /Q DҁvVEhzI#6iЭ &>>huzED{EDɚET3Y6YbV\UVUX`"vvG]lq(:"[MUTQU"Jf>|҉x^xQ˪m /Tqfg*\KWh ij V^l.8 |I tS֠F i%0]E]-1 ZHoX6 ^j5BrlKFQN𱙭òf@8t۱X*$HC$$ E:&L d:^1iӯv#ʠ~wMyc-w&\DG$dhheY 05Z,b0Ćj jZu:)i۞.;4@B81kVf# v}Ecꔐw"Т>~GjВB>2|euHFVBڥB|\9[Aƭw=h❦TV07D)s4Z= yVcH`A,w s /4zq$K_d=d ddYam-bkDPAX"VT0(XTNӠX3&Bo;P=CKʻQNG$Jvȳ! ıcDD#D"a-P;yݏLbѫw}xI^uiR6ww# 1 Ў>\0w!Cqnneǐ~b l6;^m¦`H z |Ȳ"Ŋ2V (dИ A".p&hL1 Lbbn@ER -'M$dR/! WX6"=uuZ=UꨦHyVQZ$ P( 0ۍuU1Dgdhsyo9' ,OL=ePrfX`[CU؆dE [` "F1c,QQ H$*R41Xr4P+g:=R*)r$>{>h44TB jRP!qqw[_ڟegy+}̬Z"<2D)1=Gj~ezóU 7@dh0IPr Vhu#E NYb@Pr.dٖsU*@YVDĊ`j j1U-AT KRQ0 @N%hڦ26iJW@".*j|"*A7FѴZIJe3xͅ~u*GBHz. ۂņVB3Iᘹʦ!PDi 8L [s4;b`ip$ǂ'ǂjj3m6ZDx+@M(0ȟ^ؽ ǨzcC+0F\4t_+Ƽ϶ ($Gq2E8뢨S}k BLjǻ)z֎[UZE*Sl")[-VGGbjFG !xui;=;=;Fp7 IĮ)B YFUU *ңIsp+\x&Ũ(FmWfP&6Q1U5U{,y%l?@@`Tۭ6őUUzҡ _~Ws\A?:AЏN쭩ȐXֈt U%rsv5jt>!}c"ٯ˚A-`U_׻`p$N'..A&>uc^JL!H ZԪMnzMt6.K*մM #aK#I@V=U}BZ B5E|(#QMM%`Rt t&ON |K< +( CVt[L;fDx`&4Jh.:F"QFMtpZl v1vC#Mz a(Tۓ "Ŀ֘: : `hB yMA$rv+ma$tO<_KSpLGU ɔtU 9PiNgu+xhͮX}&M^(v} נiS&srvtڦ ?t4i6 )jUۭںUtRhvѣiF3JT Rm: Ed(&W5#B IRj֊- gRTJH)/)=DȲp$ݢgS [{fϧ0zGQeFTRgey*skFf1pp0MG"AH$O_}E?tUe7+{MD*i(tPt*9+CZG7Mo]KTVG$QB ~؅ӭicIm0[cSe7˦57Lپ3"[w4 Í W;| ~(T>ZBa{Y, TŪ"մYDm!61MjZ4 %Umϒ6=RgQiNiRD[9.G,ZΤ,'1H%aha9}%y/$ftiJ&ESRinI1:Wl\_s~O\rk_86;eOԌt+a U(eh" ЀSHž H eLD!\|[FP[E ,McU.WkUԈjUՈZ+C0H4 _:~9oUK|3|S*L{ZM+JTN^L#ǿ_x܁ /߽p"M.'~'Tn T斶#3}$Ԏ$\e!yt `Eƴx~>cd;,`Y{vX&&'Y^UŊT`q4MCQ=0 0#bDR$&.hi-n-$N@u%~Jt|DUTa('mldvynT &Bh7UZʽDGJKQ9IʻI-"QH֨Yx_-5<bwԊZJӑ&¸/~&VOQ!񴻛D)wu~kLNqz%X&4/.EtU$vLs]f/˃h($ʥ{\݁ Ȭ%dmi^`q\HʪXPԪ VZV(&  Da# Da .HND}%M7I @QR"MVV% ,+6ΖG*YDFP[ռj שP>.T XX slK x%7}5Ʉ7ZT-JcW )~&4e>|Ho2xY>uPu.h@7YYα効j AZƢM6$ tZIӴ@n۩iPmD»CQ)RխN5ILKTSmdLj8~`Fe #Ң݀ xbZ~ 0ׅ̑X04hZeQZ<\,0Mmh^XmG%/@8Hz,`9  Uc!QU11q 8A.VcP={<"ZRSFWuR) W5*ttAT!ҳM Aygq 4ZEV7=4Bh Fh pdGRg`;;d}Jt$g!)Vn Vi^%0Ti^%0@Y7 Qn;8rj@50ZEkLE0AzV31UsS"4! CTJ5GHm~\p^ !PQ G%m)#qdZ2]4E*li4a#YQZGOjSB+ˊТ!=k 21 \cpiGͳFP݀dۦPmU6,"1 /PmE_w$ꡊT@q V[M) ͢*fC60N9PQ]kt^2m/T `R(P""Fػӄ$J?Up6T&Ʈ61v,UtK„tat6::d:>ٶ-f`\Uۦ݂|ΰmI:@dD?lY7ۍJ-h!,V4jTҕiU b\ ߔᛲk ;Vq`;>(y}ϓ趿g4n["|=ݣ}j 9ڬYw)bttaC+TT=*Z  ‹eƋϏ3Le"5SwH"!۔J5-)hhb,4mӍQSt gA$& v"UUVGHuNt:ୠq)+5="4dۃP= mѣL!AQg*cTTO\bVY7lݭFu=Җ-HJ-ZlQEEHjTN'i4[TSꮟg%ZJ3!Jgv !'.|-bF%үkyLZz`(b4N#)z mI%Rtt8TGT*\M7El5|+RJjҩFX8Q8"$6\?^dlt}֨)aHP5D"J6գZtO*L]ԊWR-TF^WF^0A4IUڶmM X04o~K]lF#V[X 6 -PXaVhrttzT" m72\۝"_۝"_]E րtzB&t;b=oJMIr4t8TP;h5"%8B)t" *<_Pon >S TJ!r~w|2slUy caBk $"#&"#(,+ TKO'La.@͵;vs튈XVYQr+w~zF:ƷD0D;8&J)+T# V-]ƊĶ=XN0HMd*5*Vꩡԙ7ӋvU BN,:EfTjQ鶨6ċQX ?&& C# 04SW%@`ZAuA'ˊZ F}s`h羫Q[FUPGdkY泫 Z\Z\ 0J 4mݞ={mx~/^h@\-@\-ݚ"#0BT`նmZj1WtEfWX I3$M$NXV$ZڮJ*Z43M=qm5rom=InӔOggS@\aP*yZ49*0.:8&6356847599::8*-*)))*563746')+6:7895+5678,56;,ԠΒL ܚ\(3S MSZq|[E'ŗZ_ƜcOG  $K~xdoz+eQQјĭv|Iu~֤&`pby08j*P,F"Թ>tG% Q}GYD L+s"NT1GrY|W!dw@G6F'О `B'ZQ9| {ˁ^nUhDT4٠@&(' shJ^+ZnIMڞ*=Zd8"+MP4խhD%DQHEY j*52[YZQcnmz 06"'?ӳi+%x)91^tlƋx~[VEY!"9K,p1*j,j Fb-(VӮVCbwؘD8RMMҦgr%I+a{VMMJ>jTQi DŽYEL_!EEREK5=,cYcsorc_14=MbӶ4AQp ХЯǯeJyE ۔QeW^yLfUȭi|ED+eDZ%Tk*,3G<'{TcbX=^R;0HS 9 9VlCwȘf;G̲Z[[-2˲8qS˪HUժk,`64MkFc0H=ts6GЫpA*膴QHzt9Fϥ#t;Z0w0Q1nD^.V޾E MlL CwS#X.FCXpDEnٵL?k Do(F~C ZE#zSxWXMhDۢo?}g+ cTP8dnQAG ab")E]&%.!nd*Ͳf\8CvtwqaiؖT:B6T灘1+2hPTU4ml$ҳ`Mc .<]WvS`VЀjL_dҤG4$4VnU{A~θ1-DU%]Y2a$ F ájސꔪ$mq4~G:J^D $`T r!Z#b*=IJq+gW_uUi} t߽ZղYE,ALZMס4vӀ(sÅoL[%*{DwD4L@c1Ƅ #N7:Հj{r'W/#T[f{ln5e,SU#窧Q4M|VUUUl҂-m\[Źwo*=+XLnbb8h&ЇĄa\zq8Lۇ!.K4bA4jn*%BM  Ysl}^&D i%T rEr1"P8T4=+0\U tbDI 4vr?+t;_d]= 5Z f3C9LJ0n靓@T2RPԜ>pbVIb9($Db,XޠE*"C$0& HĄ2ScIm(JTY@אVNDӭ mLJ4`h`$Ym۠짹yJꢄm MBE2 Wt3d\@mL#`77 D\_u]`_|<)^*Keg3Tܳ?f$IؖɍGmb*dq)VUp)"vS48:Xb&H@φHJ:=TQiڔn5JITW ~#mU/S|^eT( в 4X蟓ЊMЀP*6mSMzhUuIE6JX+rQIYЎRjҩVJ>x6!$] zm~*>rG[G0T|끷`$ äaX$g#S CV!fbXMAm aiUGuXIL"aS*PJңmM`-HnQi@ `TtI!q:TT2  KP"t&QDY-;pm LZT@>GmG=H49bPԁE"k*d,IHaH*+`1V bTZXPFIQ Ej0/"VQ=+ 6/GjN )+ɮU0(n#*Rz6CZ4ic}ͻ| >a^h @\!R1ZU[VG4!3Pf Iutb`>m5U؉RlhVc'J%J&8 xbѪ"PUC4QŴ bpH.֕ɥUҢBTDѤrPm/slv1@Q/4M5=HeXfb3f9ʖ#( 6_ {4@|= WWͯv,:/f$cڅs!0 ʝ=㩍R^c<1DP"rdbB jc ֈ,ab1TC9r˘Hx&6zP;A-"vm nR:Js9E)HH7BcX9~}!ڜ&ԊG nB,Po. BykC@_h{6uF2z4msAx+)GZRMݶ` є#-A 21ɛ,K: p$$4TX+jժ qcHLL%DZګzjw[*IMJgSn$._{4 ?yTUM QFj6i#:?BHF \4a@ѕJQ]8oS5hceOIi$V&t:4iQmu2=taGNJ'jkɾ1+­#+1+­#+zeI@2 E+đ 8VUZAp4E1LkNiRՠZ*-!TZTGNnPׇD4HH[T6=T[MC|g6& ۦR¾ӈINچl2@&JVifݞ1C^Mp;8#yժJj*r9op##"8 peYY.c*Xc,ZE&:XL,j* : `Qhx#F7PGa./& C4p|S!(H5a0t$ԗ($ P H1.W0$bDȫEs[\!8gQ ګnqtv>Ğ98m82(fi%1WdN Ǥ1WdN Ǥ:A d2$bU,5jAMH,V\&p cqDv%Ti*uK]4UN$MShI7C?ZV֤B hF PMRThzpԫ4ItD K2 l*qhw_59 ۨځ^\8 W =t z}h C \-U0ĴfpiU3 R X"#& &EDb D$!Q 9*NZmD9Q9ƅ!2'@%"T陪n 4` p B!| L?JmڡZytD'D/uhh@S"}w<#sw(!{A?B8 C>J=|GdIX#~wDV5IY9FMUF"$˲yE3AX*7yE3AX*wIl2c\,VQGE!CECqAU$^Xlt*MR=IUˬJJ[% T#**J۩4*0Jv{R3S4UazZ9qc{k dhӡf[Z\G;Ur{\8mI '86fρಝdY&,ۖ*xQI+mq7EVX7,B&n:\b7j5DT0M( lIHRiD|G$ 0*Q 0Ufy;Pm.g]#EQnS:9o&j T&mҡI6;t[l^4iy >bB`0ˮiv4Cd>mtGA[п] ]%-XSяNr}h5lx圍#-Z;&^qs6hmxHв6٦rŪbUCð iGBń$ >zjt;M@X AA"I%ZBUMNT @K\Pt;mGEM[^B@~EMNiY[k4ʴ$|#QݯM&rEq`xt3Mcѧ 74nvyo ϸ>xBСqKqS B-,˲b.Wb$.K%$K& QMSR" ;bPkA/3W^ |6ұ"rIsDKi?cHLC83~F3 ^Fi?4vK'&IεtR6li뾮]ҏ,ssg- tp|3 o%!^x\ېžE1!)} **XvG5UQQ'+ K$ vmi zkatTTJJSu$ RX{y,wO-yz\Ɵ+*-uD|\%.; 'S(R0*us+GK9z69ubvQzAj:)?wٴ1O;gw\iT4Bs+M Fh;ZIs̊\U5( J' b)ۣCϖ@6J4mI(Yǽ:ݐv7{@w8}>;3Cŝ7Tf,Sūs@5R'Q %ҟ󼎚 #!OsM2BhTEy9hơ>V ԼM񽢠 w\EQp9 3,fYʱZŰv(U@a1kJT6TgGӠ+i1g}r %TςlCd7,hOׅ)F'\v?ЍSARd:4Gm~O5=Uu _nyWpykBݬ_>\ùD Z)TcAF^w܅O4w sc&DSM =>DF9P%=U5t ]*II0d>x/~y]VuiJHVչI8"Uʐ+~X=QISi);FNMrFͶr,nZ5@\ 3c5|ݹ6 Z; 5Cp5b8O;t4|aBGvaRܶ c,g,,BA#Au(MLhbbT"Qݴ6]mӼl"P;^6qU/|?rۭ+>%_E3*繶0 72B* &< ADw=vzzXvvyWb<iT7u`MC)<vS68m/@݈جX̊jH*&b:)RhDY$a`GXaUD i"*"WbXI:޲X "$DrЭ0$2@dLz pJP ҃h6y*ctAZ?(0J!ׅF<ȰaN>> @!U`hT M8[UB 3U@鈛@鈛tPEXkT*L^(@QBU`,,DU#*sDȮ*΄i@đM#8t@+c?i?Jt*.KWlY d70:Y)A XUR'hX0:j@L5J$(֪Xǁ_BnZ |h#d@c-*TkfUȄ!!yYLPB,Į\z3KIJ!PUVNgi գӐ&w!uDqG̸:4G}G)UĊ`t9VSTmUbe_}\ճR3<јL[/YZ@U,GIڦ蕦g+E䪗/:3$E =Q[Ad`kUŘ]njbCt#*--B%qRgs qID)ddfB,6ˊ1+I +jQưEoabT)4ZA ۊcJܷ2M'նE6>]}(JW/dH-=\ڋdϾSC9mۓȖ ([hMVNA AN^Kb e k,~9$OA=ؐ}'9sw2M <ќּyTnnͫ~% ($YV+P)hb 1e,B\AT *1bDL @ !@"AMBХB#ٻpl[$!JZIG \"Z ! #/Wy9_#[jQ4IܹĈ@2Y_&vȡdTKY7@\<;drMsuƠg~ʭ$mPja@Jh ,zeQBցJQ= ]5,*` 1V15ʄa(QC$&qж(^CB4@9t-\*!&DC1݂AN6IWg Y#Pv6=jTiRn)!딇,E]uuKqͲ,`U $"@A( D#a4$B|8"nvFR]ᔥHG$BDwțTEOmiM7DLnT#4J%b+YJJTokW a8^9Ւ=*ϔ.pkcE ]r{Hnl7v#:OIv EPP^:M:W:M:w61TΪXUH|$*Wl$ h!!8gHmڦ SEѳA$M$H4|9?GIE+3\`3W5،5eYh"V"C 18BEd@e@Qԍ@XhE!.HUEzhgCU{p4AViTR ^TYsR ,¢@ jQiz4 N$*uj[JǤ_@\ s/@?S &JNњC421ahG*:-.ǼT_D`4Bi1B ]0DX byܗд]*T;gLJS8H"b LL40]G]P՜TE%b24 4譣n-S@A 4MMӴN&(` qs ?\[7B+* Ɗ3=t{&Dl hSVBhuRRm:oFsQLݭ8Mݭ8ÔPKİc*[(Q^5 ah\t#UH՛ O.6Ryfe(ӫ-_Bd5Y)Ԧ,Hb0f +YWcĨXUT jX5]NK4NIۦI?8q6mՄkGK_hCXR-M#m)D5/JUSݤfJh=t}kogͨvPQ?="*cAhhLjRNwπrH QP:!M:YՈ5ͽb:uVy5bMsNEDmIc6 Nr&REb5֪jHRqC"ر.V!OowJuKěk$yODIW@sMS]%TPۤ)nkը(D >Q|%Ԡ]4a?DzԧUF?fgZӨ2'z <BiTÅsΙC׊+GbM4qTj":. D1aOF;N5MIQnuKtt*U>1/MizXJhۦѶ={}r)nYܨJ%IեjDBE! zT[@s2ZwNc) y8U+ Ywf،OggS@ aPA(06664))678,+*6;4<<9+.:6՞@D-roX D4"|0I6-nrʪ ŎaX,Ml`nجNKuI7jP]I5-;pB:5y_^CZ˭P:)t$Ohcl !|]=EH#IUO`yH2-P:V f0^ 7D4>ӫFXvo@ؘoNd}E:^e1ŸpM,#..ƅk*}!$T * uK.XMtrFPQ5jP0DHU ! >htۥ*RUXy؄bhV=s.E*.(#m01qLM\Z;^kc(gYl{fj!`DM_B#&!~RQ>w4||ȓvX"P-ixmqa! *)(b(ƢU1 J$08 X B )IQՍ-BD/iiz(U4Jm*ۛ~V]ODRyV Ckf6}nC(Rmdn}EԨyh*K<ݑb\1a.Sz$=gۦizv[T (wByBRCފ|GgK%C+vk/OS| 7 v&*jbLA011xYt#M7hvZM?6JvJJq4j{C5*SRjLfOK!S%AG1=hN TAM| 5@,_>\6 \րUdJʥ|2_RN ^Ri/kb= vu9VYmjSSQE.I4b& HR EmChnUvtźNѩ PzAQ ҳ6݂ գ8"V͉^N)T*Sʣ㧑]NR˖`LoTHi&YyZl]}y,DbP6swϷ0v6pW[S7̢ vdBJ;Ȭ1>&ʨN,Ǥ1YUUZ"v16X펦Z4il$HTtږNITmG*0_BV薎|Qa9ᚯ'$-'^$@KIaRǽ7"j"Ab_sپ< (Jt!I# fE.hzDiű=|b"B |"r$uh,L f@@n> T!M NLcBnRW &(KSlvݪ/} >F{a}Oj<1Ī,*гN4mG7GZ5M:w^i?0xYO)H`1b,'im)^hZ wcǹ Mbn,#@" ZvT5>:6K:I|ngayv-6,E{NRo@hkCIKO?5iz¶\qbK5*}Vy֒~A1 4g^_pefv/͟jmMEȾ~;iMb,W1WUkZXQb8X-vXMSR4&-U%$[TtH60F:鶚NJRhK)yiXo#[WWwĝ"!`BP qz'M:sSqW8 /1KvJ1uk V@k!Ֆ[^Mܜ3Nc֥n7猓Xu_P(!2b4Y^ +6s!<Rb@Ubvtڞӣ4҈4*ږj#+ԕz@BsǮѪIGD]BTNF$=#G*EDC1 `,;6*!  sN~qH*(l瀄mq!)w"&hTG[ mBX PBpުhϝ%> FЪhϝ%> F",lLr&"⡪i LA&i sc.˕+;_[SWwx-ǀ" EH(x'DZVER)EJVR1U}k%LlQIVuz4EHғke dbBE C`{űKIv**"Hl`B4@g:DjnB eL#m%2yXͶDG pVTtѴ* R4K$>L$BhDЫ%M7iU Bix Zb5`xT*E" hUT4xwTwSܶt Z6:M4WnZfhBU~!\-azq!J t.ظ4UyTuўJVPTH™*DVh DULJGULJ+j#P5X1̳;u9|߈bR44[xo' $-b ^Rխ4ISm)mp8624U s QUm{Rm!%[;*ZJkD MTQI <ۭ!vkH (0FTNJ tXJйtj=Vfg&=-x3+3LzB[/b`ր.VBTK)!8حV+EVAc4B,Ts4t;4FRMcnq\ȥ-&KGA|VU:*uG^-mh+h /o9oўM Ȯ=3˺n"K0e 4zi5hNf0:XLBn |1g vnna u+i5:FٞVC\1l$ HH6,*ZĪQAPiQ5 6MnViF$8<&L*P} MJNڶMNS ?wvIk(TJuSpu i1YUf)nx b7B@UT! 0? `G fywNfywNE-Dl l,)ob&a`E Q1  tB54$R(U&d' JmX.iO56MHJ rSCo+i:T4C`0`ATʣQ`F(M+'vJ} B0-4ŸG:X)Q5M@~ęZM4É3d-d-SHgLV:ɹUP+`cFFUؑ06 ŠЀ @TEӅ*HA Lb DYEҶ* 7mZ՟ |1 **m5[ Ij;5!8BbHbm6P6rK|>MZU4FpnrvPhj02l~C)?/ӭ77'JwOm[$mf8gM9RAVG@PqtQLG PݶA5JU7.- >,`G2Izicݭw4F[. °A ID>x!btS 7dcgJU[t[T6&]'@#*j<JZޡif~D&8x:9Vr"\<,6aSV P0L,bA FQbbBGHlԎO[Q=*M!: AQN%B?3(MiQn#JRYjkTUl#l6Rh0ͷ49nw:Ύy rO⮟;;T9OӒc/VZ3p¤b^=5Eh=YD]=5Eh=YD@nТr)VŪ*Tcf8b`qtUQG0^BI ꁫ,+?J)E*LAAm*,VԻ0 iJtJ( Fr+[l$-ŴC؄Ѩl?o s./ɀbMnQLz-aȇ>9=*c79=*c DlV6@ UU1 j b6j`{VHIsm M$vCaFh!Oڄ@d>](ܡL m7MU˗(֦4`n3h -vϐ2e95M'HSi"hÝ 8E-Sg4_9 ~݊I=nZl~@YF rYPYjE`5!6U:ҳݪTDUu$l}8C-?Nߊ~oQ2VC/? :Ӗ)=T꽪M6u" `rMVBG|toXԘ!~K;X@a ƗZ3XjȞʽ}z"لA&9{gD 2M@ e,)'ɠXՁ0B*bEDZ#(PĂA K86b8-|&sB@|a 8;I@u*0xV yy 4n Tizjթ*Iv"K6lDW~ct`ֻ*TE#QjҦB^?SྫྷEGQJ}z0=<(;>azxV1ET ` HZIA&RHD*bTA@L1A Db1D CPUj@iA(mQWfBAi75+B(M¹9oT6tJRPq![؆jj{sQ<[t-1іFs4VGᝥfǦࠕ8z&ƼnM~mx~';&U>6mU*j 3 7qe1dU2IYVZU0Zq :vURңNiz6j;4MJ͛I$MRAҳUUEjmmU,ŕ|eUupM?jY> 2PEin+UUS!tk-pa4NhDBv=i|;>iҧ*8un+-)Dr֙-XƋWziAj``< \Pբbըb*ADCmN@JdB3ADP6IQi*Jp+OW6Y)hiD&QT鐮F۩DtIS*1ףMp 鐪@*խ*JI+h+j;B8H%X4zQi@Ih>-i+&LA<-i+&LA0&YLb&1M,M\.bZm6 u0p@F0AJwD2Lwaj8MHn-Z~O-0h*nZJU5nSǮEA퀃@:?ji;4hѴU=긦<&MqNPI-W ފiKngEQѨS+v*-YɚEENgMq@Bq'8H0%Cq(IԡMգ Ar#m6*2W~^/Y \HCRjXίztJS%;4ې>umn3E~3FUmJ BI;Yqu_-TߋP*H7>-w;,y|J bqGÒ6˲lcTY*6TM,iV0#a:>6$@QTG5m*d@u7oZndS oO@Ov$$Ik^mh vUh:M\:}[ޠkhz x[aTh 6n %V2R}u iS4bgbgqfu*ptXl!i'Њ 0L;M[Sin5hm_]elOZZX2M(itKr``+Ou1u}ua tATXbF nj,NXlAT|V1+MC!%(PY&#|M6)d4ug~@?mg\ q 韶~U5$˛6lCr9"5FQTk,V-Vژh€ >&6ti.ŅdS)n:UMbRQ m =^2`CZ#hҊJ),3x{/wcGP(I7aΩ^zr'GkV9ҙM]0hډF cL&dȃ1mk2N&,U媪 e U47̥hbz:ҴT[նUTTit%m[J9HsI(hѴ%ЇˡF0^+T6B3ϫ\KAT\:shwS( d nε#@!ѣzoCeEO׫rBt̏0%ǽ F9ߍ>rT'X T%#:i%t[Td[_B%q0@( PGj]@(B_ΐeCEB/B/ԩd jD^tV髭mS{Q:XJS:Ո$Rƨ[ݢSi4 _wƿ/dvl|sbTuQx"i:tI䦨R B M5ѠQlzC'hX?,/Z$)4$*@6dL ~`bgGY;;#=eֳ.\Mc4"h6e֑gP/XP.<~iB~@#PņM6rV9٦*(js#*`Rգ%=H"^ĭ!-X )t;I\R4%*2/)=UjE NXš""WZP,`c*VfMKRT}ֺ%:7LlG F޺ʚ{#+ںʚ{#]rЭ s!Ř+VEPGâͰ aQhՎ%$D4@J4.TRi5USJdE*s}P%$IIbV@  @N)M:jDn;9:%:@G:BKRENQMS/^<#/U:8]Mg ">,.WpVfهQ% Co|Z:`LYƧ:W%z0,S +yloMs++\ QG$6b`6 b\D *0s!UڒR%ZEӍ h)MHS%˕n_ ӃAz Y11PI=ĄM6q0&ڶbu@cݮO)6;e[I ,@0 [džzYtrCf.v2l#DCG1EDW^tU -쭫$I c:ж v4qCV[`EIZ$,Kӛa$JҦ&J'*wɜIҳzR3МA[f!BzLynv*4 ,o9抹IFцXQ1B)B33IiiVMZ$TAGPE$KdHIq q N tSU@IZgB&+ii4mG@F 1 _'1=Wv{>v;04P<_b5 }! ax2;&Tڥz~i4Ar/q?;Hl ,ր*RMx"h,r`qA踘0aĨT5&ժ MUӤՑ*FztӐUI%M-erP*#DU%4mu5uURuϪWL10\t.T Ae+BXyn |n?C7U:EZD6_a 4h@O0NmeZ~^N̞R2=},VbcFQG#aHGnSMu$m64 03JvYb"g;/lh5 J)Nibc:aA_6բ:=;mRPah'@3k+dܒ9"tKL.=/&J>Y^H7ABbFz#L,th6;թYv'KżjvD ӴlV1 ULbbHlDhM*EA~^)-c2oĊ*-(5\" 'T#UJ!pP%-E!QhUREd~T*V:)HI'$O۞,ogBd @;^w̹qǹ wjK$>h]b:V( :uk5_\nCաC2M^+k3(s\VUjj(6DbDb(TڞJT%m[i[ڤE/Ղ8NUՖȮzEm}/toDsL{MJކ j@OQϏ`(v8T"Ђ)@\5k mA䖍ͅḞ%Cz0$*;bgh ^-5AR$l$VU*WUQ"8X 1 QbH$Ρ G4-t[U;q1W&j.N^ӭi?oԡy^ap.DKCθpͥqi<3a& MS1孼GM2!DP "ᭁw%_DBdeeÁ(Il0ꜵ)H c!TmEg-Bd22EA&P޽l,',XUUC1L,ja$ĄH4.&E!q¨MэVJ5fg4{G8}䅱#q0[B4TPTZvLr;tk9$BQak9G, KM6W* 0#)/^}Pz\*ؙ C@ecņ9bOggSaPvs+@ ky.19eM]ۜ.t}+ڌjݲ,3Rhb̒,瘫jT0QZ#"VE@WnRvDPnvC+Z=*%:AWz{Y'*C6* ዑ8<9 ү_ *~KZ"j$9%'OCϦ=kZ1^#N5TT.f-X#6AګPYO+Y$^P\W/uRle T*06al4[}b<:^)LU+|親>t ) @B&@Fmn P0v\.Aʺ 1VWDpBFt(ҟ{ߤ\W+7:Ec b7L[:Rҧ_rhۀ)\ׁNO'7ꔙC ֊AGTa" h񁉡WL@c1]ŞhItp)*(nu"8Tٝ!l6Ԗąn@0 TU5dy+=JmTӳzs\kZpF2DB5b*YMt /U]/tR.dJYg6Z DL1Af4=Siz=^,j[k!E%2}u"mgfQxɲLtbHۿӋYl7eJX#BVAM6S-vXLLQѠ7&F+"2XHJn꫃ СGRFhz\ FFT%utR.8[AHdhu|RPHJ۩7)ܴDssOR>tLd\nHd [ `/M&@6sMp-=w_f"K>VNL`C/-HsW\UDj fZ,h7q!a4a(KMQN'죞B?_7yD۴EI:h*mFW"1%.k; y%[V]XtXЯdG7tc!M(Qmo m4Ҥ@7 6}13gŌ%x8Ǯ& PӝުYUwb"[8J0.ULdR@YE| T`Q(`X $&6P\ bdjS%4]j~w['Z<9H{bnQ&6J"gj4UvGc8rH 5nOUmiU5[mRRha;X(~gU,l`DZ,|t8|}8m\mp )Sk%?L%7}͇%.+r$VTQ1L5BBq*&H4cc!Eiۦգt %\t %I$iG7TJ9,MZѩhžx>>ӂ$HH'PK)CD X`:ճMUXڻde_j֪@ޮ%i۔ Д;nņUk%"HK r_q)mqI*zeʊW*644S#(p\HHL\'m=+R66M5=#hFݩU%DEG7HKf: nקْjhG0wሗDTUۣM*#f!Kku @k~dRMU#C'v3ŰvRm [vE4Ln/BZ#;f H*7NY&Rz 61Y1kSʊ`8 6aǂŰ)*`7cG&mNH%h҅*!_S!T*s P&__r]aǟd;O;icCц֏=GtJh,WQm;UYİ:^G1ڼMC=VNݨG ?߱;"(cCH,(cCH,zM9csU!&"@ hbH<6. {TϦT4ݞ:G(Gx*Ցj5%aJ—+bAQϴD]hIҢmC>h@+L$HKP /W(t5Lګ"@Gz-|9dVEz#%_NLu {<*Y`؀I I ϛDQ Bd |lbrFSUTS0E0D0؈bhRjffFA(8),tJ1#Ѷ ږ @MY jJ &*`R>۹s8ͦٔtHY 5 -L*dkB?(BϦtP077P[RODq)#Y#E KK ' @Q :Fwٳg$Mt:M4$dޚ~}?ɒ ͋dɄ&#%SLQcMiIm TSTc?6mt<\L#0?3$Ggbyɝ!}k3(*Bfjlr1*("X1 jE1*I:jC)cӋ6k(]y}˟:}˟zmA$ʲ*UU Up8AQr$DBH8J/l+*UM2  j#:)m[Kڟ14ъRU zi#iP( .Ad\V(i{W%md2Jt *g11xE0f!̡A(93f]hA/1An4].^z=w)Kr %sz +oX̲bAԨs" A|QD0Hn )=RTrre%t_:EO"rIIiF5:M D]1IАm̼);|^Nxkpi3FT%f 9X+yݚBr$ $ c~J}L%%z65Sc*)i_0e]HUUUt Q1bQb $ D0qa4DCuKH g[fF^zv //uJGa^9C]im5J@;ȦmE }-fF)qeEnxa%KM5D@zx|owe o8< &)Y1צ>xܦdm KFؕs9Ř9WUj5, *b1 #jXM5BH|@H#J!F nzM?=е03bq+OB]X,ssz1Rr8w;z7%uRNJy ze=nysl)cϞZZ%`-aMin$:yhv!.;rFu>x% E{x-)K"0$l,rbYbUc V İ(* cPA'l(n N 'V; ;e %EsF"8[O/aQ\Ses*)ު95x V=KMm-.@7^?>#jǢ.ä-"_W!Z/l˰kEF#gew ;w\JMsyܿRjC 3@M,˲\rUUf]0f$GE6)g._qἮXڴh*ntL5RTqvw+@Dx(x|K ʎ,P`DUV- `ŪjL!L7mE9{H oX|C(\ <!9͕i VFɦhx{,tX؁ǹ"K}7Yr\A 51jXa"q Dcb" 0 ( Q_hѪ{Yow\z)IEݔ"cqKQCEi*3Lle6WO?0XNl Ӓz8d@u$'cmVkLF%@ zmSXkJ0~wn9U{wn9U fy@6e\\jڭ&jQa`Xj` H%JNUn{|,4M6Ƭk]YTJ[I'mIj|@0~ ܊+tWc;:Ego -9LX=r4RlFDT@qǿ!: y~wtq 7o*V%&(qDb'b2!R\sԏnT.-1qFZLӤUU2UZi[)4ѲJJ۪JQ@wQ4篜6:JhϹ;vDB+o}PĈᐿ|عSi.njbZ[XXQ{wQ 1lGNۢHV^wrwHY(KųLebŜcUfs0DP5ĮD"e96>FacCqFPQi٧z9g#LAOUiP׾vDp]_4$O@|^/[&ي/fk %>|sS\8NB!=Ds@CN5Z6|s2kH\i8 )vx5UkGWW[{&<`ɹ,VU5މZ&X "&61 @㔪(v4EDiʧZD'Nդ鶍+ciV8p "Uj]P SO)֕Bt_:խD!Zxa'LNo-cѭUM2ҁ љJuì[7ɸX͐q憅p3Ʊ ^wtf6k[wtf6k{6*Db#db!U4"6 1BUMNRsΖc7Evl/[:mGMc,@a!Cڥ:2>OEq_4Ѹ@b.QdBJ4GmC\DNf"Yȓip!£xI91P&sKihps?雷^wq/˂gH޺{I_w>:*G!Vi&YrvT,媪jbUTD8 Ѱ5 1xQE|tī*&QUH*BJ} b_DGxmZRmU4$(Md]|ǃx!"_ ]> F{&ny!29%~h! ;'w9OwO$r Bp=F9qe ;}}7|^wn?ӅXAOt+~sLc,grUUGQh4^AB!Al*@DvAA!au-PC >_82*$*)iG"U*DMTӡ)sߍYբCDs2'y!@}I L k1#Wބ+\GQXaaӡX1I5o Pv7d oٜ݅em6Y\UUф0+فNJW "Q)$ 66"]p>f9FUgiU,UwUTtn5jߩF+nRwG %bEՓRAHE \S%S+o.rb 4<~R[`YmKxusׅS{cVÞǝP.>r1_F{%LP2vzDy};bFN\xRblmrUUUUb∋ N$&1$I -9HhRiu(*8PUW ,Hշu °11 i{tTSg*"O!zbЕn*Zz sD47TfnKƫ{&MO;)iR{]Yw@I-zv3^p!;V3D̩u־vb,7hnQ0|Mge1,媪 b  P1$&5-!.6Fx4Eg^KHj<7*0=t"TJX<\UГաIW/=,^ (F}ow,lC_%GVXZ26Sdmt$TUa',U!a ~vb?O@Ow"q8c*Vpp+`S;%aƆvT¨㉆FmBg̡H +8DJ7mTS@0wR馚i0 xz!X;']•ɇ<'qi-d_YߣO :c{źkMsD}܇L6cj~v4jVܳ-Pz`=lrbUUUӆ0nڬj*`8 Q)&$(֊RUrn/>  QxZ$]&O-ɐO(\TtKHwT`i2aw% 4Aߤ<, c`]U'Dn9 =<~$9H+1=v椷[#s :~v,)&6*ܳcI1Q ؜ rUUŊa:Ma XELL„ah(a$; Ɠ 6B(ІV705qUv H}HEJ'vtwHt-y+lN#B< IQUI|#M >c"eJ(քbԄQz Ԥ{qI0op8;ϣߪk!;msMd^v,.i[v,.iL`YY1+b`U.b7 Gn@j۴zv;MW%m ĥcÊ nJ7ÖGJ&_5QI4BKoG)u[3XZ[zSCd)VDs$ 5I1nYu7 G!ޏCCR 5S赝MOggS@haP^JGA**,,69:<4:7)>7499))'*58:5vܶ4ླVY`YŲUU bEQ@0+ 0ꀸHaXJ[U.I[Ee-$_D+] d*)_GX#ӓ֫{YqFQьHj 9i4Z6`_ۑv=r/WWmIVX  17OڃiAh$łq R;^-6H| ԕ`)O&rU΅TFBBH''AE"9>IvVB$m`wf6okX:}۰_[ۗ (_x/4e,.%4ja̴v"ۇn{W/@0.X -ZP3> {ۃX~R%tm$H z2&m6Ŝ˪b0c#1 C(&L VHhǿ|+ 4C[e{k/!.2 閌[^EtA{ڠiIuq`Ye}v)t Hݎ+ |Ct]!G{B>6sDGSCo!(Gl(l&UM.t䑺^Pk!"WB 10˛Qs1W;ر(*$HRݞmmiUmt*Mi4"M#vY-)%p1]ulk%߂ӝ_}}jRhJn&* I& F3 S2=3%1Cv"rb1q6`m dF"ak$(.9uTBVqAm:*!+kj2`MVdŕUVU,QTP*1$!1qAh{4Fuֽ暈wuʻ{QF [@>c=4)Ny(<8~V!=評V#](ZQY@! RWCJJ[hm7Ad&Qun%=p@!K_  vG KxL,AgzG KxL,Ag fq;r,Ī<Vlv4 ijb:!$B`1*Ll*PmuK␴eSG4)M5'S]wG5RoUʯ)&a*yz%,A*N+h5mz6ITS¯ MEx!A+J,:K3 7,ggrD DU?QO񭷚 TXi:xf+{R$LU1a:񨢎ctK^"JoyHVu"U_>SjL, t| #CUk PNUx0+\Me#>t :^1 bk%ð-VM $v;x<˙VX$B(K Eb5GUTMuUVgb)AUHWV@4@-дl@4 Nh =BH : JpK%#ƲN(Mٷzhqc"a. ґ̕'dML G l @a62 >*^ԣ$E&2J8E=~QHR[d" $\L’"9VUBJ" İV+H(4$FRmS=;Kȗ&˄Qҍ<&^B`x 6%iGRDRH67 0.  }qkCG/d]MmRODURNJփ#7n  \7`>k H`ɬHEI5?_]j@@ D~^tp̈́rda"?lC8\.gU" 20(V荖R*:KFJa:i 8tێPjRJ,FKiҐhDZm'"Nv C |avn^:8@؈@!=@ fZnkD""A|E5{a7$Fx$X[T>vd?s-9٧V ԴX4,I9/I9`` ܾ_[e@>S ( j4Z!Q_>E~_~v[4 3^X,G 7ˑA JLd \/N&=_)#*J!fyY+C[C[+k2)@cA0:=*|\{Yb I I 0P0k,4AhtnV%_D*5kb5TQi '[E%캲NЧ@VXy@ LHѕ6R"ɵݱ5Grm(owxʛ"UUKdTk ƠXiPQASVg! 8AfV ARnA@hVr GF=:9N|dh4B䭁ղ׳膉oĶsXK^IBSׂ,=HVG PX:s@WA)K0.f<(B8 ` ~nF{t-tnF{t-z@nrlreYD*X& EƄ qQH$U5TҖThtQ:į̓D{(=TR(D56UmןTPҡO8UN)*VJQ~Y<İh/URG˜ڻN'w&[d爩=$ފmH" ֊mH" 0^I>8,UU!: *V GQT4TM1$D#VlTyf^ЋFCSmG%I<|hӧQIt)iTPmUohw65Md'x/BͭsK,o J$?/4h)j4~01?x/?c ocbH1> d"c e, ͬ^rכ-L7Y:AY7[&=N,˲MVHeUV5bWQb¸D%% F 6Bca;X.ҶaZb~tn\T~'ȷ6T1a\V[E X!=a)E)(mUiTt%d@F' 5?U졘5@ 4 twbH(6.pUv%L6Ye`X*RV`ZLBlL$2XBE"!% yŻm6MjhJUZftfc? )ӈsKALBZFNEIJi1ZAh*jRO:jV#m8r,Mk0 {Rrr1eYĄzcql QgD d~EZ2Wj/`1&h/R{1bb:8X&JPq1 &H"Ä1q *1$tu$I7()Oy(wpW4T]\g%6Ӏ06yF=ڦ=nS!"sOhS6g&>+BXvsR*JSy@xµBruʢ>]/\ Gjpڊ fz#=Hib^1X\$W!CTt8D c 6 &h:pO\&;z3vĘ귫4Qt8? $#5ءw]/n9i5{{upkNntePTIrר^ VJe)*+V2y ]jM B F;pӖ-*f`""6CcQa6D@ A b*Rգk$̨EJ럹8{R*DR"& ($@U(:K˜H m+hk2`dq# K>Mωڛu79{ JVLu+b{#m~UI3A }ӮN 7^P p FmCY# ,VkEnP ԚZh"h[t:m!)Dq\$:UIg lTJ7Q.Oz\Gzvn#ݦ+p{כABtZ tT$J:@"jYhhޒbvϠD'cq>ZǞ%/]UN oct#r>C$ۅ3I!N&;C$ۅ3I!N&,1i$&UrUdE1m"UnZ@TcADhP$MJڊ)%4od$Pmhfv۶H}emɾcޯ$4Qmb]5 u\-"dNMI)be@>HO>E&M v> mg}]QNhw|mK|/C !\t>DذA7'nlsLVsUجİĴ[89M@H$ E öimjKIR="u.s[ " R"E%߄wZ$%{ Q_*WM|d#s RNDTiFGR-EjZ款h$덌f9|u]DJbWP$t>CF }T,2 TXb ^ ,Yn*ƪH"U@aAT j1@*M)I7mzVJU ~ep=h$RJ/IqK _Tm RxV,![zyĹπ\XTQ*JCQWUqZVgWlnKomg}²Wp 4њu7ۣ^)IGa J!N 8tc^H$ єe,.VaZE-6Q0XtthVTNvGiJ%RJu{v_^u|eo'i]2!y!V$uI*cEp_wWPVTŧ$vc740яKEKhI{ĈZ3V@:v⤿[@Ja=Q[wLd(ixIσA~T<'#M x 91}f:"nr&Yb (` 5 +#Rl$.б9Ph+TRM&mGZEGpEAܖQ?\t.4̻jH"M!- bPwʊM DŽ&e %ݢdZ=d{ Lsam zC",Hk="̘v8 ! \^H-71g! P&1$O#d\.W`"P,!%ZTh@1똄u$ H*=4n IlFBc=XBkh4URB5*CE7=Ѝ"#fr9CiB$x mݯB'I[E-1{N"  B#Rett-hm*Zֳ֍8`|qBy3t 8@sr-AhO `:.feUh1aiCŰiXMѦHQjTi T_N7.G)E$ :j~km5inۭFm({cyH@iӈ-{] ^TE\ZQeo%6lZ%_LvG?i%~ `mG-&Z.1ܐ7X%VI&@*֤񧦄H4++hr TU bEjaUj:ݤѴ ==/ϿP S]ZuZ}d8xA xSҩ(B&2(>ҥj "S:^DF[)J*J470V7wݳAu+۴4II:(dentIO~g4i0wEbUjZDTvDl N #ATalLliHѳmiePiu&dyvCGJuZTTIi{j%=f#^qqy B7pLݰ\ԗR;jfٖHA7D{I*?LZC fNgׁցNhli Th0w(Mh)N&X Aah'؜e1WU ijs4 *V#Ihb#PM7iJU)m{mbY_ ~5 raqG1`=m> xV2js9T+s}ttO;:Z$wkKp{0l~x8zh=8r{!;` 2nOXT671TEq"H^I85Dn71geeUUUԢ NA$0 0tKTSm 譩0?ڡ;)]N: mRs" a*!PdC:>|\o_*$Η:d. -$<9i:CH67 iOJY1`fr>-A$@TKP^,),@q.|n{LoQ8VbVb C4QT\E ΉhF":=MREJN"N,jm8$4")‚OM{۔ )b&JRPڦ5C&Np xU^#,+RD#"mw{wClV׬e*HOik2\}l4DuL"Ib\1˱ʪj*j(F)&HRRofwl#,! B_Sj,iP*E/WWu",||=]O)}flv@s%WfD-U4\TؠZ iX- 0}?+p]RcK,! XЃ"2k 0- Ծak_{ @P,Mu(ݩ׫әԩ@q@qR-@U0XY=2F+7vZp؍@| A P& zT RR9mw^p`3iN>,0ۇƺ'RgVqyJ6)&(9B& =8^Bf4ږ*t:n[WFZ?4K#w7jD|!Z j- )Ju mnAC ni+>,E9RcMܲ řX5kEj&zVMKD1" "!{unI")Rb#%b3, :3SjTH^i'=uUu:;9=\MͣfYƢR`no52}SOTJ%-SD@^xCL6"\UQV*`XEAq1XBc@ ,؉:PmJ7 tBH4JG ‘JHh(bsH `8r[j*JTGGJ 6JeEzjNi}᭑7R01ȭ2 C ɾ(y> =>{zJZ}k[8f=xG%- =LX&br"EحVh`WQU5*0!"0xtZ!T<5}I'R]BXa wNSh@ҶM[=ҤE }=)Jd3AP @ho@#+`XTTupkP [}?J 8뽤czZ1(yI5{IbPjI2+D$$6p!RA1"րAD(bXl6(ۊQ!DJjz#J %|(N#:4=:Qm:HQT[I+iڷL-W}?6:6D*=+ն- }Ϡ V鶁tӦC@w ȋ oOggS@aPH1-,-5>&,+***'666?<7(&67454);9967ʝݙŷ,ٝ+P|koR0keL\"P/P2 !j5"MF)^3^שh!aap ymwGϳ DV۩ʖVS Dn./Q m3Rh@;fQiMV**` ,8 pHUg$k[RlC[s%^ր }fG؀#YW:uP TWdN u+Q7.XcE6E$a6jE覘3^^DF7QͭEU4k=Ӡsyh 8Ty$H~2kUU4.xPJ~eMIq\Wgez_WgezߨFUXQk~:3n$?I5B,:V+u&նgg"oQ$)fl^64π'<fJoIJ1"hlqW6 P-Qc?(u]Α8=&SRIjQVJVZ1l1bŊvՆn:*0ˆDž1aHT[RTSsz5/WyuH]IQiY6{i #_)ҴMEj !Ip/5jzv*C#<ه]FIe@QqvO2\P` '{` R+ &T:TDMͼ󵸧Rdq:Y\*TH5)2GKMztZ94t@kM@ȮJuiqSְA A՚8Tԕ(ZQt}c&}.]>s{P@`W? |Qt+1 y>q  `8? *3ȍz f(j0IIVժ|!ZUǭW$MѲtBLL,tܪJ9p hETg۶'U%MGUUJm'XK+JNGzV&?:HX3͑f 3Y RՊ @FMT7T#s]D6=+RU*|LR3Ii­PQ5&zi&CKtСg-f껊uKHx{>-f{3 WJBb+V.("G"Al&+G5IZNӣV Um%Ca#i[UM)/Cm4iTW]=`}q&Mu/@J/  NJi63j`f A)BEEhLy(Rv6ϴHDS)ԡd2ZW;ȍ >-EϦ~/,ut[b=M_X(!̺c%ks5j *b 8*`P8">OP@7HKaʝX@WT%[-?@ݺ› @*MTW"(؛ @T[uf!F!ck F97$QAFhjώHt*U$6+"fֳk$.CCwMξ5=m$boLΫ]F"Ƥ0a 9 6YM媆"v, ST PEq I\\Bٱ `T,1*(4HI6H'ZeGQzՔ4E>WGNWP SkiX@ 44@5IJ7Bfgy&!%Us:}n-;l"2]x ՟z] 4vj5ܰz&|u=+!a1 :fJHXBW2(eYS W^$rqS6f 6EM1FEDTq(&HT@|$1ߍT5h{t%?`Zt"IU U~u4R)6%n~;Z~BWU T㢕GbyzMsmEOydw: W+nuD;VÄ(Qz,jS`yX: ^Z R:q.Np)8g!jDeY.Ǭ Z+Mm(Q;HϞ=4դzTZv*IêtnTl~/U"T=#J=_r5Co==.#8lVT$ EHn2YD,IFӈD&VUe;C&bꞑ+:ȿWAWܠ>jMl7Qkbf? d?ᰜmrrr1VU0ՆnͮATL"pTHDʄNlDy]Õޢ;d^nj>3躾:-۴Y{ln 98 Jf-!'D ˛Uh B"GM QGk(&&&hu! m mئ}%cHhJ\4T. _mp=X3>^*wǣH]VuRVIHxsghå9% ^il״0vԫOd 1@g1ڕbV~FtjW%[_I!eL |`V\bcAŊ5F XH!={vjnMGWHs>ۧFUMp`O ^UZ_^\lQĆ(gɇ`ă"IU -A|w{2S D;fx )_ι`#̋̀KZ@S`4a#0'U/-o9|AYQBmLNxcvB,UH+ZE"*VŪA(Ah0L 8 $Tt"Vh4;I2JDZ(!8&2ү=(* @T*g#MMZA!`;q"UGfP$%h6ИNݦZU1_M@k~JRLZ6ggJ§d?-Ťesv,5%6˲Irra8X!U*KT*1DXJPxۣDSV ĬpMđAj6] Aeāί4itZ`I#I>;(!EOaA B@&$iiu4**lUE`D(;Zи8tTI- a8C 4SLZmdN5嗍ĝ• CQ "@2ɲJbB,RUUv1`c1B0* bo<3z["1W6CWɳ"mILP?h4.Wt^J|څrUq ;xqx) M2v6o;02ʥmj' \ߖn }(lE0d!VRXUkE,t@1I6鴍hCJ aSHѤH{3}#$ªBΰ 9.֟ 4cnITSUML#ظ*In#J=xg2V oGn@g^~*;aW5P-"s mˬM\"Pq;`uRXlN,_rc">75˗ܘH7 8*˛\Y6Aմ`M$ĄQ"aHH4D$I*hRDRӸjݥ5 VUUmAq Kj# MD~gGC( i:bx~A,U`P/T7bCA߁xeh7Zb 0\\Ɔ `~Q80Jқt3ѳ"圳bUUZLAPCbGGbU5P+XP|Ԏ :Iii"TU[(*&4 o4 Eb6ҦU\ԍu[ T@6dp%5IO9 7{ߓ/#z@4ZoϟX?-bB2@t|أҚ 4hOGl/^Juw&9ޤ~ֺ;oR,}`Ŭ,TH*is N*&.A $ZB&UmWUU*DUD*Sϴ8;69w#o*PAl*Ǿ`G,_)m=WGV9zRmR]M}ev#C"JB696&žճ%u%d%Feጝ9S9sjŇ3vLtj?veycrk@H @`iT8BX #E1 VuIMIS:҆pʧ0|^;N4;&5LsX MvBj4N7FF\ +E}9 Ƶ1fTrC%3J)8 -C H-ϩ)zM;cHԻ3΍1tN"JNYbbkDU` V49t4D0cFS$D#ij:H>;-[Zpg$LJg(An! BBEM-UTTk+NL3Tomuh{OL0l՟4GqPMEhHپ&8j]sd>!#Xq^6aHV׬kIVGŪVa={tj=ۦtzt:jB} - ZZ{'FCI7˨a>Vi[](LH! ѭhMC$TI7)h G1@5$; ֿjZōS6H+T)qX:6`G N\n† Cz,:s Z1E/hH?&oVU\U$/f *F -c%B QT񁭸ݩ*"J5MGW( PR騖:]o:VA4NinmȜ}~,BbJ+ZT8Ol s4TZh7PU'JQ#@4yu4%YVUbbZ0!.0bCXA`#΁a&΁L$(դ{@\nL=5RvTTnN'݂T6*HR)M5t*P[ߘ;P,I.dJJW\>TЦ̃/W[wW 4B؂l[2!U <(g k0 $*AEU}dLޭ{A.0,JXU0MC ED TŪMCa Htz?{E7K'祭 a?{|"I7MTU UJڎQ^:h*^#KDʑjFdXSA4ݦtMT>!/q 4A 3~C7r6^ouhY_ ;0&kHRglB9l>+TuzY۽Nr61b6ErhGQCEUm6 h|(&  I,z{B%QZ:eMc.o-%LH\!܎K\j:>B$.@7 iF+/Fi(ЩThjnKzljW .d_,v1Ѝ.jE;8>^x̂3'{!c9A ΁,Jb.Rmbv, >Q;aԁ!a "mmJttU+DT#B/)p>J~ҵ~()N ki %>{u=LW :IsAU+xߴn' M q *VlbڣDCbqԱtDtCAh:T MDCNRJdW_},n<"$~-*I ңmJSճ6)'6@Oy5|% [EЈ`;CҖN'Z$~yv^\- V"FH_i/S )8V+# ^w&'vX|8)*!LGA*j ŮQՌML ֖cR+ Rݪ-7TӶ꒖nytlpLڟ}!mMѨ&Ք* n")]RTEBPDQBCKkVcIVR!AIzW~ Ѐ h@x~H 8zCbB8(3ɃMkOggS@aPd))877:4)+*,395>@577797;;:+9=-/..+/796-+))*457*5:;)(''&%(44453467^ʙ'Ρ(3$ [y2CH,CEV^,DNB7Uƪ b :GUla3ni eCh%8mtCj4;ANnD]'wJ*t+l {|* ^R >׻[ pJn6IJqa"HaD x60xtjnU)Ѵc2ߙύ~ڜvu8hD}  %N,g!R(+;xaH_P$.=aE@7 ֈU(Xc`-bU0 #Mz4 ;@EUHiB v} 4ۣ4).EJOmURQ VMiJU:H4Omt^_e%TAWmmVCjH `hk@6qFUթ{qaȟs6j{*  Bu=^4AQI֔&y׍I+*WUT QLGCDZcŨZ+6.j1XXѢ4#m6mjjsFGuWq>tV *K)%;>6 #aOV# |cDŽ0URUTIg`k6] AĶP$&@A` ]vd-ū =:m0R`֠i0C)d1*jOSq HC XlʑU{FE*9 #M !T4 EHؼ'b!&A\V-DDQ1VAЎ q=Z)qgi2$JI -WR*9_X  E@ `PqaŊK- %mBht;)^n;R R(8C~aG,Tñ$±w&Y`iBC' ӄ!N,wTUeUU1"b@Q&&^'A #QE  sythK7%bO&ڶM}WhNCNPmg-qgCf/ aX1UAUUIGI:=:&IDt4AUЄ}P8p3qI:&mA oӆk@"nׄtWp\ T1.I佁 G@NM" U=r2'!v4Y,˹XYHmi 0a:64mS(TDڤZI_)0.ސ+9 I;C٭dMKD)CI$V.;3{g7'{}a-|*cH|k DkiV[UXN5&i+w7X: NhoĐQ{1zKKd)UQCX1fq a$HALHF¨0.PyIi%ҠE@S:'B{Ghtz"!ز^kFinlOU΀yϪjlD5b1~89@'q)t{V&7T')T%D!.p^qO18T<W CIz YzG ,˙K(*V*7,VGA+-"aLDcc:'Q0XJ% $z6MK {`FE_Ze:_ *#`T:UQMAH jp YOB-]6uޙ*I/[}1#UnSe>JUW"*RJd4rGBphТw1B( QT"$af;l֑SHZ0!RρB D  9vZ@SEg)͢EJ,kEihNEejb5R`NQD9S@jĢvIIJU{Gm_J@iJ*f8ql:E!Ȍ I0Fѭ*ԋǟ ѦgU[=U_ZGzɖɖz @VV:MvTaU[=wvWC ɮɮC–fh-U$qQaa&9JzV.RVhձ] rAjVS.l8&eYUr("u %PX8UznSMu*URin BG4*m:ID۲:PJ(3;O;hz^B;}k@Zď +B:dC4{.aR-/Ƶm*BS TVmp2I\%BSs\h|:knO53 leLjq;B@c6ԃt-B>>sȞFc SαQ \1\Z c QL*:&:M#Zmhz4=(U)iFъii #AM\1ڱ\pz}9XqUZbfP.Vrt'3-}tW镍[6FmYCȦEVsGEZ 3L>\vJF<vJF< o\rYUU*bq4쨣hTT5F5JBA2@\4@hvIUiJzF{Wc{0,!"2#,n6iTQ]J% ``nZA1OgdHIRo06h@8G:2R\m_[Z;䠴a> {PU ^#|mӫϲG@/w*z;+ X ,-uUA[pN'\U6*@H!'P% 3VB:EWnDl<H0*j(hdWBCWPXPt B鐦Ym4@&6ǭp3=aaz)"HH `Tn6垶TݯwUVT-,M⦖gqSBj:b,v{`8T^&Ez_*m7SjSj)`sR&QZ=Q6{AkpRĉJiN+M5hҭ17jD*H0FETMJ[6m( `dwDhRgPM$JEDLiJ:Mi T(Uڦ :*F$Jc;})fl'B[vB"&nhuPqAD&K$&%=IhiQCORuF<TQ6Uw.Bȭn7 H܄ |_dJ[#D8_^L݇@M݇@ݚBdJjm"x!mWcu~L#ݶgFLqs'uɭ2.F XQ)ВJrbbB?EU!qA7MQҤm LӹḨ\[kX@VG[zta"DP.B1a%::&M]ijDq4vi|j )aU#VvJG'J+>Wq(!ATId[O IC,jXҤzT!6>"g AN"ag 0TARxtk"" c50=z 'sJzz_ѭ`TתMk榫6!i1{IuJۈ(ɎKNdq~8?[ @ӰXJt4*MvuRZ*N80 H-!LtX:EB4hE(=zѳqh}j{M$ERU|x@C#°1H'ڢ"ZLl6@UVA jbŪZALClF\`ALBu3mѦ~@|⨘(*UՉhJELtBJi[cL-_дt@Z"*Z(Di մ X Λ=47eX#2˲׋jLMK{u<-M!?F6a瘽!\צO\צOQdP X-p.wSahm\o޸nI0 FDX%8:Re?cEH#&DtLlk)F$$V5ׅ(E7VLN4P1D٪U7'd T07DkQZ<XN 1[$4\_+f"ajīI~#J>nz&I۴\ד溞p7)3 `XU<: lqqa DSQd $)cmhGOmnU=T֪}޼FPR@$S'S I %SsThnW+I$ G"gDhhhw6.1 1&HFA< H$P6J$i[$E3vnn/r \(tRkBbsMRnRLGHm}7YUխUZF.| 5IeZQ3Blֱp-';Z-mQA@pv-DXD(,7< WHx0ఘrcYU b(UZ G ,*-8Ea4q!Q+eّW+.nt/Q܌mh塌$!O4iU:QbG<7ZyD|Muk@1EKSaј#ŀuյHxI\," Kp9]+҂k~Xуp}Xуp}Y`,YYTGS0 F#(A|HNU5i{,$PH'zr8X ! c#XִUIM} <,-"'xg;a/_ ԇfK$:4Nlݵ ܔnHӫKMdu3#e8*!wQ Y>i2An MLkgC|gsRRM@DC  cCG"qqA:6ӨV)m%=ILMj)\/+I|_ S5ETW|\1THp2ӪTN۔_\-ζn-(fohz&4[q1 vٻ^ >[]srw{P8V]7$7kL$˛MU,s!QLa G8* 0J\LDaĽ(mo_Ÿ!tH)|CU|ZUUUխ6EPa{-UnQ@vt(h$\]Pi e,qIl6^5xPDwN(DH+{p+"va7@2KK۽Avc {?A] {?A]򁢂j!llJ6e" 5 ðV0,6,&PAzM^X5.tt^HHhPhIE*ڴٹZyX–h nSPOhMKF6`B>9w6܇όAvKmuRMS["Т+FM=J<1i۶zDT)Iܯ\ ȐWw Lk&/Dx%p(o,A69 {ЄI6#h$}~ct-.̪<3)5[\U;yg j2)$&kE@V '1q*5Xc MEl` EUHTtҭDx4B&zv+8N)+Þ$ 4nI!t_mjm:RU#+ViJim2/; ܘ`҄Tz([ǂ>#1Tl+nDJf@@6U4B@C#[v'-L oHHsj3%#1 (@ib%DMR\rH*b "Pu4 b1-&j j<MTUiJiQ&H9QU[H$Jå"̅jKq~thvDJ }=|)#vHQo֬P,*>8y%i~ЍBk9#K@fxuͻ=d~hcQȠyު;1#j=Eǫx!HWz& T'7g6ʹbULSLp0Z@V1RtU5 p(P@\4M Q@oC'TP!\po#KBNuFjCl?*QnNh0Q'@ilmmWqGgU ނ[4ÝBa+M ,kzAR,CQcfI~CX&HYd-pkVod+?r޴ e'E8Y9VPvGF#q*.VMY:vVkR Q4=oH۔jMz˄bZcoiMI6cT5GS*T43rUD߃PBR4DbmaE% 4?QO^P s$^m5@ǂRXa>6N>KCKܡ^};Fܞ.ř[ cU!ř[ cU! r޴9U"(DIvݴl:h4&&Uoj!B1(G#>mM7U&ޢRNӴ])wkGSE~ThN)Tn\]EnTԓ<DhBuV?͓RJPAo0Gv`C\ɵIB$mz] ls V3&.iљT9|T9|j&0 bTSEk2nn&;LzWlg뢦fAZxK,W7D9$G9$J="00[1_skBLixOixE @c*ڤZ'EY9P@\.5 Q}lP** w(LQMQݨVJ۶ѩjټԹx\SG"ҷl{"X` }my-gmءۭD9*Q d=bBvЭ\@#VҴMmZrnqZNJa:UM)5<Ӗ'rE bQ?T$@zvh LYU%kL'SJmZ !J-&} \]ܷn2r2 PEtiɫN i:IU6$DuOlVmv{V.DWO8EWO8K5XfAbQ|B`%*6mφJML"]XMuJm*ܙjލrL5jFdY+DlX\\e(VEUUk,(`anbՙ[Ua$tl$q(I)]$3 kє(!Fݷ:_4{H?kH6*T:Ҩ%mi*,G(Ҧi:m[mF[V;L&#/CU 7!16>iǜv4DZ5[ 2DѠ 9aL1yN~޺Mە MN3S6{nW+49LTL(kD6&Yv6kXZc j0b8تt{4YO0Wa*ֈ'iҴ醞Hi 8EH7 4 G/ H3XhZEpgF JSmm:nTRU@׾|jb0FP~nvY Ue5Gz+VedzCRBm/`5Yβ,PUbX(jqt4Æ٥JV/VGyF{#)WkP *ы_떿?HKh&ohr|:BwliP{}F!ݔ̴#!]GA(@{c{d+XhLLѣiNC=Wo(TގXĦw)mK C.ZC.Z:e@)(jGu u|\}ЏiyFPP%.jQ@3S1jmjՙ\+u< ~\ A!A!:"0UkjT:*i+VDW}Q&nE[1@}Tw>p NA"N ¸H4gDI6I[OzmvV1܆-^|U :a˟:E*kVKl\X\UcUUl6:h:XMQp4!D@tgU$ժνl&BBh=;e^׹vң>auN*IխTP _i#"5&@VGo#!@U0?j8`;Ȍa4O2kzeS{{_P)j@mlu$&TȈkADEZTkTTEgRIZgCҩPJ=NJI[ + j5mt5`*[bK#@Pn  4D*݆T[VjTWUF[Dd|ӯ=Ā{7 JӄߋH773&7f2Sk] N>ۭb7ۢ*ZmQO~,)leY|r.$WUQQcŴ q # 9 ]>%աNVT,qtzW8.n`/#MUa5ND$ @uQ -deĎ8( ̷]J^g'pa-Q !OggS@waP *.?)+(+++8<9*+*8;8786:549ޚF7{]_kvfvuH|L,$s.&hqD-VCpL+mXKsvt[=(x Q>C5w>]MIB$o t4I4M4&!B]pVlMw\`Be6qݡh㽷l%&WG%p|4ɰ'?%j0i4;ghv~A9$% fq`lrRaGZn6mmn40!ƇMUTS]DKh6燰NQQR=JKu!mSHEh[ihREEK6Su˨߁צG ǭ4ֿ_wDʧ#(ճV>>b>-E.K&@60%q>-ԭGOwF]D]Ȱ=]Y"# \o襲`$j$$pʂEBmXU9Uja VG9$I$Hc)^ar< bÐtKӈPF4,8%@&pVjT7Q^YtGn%:M Yi@4=>%޸hn&}NNBؒfZd!IBEsv$7yv@-ڌwIqfKڅYȄ tR]0md\:.C~ʈ ceIU9檪jb-` 8`m#ݴSiQEINЖ*E46UfNXRCIU\}TJ$Iߠ[Neh=WB7jKNtDI:0 6;ӍM[e;,ׅa^F# 9\CSa{3Γ檅pm˄IyL#lyL#l{`jJϗ$^֫xSe 23A MMuJӦRTnSwCx@HAecW!~͵K' 7nni\4Ni> CmIhƐa2  {`YcUUը1X5CPi5ZG$8hDFҶDii:*:4Z]WCJu[J-53nڎHBuBwx`ي)ѡeR,GȏND}O bGs{sDRuTc:V,dMcaN1fZ CnGY#8wGDBl% 9@rCD"H/@3X9YPYUYALj`iV\՛QeϞ=5ANNGGUiF8'n75! 7ݞfُ& U5O=Yena!%pVŹ;Kِ8^$j{vK (C9djw AO5udeņ#uaB·aLLs tÉ$C~ 2Y@1 uXm3{TO' - jJU*bGܗf1Tl-uzUk#A`" *x3A!h%,v\Q^h(/4g@*j"(LBƥuެs4\W]p_W]p߲R)T0 ꑶJ)f_|\ׁuhܞQSH(k(dUuQY%XE#=Gd] xfhVVuQVi(.׾U-J""Ul߃!e~@YTQi "+؄M \KWvz0FlӁst"ѲK5&"V%m  *a^kظ0 ET&d @bnJ!ttQ|[~#PQTTb*[l4| @*j:FB&Ep+rTdDSLGSL5YTIPcE'!_HfE/\@_@o))P-vԬtңiTuݶ3]R;QEޭR[)2d߽} ^.!Y,1XL; "t `q4W:m؅h*`L; ` eRfZ TSӯӳgJ!qҤI:N${}n@3Њ 88€H5=ZUYSt4(y^V<"OH*h.A6ET&aLЀTAVnFcٴ'LN.Ϧj'g[$)!XJ jEHB "\ M~AQ4Q MT|{uT0Q:0phKN/JWN$4HW4I& R{#DPUAimD8RӉtNdm:Q!1:jk{fʑQmpL9R3Џ" !D pV6 `êʹU( FzK^*ƦM馫M:EӦЍ$:ݴjic@U+‰C =@LEfMuJ+(Q/VTIx5ơm+IUiv4PY?dv| VqͼW#Z;1௯\mEbG^hlUuQjWhlUuQj7 v@ljS0bq4b 041$ DźV5` AFPՆNLXg0-A%쾤nnJT!iUIM)TG"` E0$0Ȑ3[dp3e wRaZYA5Ff" "յ&4 aofu8tȆipta{+iO [I+f6YLrsR, -PS lxB'*1q"@4MWD5MҶ%jKo& 0 kǐp#JH}3UM Ma @=ui =M϶)UMI t$$~6Dq9PZ 4 9s@+nPRAs:`D]`!v~h~ov1ƢiVrbB/h`)ظ$*XRG"!h`b"zS)!(XAxFujD,ۗ)RihhR &BCiMӓUCWNB5* >#|:4JTa:RRN.&6]ёi2sǮ,J,g z 쓷l0l]?>efk#&Xf6L ޾lzJIV6LY)\@.8U+X+* a<Ɗ1~ ^n)UTMZ"*SITJˇk=3)VV*iY/ĺ>zE >.aDDbMn)P#@\%)^1s3}~"'qXE2Ge‭DPx[ pR56fb1;TM)ٮZi@WdLY5UĈ`Akfu4:Zk&=.}qrڽ[ZUnMS WBoi.A[U (m訦Um((ҴSNRyc`hp,?DH+D'#%͘䗨mi[ULlzU&6`⊚ A`hT0-"8İZĄQ *EOZtCtYCfhRtB3qjk)":k.5@n*VFlcxݗR#`y]nj^YUITϪDrTJ t]5cʊH֨ J>L*ňdފ=izk+LSa\ҳs9Y=lc$*Ă jEŌA:xH TS*VGUt|1қŒU6@* CtZ0X!ݼr>/+/ޜ&6wDqM]%I-Nc{>`V@Y5h;WQ ~z]l)Rd@HJe lX8fժbDD¨G ^HIDSIC祊%, tU#TY ~u^j°`t 0& aba8VhEg+[枕Ql:ÉtEFj.!?FJ%בN) 4ОBh~{{;W :5,9OstְK>oBmH`1$L QQU%UĆ1q1QKq L@(VpXNPiJKV%%ʺ'P5=/p >`[{>1A3m應BKua莕ۥB|OA5ܤ6 ڼz p>MxHr,6e&p BF# XDŽaLlh$QGFTui ]g'V2?"jA$KtWv|dB,@*60Hg&KnVW>OlvI):=K"*PKiTa3o;eYI/ ?"X%*vyc>xĒ>A-wx%$}Zٜ嬬9+Wո0x,&P$ D N b0E\MD3iQWfX k#|ͬ, ;tU&I!זCBKaA.RAϑ³_%d||5(t;O,? ( MVh0*!z:&Źյ<\ Z;VhN-t x{<n]:sHipcc1ƘcUUӴAm*c&CBG&m6=Rm-*N.^=65V %*UU+,Xڇj7E;m:5ͺh5tekwq0_d" }Gt~9oy*4́ݭF.{{~w\c?H;$\V1j͊i5C@#qAlq&CHHT4MtUZVU(Ƃ..mJEi+9%Rbm\k [R*tJIz)bh)vËotׅmwt󍅮gnl WT-.L榅@[ϙj u|:5ANw\L;.&fr\% *9 h(mJڶI:4J5Җ~j,Ucp?s7ԠD:v[ےŷ*PX®\־L}hFQX1*B`JyC}9*0 "Om>2QAU]m܂3o찖 Թ}>w\SA"qM1Ds`rY*VU1VQXL$&Nl#a$ c b8x% eJG3t+9t?Ψ;A"}_3]=Lpp!zQ.6`U{wǺ5#H'U%!M)$,A(-;zd>_c ؆Ît\bK}t F ֖2v\};.g ,fcUYݴUL1D(T$GqT"t *6CN'&EST!廠UYw6E?q/s1=nM t~akaHk!#|n֯6ս$2쫍ڶ[Ҡݢ!AhWN@a:Wq.aI#72wcIvq% dw 09XUUhb9D #Dh $N5UԇKWq1Qs[M)X  J@X;妈WIu%3(OV͏DP ]bS(cԚۿD;3gvKK=n/߆w{ sOqLVd˱br\U(j(xØ "0%*i鮪V#o+[mUUI)%ꇅVLH zc[f@s?Ě꣼O!GbV9D"ւӀP!Gr>Fݎ8X$yJ#d${\iNۼ}=^I)<+*0ۧQbRqUt0Q- 2ќ%T~Wm_F@ E()̵Zz lybQ #g]AOd^qJ:~ki#>wbML|Z;=޹kb"ڑY0Y99\UUUCQ;PJ$D#ALIFhe4DJz3fPñjO\+Em4$JI'3ȹk=-SПC`5jc8t۹BIAʼ_9Ԭ㴠 kJvnt:s}ڧeS k:vZIg[! .>w]^J7w]^Jw6be&LąC8фD1$(M4B(mK[HO E&A*K,nH)p+{}X@hBH@$nl2:Ys|^(EYfiF sJ׽ubh1AmboƭSܾ5!4.To~A-5 ~v$/]}v$/]}6dUrLbcPС,;xb 09qR-Δxc_:;s# !SiDi7P$Iub#_R ىje(D"AHRVEWDJSpUsȠcvHhhZm1[>5P02"4f$M9Nn vl+R~$%};R)[z>`h,c\U6n QLb3 (=tZҭhIDCN2ȇܴnpax"t6/FBuGv&[g ybf Eyy:UޘLԳ͕(!( ONҰbV& ԇRDz~rn[]}ȧm vT͹N c];ZnI bdmn &r͊qUbU`:ZLb8jS1nwPG݂`X D*BySn+j:'k.5_$ex O5Ӟ@HQ-9 C{ RR(i ?]m*ЗZ4 u z퐤L ~\Pŕ\B!~=%3.P@2aOggS@'aP V68*?5+*)+*-*26846:(++)**466+-/55394766++587<8,56ּƅNBUUANNjYn~Kq[׶mm (eP[(j6߫٤Gh6mWyU=vYO$qÎ6;# $7+5eJBT3R2lr\rUUX+b#FQ*!$֠ cDB}E$m:Ʉ|ut]I 2՟\ TiRJ C`%:ۦR=6RJ~j\ 'ʣ/ eۖRR8L+WGc<}M_a Jg-1 9R[H(9~Nv{{inJǵ# >wDX;"wD,rH]Y^b媊K4% h wa XA1ABHإHhUhPmWS*[퐹5]ҥJ*d#붪RUlg]Hvr6"l% ٫%녦疷ˎrc9B1OrduLύ bC> `o+vb EBvv$mDT+H=ڈVX&f\⠂ a5I0:BKVcG# Zխ$QV)YE=n":ih Y4DA! 0Ԡoi~> q{ǀ+!OOh|By\6ܡD[Ks;09upv$Ȧ3ywNv-!M;rV}nye1rUUU6V5D0,q /c㢑b$T7 tUiA)UPHZ6݈ҏԆD=!Ƶ!Pm4 $'` H)#Lsz'nj"z^a+bdn+gք֡N5s1!Fӻ@$vT2L{DH/쨲e").'Ue̲6YL!˒\UUŊ5VP1FQ#il(_KdT[%D-a$YwnQ53b.ISvu%Q:F|ƗRL(DJ%Ƒ:6R5M+D8 !99MGNi9tU7s,LB>@ht/=^2*@PQQ5䭃"Vi]hg.j.V gJl E2Њ FK#hSmP~nn۶mq7N4G>ΫOj)DdHbT,H%mۭOIK_)!@vŲqyA\"Fٞ ",*Rb󀛭*VU,bbZkAԊA 8Fbq<ooDR:B~Jӥn&X(T" bR=G7n8Bi tԥ\5T{XZD:(EKpCUٱb禝 K9s!$ F]mU!&"gLC p-x6ȗ)vTqK)Q{A/(r~g7YOb1WUU5A|0&6B1qD U$ b"9."TU]vU-JDx|SͷV2$r #CJ+6׍D^:ХzI{9P2;Atż[{7* ǀI`J3LW;TʣEPlvrИa|ێ@N>=L,f1"UUøH0 "vD  0BLl RBnT:U0[6qVՐ&mS:nK|Ё?`<^c`R觐ū|:K"¶2u!l [pC[,ڷL<΄¦ߐ[zGw [hl~vdK ,V}vdK ,V}6˒61瘫*aBcQL0J,bM4 *a0QG j۪hQk,UUUհjGhjŎa(B*M7UV"Ǔ ˌ'VDFUf䛥JSJIUPq#Îs&X+pz\Ē}yP#/BfPo#rqŕG '!D5"vԠ& d\>0I޶5Y IαpX:Ŭ*4,EMD2!Aq8F%9 6S4CU8T!諡@PmۦӳgmPMMz&ME.mjڴJ0*yΟn@! N56to`fٛ xܫBe5a{WS" w Ղ hl r΂38\+ntB}'Ɣ&vtf} ݶc5M+H8bVXα$P&(CcEDMBa PJr&աRwk*?Ze @~}Bvp\%h-\҉9}iȵ~%VnTA ,!o֨wa w^K+#WOB$7khX6[V+sO6!wE4^q6t xg$sX9VU'P (A(q$ *J"#iKJE[ -0e-utڤT p:Gm oXvQΚUrÒ6"-[ox$$шR344l6\+iaCV40 t"G31nbbU4 +`tT榦zss(i+zv;NZmPUJN4*Ku-tF%X:]:k] Twwt'4IV8yoO'Te#9ڣ7 0;9hU! U=M1{:ܗN*+~!ZTއEZ6C2 jBYM(PP[AU4a`/ɠZj;ד&dI{{@U@XjTTr0*JCɆ90'T(H3TA)QD+-۸O Dt'* {RT#DFU^'_i׹G$u@ \ F5B[WEu==(`\<>ɉ3>ɉ3~8T TW 9$UNeMľpBC}ᄆzk @fZcQԌtF'3s~s>dk`@,FywtzhDqőZq?F ~zkTd ID TtT=CNܲv%!^"~mOᙉ,21183Ydm@T *%5 | YU)`cԈE :X@-t iڂYUBAzqmS:FGwu* H:ItNVF5Y^Vn%o/q*PD-t[n&)aD M eB:Z6Dße M;Q;Ц=v4SDcdE@k]H~AJl GPO=H m%kA5 2@ց U[ ;DkUԊ8] -Eu*S H7UB]V]-zB[ TtJU Uz6πT*:mztZDAb!TI ?l&/HZe{Gָ.{ҩ$N)&aX$Um,D$0#ͫVHICtJPu*ta6<6$nXBv݃Ԭ1И% =HyQpe$r M!1bVRp0Mq&ŪXTD"(LCH I{@x ˈ&MZVIJ*ٔPMto NMQb*EiڪM5E*ؐRi;ݦ6#(@2<1@%jRiӶMY著`#Wـf7}v)݂9O \ -cfeLP@̱ДU.BXQ+1L"4Z :*%H *3E*ZXFQXܖW1xح ݴM)I;{5iSmhR9ٴx֭eeHbliC3XR4.hp]論z_PRGn@*\y u]$f*JzU"j"1EE щt~MLU'uE2Uɺ@ ĊQEOj:Mद^4SbL-jzHdeZV`=7%]T^^ŭt @ jT吊V@{!*l,Q^,Q^ͬ"AP&*:MfUJmFTYT"ei ҏ0mb}r$M\$M\| !X$X ZU%Mhz/:}z-Z1 4Ӡfx21F")&pΨJh6a>i4͢fQGj "ֈ*Vt%zGKNk~)˻g48=bDӊdBZ˹p##d1S`%9eb*bUb7C(;% c8Gbc'>΢E wG^@D]H}aA\$> Aw[@"(PJu+aَ|[];I6n+Q-1lwC4dæڛXFSc !Y[gM`k0Y+l^\ &_uJk!1D+NmLbܪr4bD$qHAxU{yHw"EC%VI I鄫(l X$o_AIMiB4 443G/htCI`@& 0+T=~6J]*CH(@3GG}y Y =%г_2{6Kb7\1˹jZbѸh0 DElAl0 B4==)T%O <~:Tzm[j;zS|$\J #7n N]^ EEJ3UIrt '1=ЂFsYvP{GU)1a!KsLP%zt 0Y&F)l6WE`Z#0usU8GkUj8J4H|h$:8A\L(VL$20e#'ц"MS:kRoC"jK:Sui!1n7+>Q؀&J8p6ifb&Ԗ~\Wu0@/"ñi(ۃ[J` @ ]p17^jBXݍt|N XM)iY$b$ H!ʼnFBZhAh\|4HggT++HY㪙YmP6եu4Bvj '!/a{߿D8ȺZQE8Z_hI!TiMSDm*,oA2 =MBקm244J բwD\3?W-'!kGR$YYVk*QV͒rsIP (e j1j] ðiw[T-9Jvm!>ao|6MikG/cڪt nU- o8jAOT6 r 7IhiڶT%>iCig)7 tc yѕGzfpY @Nzk$S:#ɔȺRFF2bQf(ѳ:!~":˜2'xjTf ТbNy1GzcLS/*Pԋ ԶIH`8TtRm۴gg[Zѣ4a6a. 䖁F$tLR'&.ӣ#] pbGD@(,_(%[PJq&2R%Q5*2T+@tjRi{(ӯdݬsY7ܶ .մ*I#ji/^ mɳDm[hDDcuEcum,\`wX@ 0zMZ IOzTlN$e0L[2I6m}$jj"2Ȭ%"*TӭsES2v D[:fwLfUhmBkb7@ |qqQ8$]^;m#,Ej3 Bx4WѢy岯cSMMi[ U999Hb5ωe0j3Zl6&g+$Y!U@%Q&QcXV(K^1蕈A($մtimƜnEI`~Cɜ EUu&QI[-ZYwSV @;(vSV :@n)d*6¸H+2iq`1dy׋Mfa3XɴƎgm}k``G- &s۴s$D`2M 9HB*5%@+/ XMeDV*(X"` ji5PC(IIW^o,TheQߘD~.i!n- _Ԅ QR=ߢ~q9ć(T4C.;=t* cQbisڧ*$nDt9w .!#y!:m 4kMTg2>>`pͿL{>\/ҧȚ2! hh,oḃ*Vc( V0 S5JvJRV4Rj1fЪ-aժ3FEuUZu#-s6D!UQE)QE- eid$? }˙nl3>{ C$3=y(٠- ] &en?+,d[wΌx՟X2dE v3`db1WTV`05 STD&@TS-ڔJRM$ԌZMECeh6=Rڦ}?3LBU4VRhҤфl}t'{0C03W~TS7 RsJ5/coq`򑛮 gVǹ feUjX1ME3y{쪴0pLuj\HaӡH9V!HF1 j*(` ӪTtURH[)n@ =HtHQin5K!3' iNTAڦgt6BJ۹lZ)Iu?1Z[V@R zP @ԣip'&O+Sxfb#$$[W$]>jj<!1w>O/r?zȴdn+fȫ"(` DŢE"j(Bka"al:A5mӕP)iC1C&-PنR%80Ag+MHTBiӠTE[ ǀ#`ρ ʃeCtC*J -8Ƭ#@Z5OggS@aP [gk+(*)(((7:=:894.0'0:5888 -+/+-9<897+)&3:6))766&695*1>8*+)*(58:;49547'576X`2OC&v?0h\(+e$FPTL7j,Tij1 Vh46=Zݦm*J7t$N 80nJj^T*0K'*^uGT` Qb5Prs@.=M+A\Ta3 HQݤTT`fǣҴ8}4/u-vѻQ}4ͭ VT7j,j-F( UB c9'+*S$ !Qn$UDTGdYy  }@>-j@`ZcUKJ)Mmkf4 MsCnͬSU("bD%ƢrRƮ֌ UnM$Mn܄讋'j k獯}O h4ugELLJ@Y1"bB"T.DЃEIA-;}H G)A4n*$) eAqy- DR e^ S8ĉդH/YTCK#K la;ܲ6A&B05bV:M4mn_MAh(/B^Ij\$C;#&MvFL>J0l+ C PAM1J:aoNUw$BǁѸPüba`1FeANHP %:eE궝F4uF[y3j!0#DB0 tP,$A UJaN摨nUE:) -UyаV~Q;g> kC%jTB9Kbs9Gʛ1cDԈU5Ed@cbifbСq"bÐ0T-4[{-+Zj*N-G\:Q/KKSퟘ>_ꈠU({O_ #^;)@=;bG精DUMWqtʝ$a,mJO0}- 3^JʧzxpL[-%$J٭Lӡ"}rus$&M5wwhp`\BMu579DH E 䟾4>4jv'^e2ַjv'^e2R(I;*gNJrBQ+Xkj3CALzh&ݪ*gZmۦiCU5!i:UJIR` L_y/]i}1a@6hpbu@Fut ΰ!z9*%*` NB M6(h߾7h frwT}})A-)+j)rx7w JZ~_YNBZS CPFl, `UVQATU0j1bb#&!!QB!n6]HSH{QztD=zT9}N RnBQA^Eڹ5vrK5DhnJ4r!2c# en,~o뛎MvJC5VM_fp&F϶ HwŅĽ;gb}$ )h\a;dp(Ml^3MQ/ynxk"69r&ε2TWs砺;+6 pnbwV. 13AD:d9|e9|}*PIJTB4-!*!;گnm6)`KD:hsɲDRV0FETUQiETBt"\M\.U{CR0 S)VݥqOn%<ݵa {FgPteBvVUmŰ;3YխV5!|㔙{2j&.hu=&.AdPP+HznӴg&>ގTٝ"UvY 2@EPRmt K6U1RNڤhQ|K;TdF@vi#m:Mm#֪t3TҭTR鉃~j0\ ׶ȭEd_)ZU1QT1qAhGcHhQWTiڭh+ȵrgv ȵrgv PɌpcTs$ h(* F5pj(j:CRcj̍FH'BϨTTefZvR';~*~qt7 .DH\h0 bZ@9X[6Pj nf2N֮{&uj7VvOX:DbS]cC^5ܜL+y-nN& Ďȼ~&M-Sց٠pX`bQ+ "DA,U6DbB:-hb1@<Pd$ Z:Cpfb< CH4I$HR: !p:4 :V4Gt`juTѺQLtD_ 4hM$^6*IN9ޥ ~%!kA ۇVuuT@Ԯ>:**vܴA`d`YlPPIR!5QQ"VSPqTEbm0A ó(TZAJumn[ JxW"ՉRQmSMNM4tuTR!6ٟN(*м[)Ntw \Dniڶ 鷕N4sC "(xx@L`5 v h>MJ/q"0MJ/q" $TA Lx$I0&DV^eZUP2P4AE#F, h&iPæb@TATm@@jTnOE@ xգ rJ R$ ]%*P:Uht jzVNRz0 T %A:܎n͘ HT+PT#8tm4ݶmTa-ͼae +˯ w\\%9r\\%9r̬W*ji)#;"NT"c`Pb5bbLup0,""6A0v- h!o @J`І@$F0JL(kA  @xl[1v48I@u` $ @'oh6AU: V.D82>oGM4**6U:i(.&@-}O4U]Kgwk_VTU+jsNHEK*(Bn;_H@6;%(\ۻwCAuk SZQ4v OQ]8 !sdLW_OW_SQEP XچWJMJ7b`;a\ bukE]t\ @܅a2LYϘgLj 2(ZQ+żzU(4aue/dߝ#~ʔI3jLk-MtyV+۪ѢDuфAl4P\[x꯭U<[ $Юv`TNNmJR)Q01 A&i|ЭJb\ۄm\7M(2IfAۆ4=K]ۂM[t贴Ҧ/b|?U4|_T:$Lf TZ?G(B&$?E۠Rz6*&Uپ\+B DTCRIE+tziiUA:mZUnAPϦF:tK V6E" IeYW1RY $1F1HXhN&& /0K@>N3O03f S0ykOUnujFj, )0l#V*r /L=7nr$l^x+K_IKb澝m< C& %a O92<vy-`I^嵀m&yգvv#)oJXDf`1pEf Jss p#d˨Ҵ=PGW(OR@b%RQ:m5TцDST%QM_Zqt/;w+W״c0 Q;d'3[ j&յuӻ4)4$۴l2 ,$4 @ @jBAxus-:U<Ҷ Qg=?{PUj:A#[-yuv4=U܃\ݬ- XYbV;dDeN9NV$5dCw$5m[Ӽ}wےC%F4c@SFJ2*bĶhNm]#MIUUBh_<lTfP,%-sYQ@@rϣظ4~ f^޾J\z ){y+q-nPVhrk`UUPZ5ZTAm&ƄD\()h:$I)iTm*=TJM zt@*PN9 T@YwmH @Rzt)RR/n MV| v@uRT5-aJU$B8nu0N0Y'Q縡<LR9~x5=nNTWbMsf˛RH,Z#V,jX " A J4vZ-NӦJjQ":d0 Yo irߪ`:xDG /PM @Wu#mEI6!JCH6{FD@ݨRKƂyCbQj4$0c[ll> =롡)pWlLДY~zP&--2 "9@TVE,X5U@VAzjSt{6UX9iAz/$?.IQmA"t@אcRR=Rj t t[H+P@^E̞HC\ d8NUUCQ' S{mw*f`qě@L3nJՉb 7pcλةaG246Ķy\mrm{< @PIHfq%q3PU*Z%Gb ֨5A`017ӨabQTCgi;DM9ͿXJ.Ur4{ RVt+5u-NDCn#URǹ@щR -4. \TTj)zO]6ffGNv1"_sǪ Ӑ$Mr4mUo e11VM'hkdDӺ:i]7IKZcUP <iX Gh,M)4uT2#J*bUaXwɦGAg1H'nh/^(kdAߪfQ)*uj*5JMQD rU,UQQQcUc,bT&nӕ۴ZB ]Ei_^^"@֒9j.}q##o g9O PҳMS-U:NȫED0H)r@mGrdTrepFG, 6!!P(;.%dhmB(+ ^A]Ưt-ml|h_7'͚J$UD bT5 w[+YLFj(|ݝ|m>Z5zJtS)Φ[T}bP>1(U(PUQU彠J3dWt 5eZ-1wGE(DSMFn`aJ *&Pa 8InPnyh"EƟTV^Em +SEA*nGTěTT .{_VRN >bY^LUSFŭF [P:]UQR= bHLL\$D4qań3d׀lkf[; $3bS-T(*0xGSMz4gȄ6RPȬTՈ"U5bJC)m!V={4ёI۳&ng^zsR\ lGgL Q,  ۪pUt:Mx=gǭ=Җ &\ę`W7q&YS"IPe*Zl`ptڪ,t{)($IO Wdل Y6-(5VIsI>t Mg>]U:щ\mfoj 2V1FT*FUӣ+FIϦG+(T7դR#%Z)F1 IvIWQv rqdmc9˹*FY)@z &:KNki*Հ ^kT(;ĘDo82 [+@'bHup/(ҠUO# 2Uyh@A$mӴ=: WOD mU~}T<(1^OeZO2~j9 #q檀Xq`[zF@@ H#0 zmDZ$"igFE"j* YTC-&i# 8 ġ@DQ1`.8 bh"bj 6P4P@uHӶ)Vh@u t ?T*S B!j/1&ͼ~RRQPfk`h'6J%*8{BH? y}l@wPK:HJUUi:ihJkgoN yƙ#p@e3G:nE<6`$nNVAՀ"UULTU00 * v4*Hh Z)$MϐtZ@\A"!!UҪG*@QUGJI!$M/MWA @IӳD5 U]UգSF7D Aazv}mv+riޫm ]J$Ϩ0р`2>jf^3ZժprDK.(|b0AIfCS5B R'u2(m pr`&lRJ@Qc@cDQ^07>苨Ѐ).WmF,@C4 !H*-9EEmjMDHBɂi3}8UR*i?P@J)MTݶKi \u8U_[UVnzJWב"!Jg9LYw 9LYw e jemH69o$VEPTVQdD bu]:aSJ Ä!D袔($EUuUÎڼNTtDd´ 2:Vu!EO1X-mE-*ՉFi9% )tVi6v1e {RI+)J s]kD!l~6R쩧ac/ ȞEM E) kEA4x$*֪EAED@5j*ģ1M4ED'DF$=T bx>VmNjKĵμ]M[MQ$UDCmUmH)UVh;W&.t۶M%I!h~KZ<|8 NG ~u$U6~`gv_P-,A Q O6$͖5,&H*`UEEEUQ+bQQ6 G&A: ZEVN[ga{gB҅"R6jQxRVC[=dn!s}>iY0r^ m24݊:EuS Ѧvs*nnIh 7&H զ.*@hdqTMUacruFUy"­syIr*jZ[lf0lau0Q1U(GC "_Jt{\e*_=DŽFh7iԎ-શGZTRH%&ͩIttҰ/&~&{moD6-ݢnjY,H2Htm/aQkra!JnDzVTm(mR[.Ya~LI+-s [U`͊GIPUk= +814Q?G護F14KVDGUInTns ҅|=DQQS)"- 4Z۳ŋ7:t:&I\-3^TLӄ4!8-kCR(ТbElmR:mUytux() (cZIu*^iiKf$֩xߔ֍,+(k"d)bY"H*$V(c FDQEmڴ]虄.FĂI46kϟ6ݸ޼~B[ BL3Dmx -X!v_ZH#3!.bH^<";`殁 y!Z==֐za$ 86YgGP? >~B898!oVtk?!7=l6,:٬s1[6QEDttTLi3 n(:g*">0 b8(VR)*TѶjq)8lUy}v!ېGуPJY PB oOQ cs(*StIp"U$M>qcU&mFVVn($>QvE*B*c#`6nO'CL OggSaP 9E.2--.+,=8++86*((%)-4676,+/-<>Mhva_vP[ .ˎ*~Ckx1r5Ĭ"Iv XLUhU)qQ TUj n۴UA$J5)RKEJU4 b%9R3P BE%Dh݂rw~pQnM0ԲKD7SJJMTI 6v}b`aɕ嚦8(VY^!-tingf/Wv43S~ jJD$2f%q&I*55FTjaQ`30]AQ"*h*NjڭȠ$l#%kFTt"JѪHG'zA-\:[ճD խ#4nD45([j|ƄoBxivPjJ|zY ƌ8dn`Xa6}kch65l#a*NFy$,@]%I@DNFDZœd\@b*F5ƈ"V BM4fF@KtG϶izt uY_NC4:UM:v$0S;ME~pEUH::R$ҪyLEG7M?qN)%4Mz0ufC R4A[YN(%qF!t [6dHpnh2 @ps9~H lݙJ֝ɬEݚRQP?O+dZLX9lTPuFDi: ܖ%'pДNgdQ'N6ˎ:qgQO5E`A!βvZB<`mT,J X`0AuZirW:1qᰭi$S$<'{P()-Л"/RLt=%&Jk Xt-$b;h!$1kEmdddf79ف9+0"UQHLbqa)Dcbb(miӑFH%DP+v X[wâF|I.VuM&*TTUU $a}!dz*5"\]m4RU" 4ѭ1v\p<y}\O݆v4ӤXH>6v K,z0֟t 8%/<sr L6I9ĬbPEk 6All BbbChP0 FIUV*s&3P^P\-|Q4QN5|-R}{H /-76.8"`s8P %n: }\AM0h"-tHy;J($G&o@0 tK pe9Wy )WCX&PLDNϞݮgӔ4)"@"T\, ulSH ރLBEJW#wP (tj:w?*0T]9i@}%" uhiJ㘚*GM(CC(9)6O)afMF+"ft 9Qrud֙C{Lh)WGƱh}6˲8ɒXe˚0(ј@p$qH P4>4DЉژ IVTi# ʴ ~>CbQ*+@MrB+"QHsn%iܡ)eK:tjTӥR%*K05l&88J32%z`9ZD73ds8ԕ7Tq+#`LbVUe9jPLS$bc0Di;mHNEMAY ң} &Ԩ%|DžB4]='aMzG8N/HCĢn$Ĥ_×؄?nYΈ谡I_"Zwt7%a}T(wjnEX-)=}Kpb~=xg 8 fIVUUM<&!*"9:HPB%#j:MjTh(j%٠ըR$H Q Hr{Y):*VLH&އMEg5sRFx&t$j]t8ASФxF jE ݶP2C/*5!~/tjjA~Nx=nCcRaoB~RٱYb1.jLC:F&PO Lks gUV媐jv+l"c' C"@XME[M:Y2=Q7䝑AMDž#? @ǻ]o5AQbR R0/{=`ϏBgj ba(2;ar 0+]bPqUZ?+ạ:7`;7@9H@Xt;'Պ~.,KVI.+W!Ŵhs6b6bp0m6 [MZjKH'E@1dI3v(oYyD5@Tu%:mRe~m1o(5:6^%sMqen“' ֻ~#Q;({L -x{ 4iҶv5lƭV*1XMꠛ6ZbM3͔eZ#0'4t؉;bFnڞϴ/BqDMZy5YLoI̷j1&!!ޒ7#SOUDM d%d֖m fEXU UŪQkEFUlE5jJC'M:鸻-yw +8_!=ӏflG5mO&A6PMi|vYNr[ETyh`|Mꥩ%@'  D0e]\l300Q-SUbf2C$,fymlrIBdUa.(&jU ,6T*v1Ac-C S[}4Usu}n v+miƓj6i|Ň)DۦJԤC4/p|O4Am/!#B Tc T+]=hl5㾡 @{KsR`~2%U;t2%U;u IZV ȅpajU  jEpTjWLi7 Eբi Eetw:bpЊhFvHnJZ:)J{]%):5av~pPŒ!qm C,7g9KZ9V92PTAvĴZ EĂ`v5T X2@rIUW:J.Ě~.ǾR$Mhz6@$p%[쁡l1HƅkUV% 'C.ҐnOGiybhb2AJ6d$4QS#ߒ $X&D$D@Tn0mSBV!!$(ւAT0b삡`U@;6$$0X\D[47n -DnR*UzCD մݤ.n#A$&"QV!dh (m:6}ǵd4mt;>Zb/8;M%JT7䟽,28~ ޺1I)lnN䘤D zf.,IΊ*V!@EVUPGu4 5TMH`#R`Av7[tBl!,!lʝMREBUX)!GQ i[dO7Ѣw@^";%x'ʬ*R5qĬ}p+Z]"4Qg =9&o ޫ2kö@4_Ȅw]`5,j_ BmeY˲MkQQQ5D"vDcB"Nh{6؄Λ"&$%>b"`^G&︐8*/.h舎HJe&TWhtQ ezXC@Q/ hvaEJJlϛk`.VuŁ^j]EM ;ZW`~CN} C6*gUXU bq-㘄10h6մmۤ*&w ?)?цҕFÕvgȕRIRխnmQJ#iڦ[VogUBX=^H3ilBQVIIm/ z-4Ȱ] 7.k@Zի 4.u7\j!Ј9)!ŸeRyn*,!,Ňxs$^XJ4pde90"5GC+*+c#(QH@ ʑ cM0"" @[] y=Q*t)UI6CE[p`9JMJy/Z裧[l2#ON)PkDD,f+s}ݸdqrݬ uqEugpLmNżrUUh: "l@;ąѣt%HvU'ڦmAˈUjF RJIŠNMs8,!EУT$ΫݕU"h  [טkh_}ѮlRi$l? %Kئً_&-L^‚X2R0Eqޚڼ!`/ [֬v Qغ>a `r9*WE8iV1 Q5-H$(FNu#+JU*UĬ3ޱs}\NӎJiKJSd_R@I22 901Ȍ^x({ӣO)IRj[I%q;k88@z_|/9Ӹ #>|JI6f}5+}RMdd8e, H1XAADExBxbB1X[c 6E6cY$ݖr+SZ2l iw]ImUxcQN RJGh, 뒉fp;@ d\y,LHFGkT줯I, 4#O'*)85n(JKjM۠6=lҮMheԔEZLEbbEժ 8TEc F. mJ )vCNEQ1cq0W2PnӭGA646~YS}yکA۳I>&#neB9:2!ӞUiKmR_ 7vKl=ӹ˖m]սp>1 en+ծf$JZ]ql\2juYٳQp$76MeYIVU8V4SI FAD0h 0UhRmsŐmҍjH4ZA:M[RP ݖ!9H=㪡Q%xF)f.=ҭ 8`ג[ҋ1ΐs3`wα0%Ş갇5#hq*A@n]{@U~iUC]z4 Nh m WfSUhQU H A4u$GT*M656= R ~'Jj.TzT } }+p-m44RF5:LnH_mGtF.Ӫ##K#y6Qe =9Gzi͝[!&]@QՔ}PVHբ-[ܺq!ʇ3 @A)>y,#"\oXF*D8#=d;8看UW+4T0&j "N4*B@NRQmHT.4!w*hnіҩjS .G:QUѳ[Hi2Dl2 Cv[{ww+ ^*!:p"qڠÃ׏8dRWJ%iJuT*> * EZ\[a:`:[ &Y6@Nr\|`6"V„Px! "Ѩ"A.p_1J,;'HFS|VZlQN$@W"^]>Bœ5swo\6¦>b8y!,%TYҼe9D69&NIJ UtTk1\eWEy_$T AJp @W1\8& "D08$6>tL`9 !QmCF=FBIbHҤI[M&4ҢM.4i% *mRniMYc+(n/Luz"2P*նb`sMBX0ay>ֶ/& 1[Mgec/Tg'ld;} {@1M ǻ̵̎@ev<`+jVe9*֨APa d*&#nUS陞lBѳSϠW*=F5%\߅0aGΊh 'E0⒜n[i ɳ/2UnP1"" ve@ Ip緪Fb89"--X}hú -_=&0ƣ~~dgyI2$|Hvvޘ$CwO,fyTVCPSTDƄ #QG  4m*]nڦx„4Wi[RBLKJX~ 4mH5WJNʾJzT HѴN!U@TڦP6<q\d0{م:4L =ǝ|F@@.m]v d0͙E!φCv9Ϟ4 {4|JΰAw,9XUUb԰+bрq 6&0*!,UN% M(m:תikuqa>7#@G!?OE˴g'Zth?"\Jp;AEh,Eh! !uD Yh&4Cc0w@BV&|Xc+#r?-kr¹641-}P222EY}+٘,ʱ\F pl6:h*դQWފ .c:G"?bz7hN\du&3L*3ʹ3DS:tDvHtg . ndIFcm@1j0V_djw*=ɪHU@HzD!*Ơ1~x?}> Ш BETTk.TzM\n76@E_ RtZH;mhVNBΖb!gT{dd`ASS]mRMr,A; ܇w(T`T/4ۭ)o_;߆99-T8V8ޢP@U+VT76eO6IWun6iI]rAL 5ӵgͭԔqFTNBB|]z{&Mm\_! T݁nT@7{@Y*1B9/wy <ۍAv#jP@offRV&{(}D~բt'O}2!h *VV[׭7>a Ud([l'h4HgQFR`* .R lCI㗮_FY֭ɈxB0#`8 ACݳE'N5iNө t,8fSAjDR@C¸mUIֺ۩N*rxj&- k eK8s;PyeንY j] ^Q5"LL Li:NhTV D#aCI҂zMg ) @?J̯'TUVtҤwIq^)\d炑m &.@Oܤ]|8Y-fe|`ĭ^d볃% ;V1܇ >ЧDj?+d[y_0gl 5U`3 &c&5XΪHDQk`j!J!8 zt TP `AD,n:T*U zѣ* >3r3AA6f `+x4ĉCL8UM$!v6$ O5Zl}ku!FJ^Ih~q/Z7G5&ڭǽxh@h_ j )LbasY)WUU+k(5IFqQG >BV6׼:*x i;6i;~X*TN3ӓB0V*թt3mާ74kc}^؟n]#84GHBw=Fy(=vۛ_)51(3a8?>s^԰.E$7\5>`8ʱ\%Y*REmjѸq`EDq#8$JNV[H6R))I">$Phw(fG8.Ҏ9Seu;*DT:qv1Ȥߧ ۯ>FB94 @V"Z}9_;{sBfOv:@0CU @Yqy R OggS@1aP qlRA$<66=+.11.2:=9.-):57)(/?4ޙDEz3'G5>N&;YeTl8XTEMP**bW #Xa[*TiDCUIRet$KHse[o:׫*.I"4-ilMv>&";[q|ðWTAwv[Ƙ:V ZGp?dge鑈&߹߀ ]l쭴3h ɟi4^DWDPDWD- nYβlMbα\U"+ZkTU#jUq6(BԱrXUi5T`*8ER#e;; iPXm+J#mH~$9*r4:ꦿ`ގ=BG5!jb^>~5qՄWvi{l%p1K4Pe 4htհɛ ~Ʉ !ZLL!z$-`YWXXcQQj,h8#AԊQn0ڦCUm:T . 8%[,"=_s଴:&B2;*T /u;i# cOUITM4MCes0w nr87&}l&vwGb7m!+Vg( \l†N[R jBGBPH}t@C.YyTP1MDlX,b5`:!O 41PWҤ4Qiu**D'w~>SǏ#ݶIP66մ -^BImEhjI׌xʃ ѲL-dȚAnoe d)3"ez%)L'; wK i+nMY[xL%٫v*J(W ,;Uj 0>@ƱqB+AR-Lvzvii+jڦq `uiN^/I:EMCx7 wNA,ک[|?R"p]"S#j:Uo>wS`I&TpIσ LFg'@x}ݒNDۍ'v'5Q8 P'*W.W(APULժB/(jPVQt" b"qP1꩚miA nQ_v/WX0_pchHe T| p ZZVBz(`Y*x7I.I&xqA߈K5aU[8C>8u `h[d0)@WZ;;".3D؉`-U#>wiU0Q)@SXM0㭓Er`Qmڶvm׼&=xߓx 8mNWt],I.bIr1xN0P0Z4=]V:q+pJ|QnjmۣG$2h,T^V T ~EB𼽻"㈩֡\$Mz' i38/B8$(ziB P0sݎTjCt!G(ALHGj& rFЛ{FЛ !IVT"L2٬u`YN Z1X`X1*"tthzTi_J#sH (J+l*EڶS@TV:Pg'}>BcmU{EPnݦzirڪl-Sͳ=)d6ʴ}xgFݺ`A \LG9$2(6Yq@Nf(CMBn?(-:F C10 <`ªV Y]E9>iPwױw!UܹIP$M4N9 t&FG|>Uǔ=G[)=SM&<XqxRTU1Q/FtsQ\:s&2DUQU3Ǻ:efJ#gU1o&]sxCz=:F\U^USS)Xz[SQz?# KЩm+lJpvR dzb`ŚjStH7CIIb%!R.hUKtmn6cEa QQ NCڮVBMNURV5YddHƑ^Iw} *V%m bKJ\ POU$iS{Zo;w쎙2Y{OMpΘ1ȢZY&k3@IJH j b`0FRcaa7HÈbB90Fp !*6.S*J%`x0 OVMtPThZBT'lEB,GW0؂8] |6hڢiZ+fUBic%-/_˵; CNSDWGH tKCk+_121XEtQ@!^ [::ʕJP=rkVT?YҵM=uY)K,g9 ɪr!bQ0lvPAATb '&V =tTSҶ*EHQӥ6@P V'"NȨoKvO)* MP=Һa*g$@4>FuJxe <=)d"t&[qST֭ %!>0eZ8^ ys,X=(R͛e0Fi|R k u2J74&,bUXk( "6STPq0 #J DATI7 QF@4  TRR0ێ@I3|&iQo^i4Wd/ v+SŸ\=?6 3]~P_|a&4D SP! 1ʭU 4ʭU 46€Ͳ891WYVbb85T0페Ű-;!ѩ6Rl!D/z_!/CϫC8Rs[|_]ĸ!Vu+_;yoՂ14td ?͆(y{#o|7O99/ɬ$&,$d*B@5 H`Ǵ:::X ^kZ3KTۤRm۪Uӈ@uۢTwf@mQ64@աغ HDT"Ց7 ͳUMC*"=_-C2p/y%* 7q!RȢu>V׵ƪ[6 =qh4iLqGj֟"> @TfN6s2NQ5UCSzH *F 4т@- ܬ- C*FTT&4h/}J;^LY_D:SnA @Vܲ>!ivjoK+s2X0&Z"TE&4M3.zVU=_H\qovS[m$K‰I skY 4j@ѦN:j{UDj0B$Gb# D1N0$)`itSMUiu"ґ&!.KNJp~ Im"+"ibfeY\eYUU0*bQ5jDf10PƇ(p$!@A@ i4M;w9K6\X bʕ"&:JKǥ\_iZ-#;[4o4YE}ýy,kjȴm?j'E>h3H Jκ@F FpF k:8b]A>~IAH{PD`le&Y̲,٪١i8X6ذ` n11GD Tׯ_Ww̙È -mǢ@'g W|?$-RiMtfGo#kRAtRIDř/;ǘÑtbk {3C܍XhyJ!ձMCR;4IQ:MZt(QrV"Gp(>4BFA rF;мQЭ}g7dWeU E$ DF@a46"H\LL 9 mݞݞt;Q(F7i HK*ݦh!-tiM%LJ?# |+LYޟDeT=6*PDyLE%Tx>/:A H`la-///\{񲠩$eBn 2 [yZe%4cD:z} 2N2!lmrRW,V!Ynb"a4lVQ:6@ ZJA۳m"BE%jk}uvqnG,Iӣ[ ʩȧ7S ] OD{4ճUaaHހ (e8I|R@ UϘ{S >zfQ m-g`VlpeȬ "X 6p1+!aV2@XlV@UMc(*:-**F`i!T'ci }IBU=JvM,JPa^C{-!E!G\'@~{b<h.FMMU%cB"h: 4EA! ˡWEqҰ(r >@(AuƐ@^z&G Ng"xa.HpE+ :ezHډ&I%9@ʹr$BA jQjFj:⠂V(`CU\ XizFRhOE:M#xJUWӴ U TU6m'p\NźN;uדmڶGS/*?!Ţd/]/-[~$D*)5_&n/F @CM;#.Ǐ\kh]y&jf.3G7+j?ޒ}HjcBH("ƂQk jwTDLUhKm mGY`#@1 RTH(' x((7K@t+&QXR »:ڦ\\B$Dl}._iۿ He/`53j}nf]Q&}4eMd~Fc"nJqFc"nJPHM3ŜdUN6U`Z@@F BAA0ME[vRTT 0c g[* hTt'I7MC!'%D%;"@kQ`Ex` RDmvDN5"jHXub!CtJ?{ᤌFdLqP%Qն4Mݒ4 QV"97W ^&5J~JMf1dt `VJ( 0F dY^,\QPX( $ZESUH5ݠۣBNEtz @S6Ҧ2*ҁ8tQmt$ISov/q(p'rn*f3QݪT*[M|'Wj`[Yn@SՊ%T Gqas_cP$ddزeX9s.rbUU5bGր5VQx Pi;"T_ʗ@ϫ x>UL4btJGڦH-;v`y|.GE˼UoIg!b G A (oU/it5&ljv2#rq'yje*h?L!Z ZAH7lneYI1VU̪ *6UQ+c ¸HDI A@ D$M`ݻ;'Fvް{@q qL#_mBA ' 3T? nT 8idrPaX Fn_=)Z_]Ҩ2ɍM8h- DQ~McYkWB~&<"GڕDEMBӧoʲ29YUD#(Li*vh-T$iIF!(g&jPOqM:q% .vM$(J E91y8֑@r$1!aqAl oKdյA**=-]Tr" ;D3!g [mO;H>_Lv~V{}tчftf+WOêz.Ќl%j2j%8N,ͪˊT*VU+jUQkp46N#ն'bۍ Ѷp-@DEA) RzZ^e.j44=NU4Lmp6I*tHEtJiR^3M1,Ŵp5DBY H㞉,ǫ ;XA#jiD=P~{JC m?t{JC m?j*@D&[I.SVIV3 +EkŪAq+ITQ!q9&.D !uFDQiөf/NeUZҳI( ta D P,֣ h"8(>wo@8?GN; ѐ8qHuGpGNȲIKhJ}Tl!L AẌ*kS7y<A-Ht@l$w2V5+7Vnܰjdփ%'} !7` qV 4oVdW 4oVd-SY 6#CDT޴,bUŨZQQc#V9a!P| L IϦJ]'z)-HJNj2j:]- Ņ0U5RiC)`O;; ``FAh)hE&|q-2A*K,-5}K1V:=ݱm0Tʽ*nY4(Wmg : 38N: 5bՈTD]ύ;tfNo `P2kTXTx!r|hBh/ФA[`8 z& +h"hr2^%M & (93̭EϨ`Z1&=:*x[h#_ +imKƆ nA}AP_<ʛD!$:ZLG)U, UwrݿDqeWGTz%ӋuFɲ"!|J(Bf&EMXcj1F1j1f8)v!ݞNT,I:bZs'C_бV$HIRm[%I;ҐU^ܲ3֓bdC~u Վt Zl+T;;!.88L[(N}q)XUIiõs~wQ s0a8LF~zÉTO2v8Q e9X媪J0tԡ#9tl||0FATUtztPji:M#UuJ܍Lzմ( PvvJ,5%2 Kaq{U"N"T}=JgPsH*&1LJepEbK+N?myd&Ҷ7]ͰI3[*.i%Eز,@^aKR(+A?nxؒJJO7i LbLbΪ˅TPt46UjV$&)V5mҤSMhUTb;GF/PNEHE8_^ 7)h2;N)D%v'QRW)F@׻GlZMv( q:ZŪIL qWXa[0c2ra2Wh/Ƌ^kjX d_/z!ԍò|۔s9X&!\(TX5h$ Ѓhb%G0ۡ D7AtB1yy) XdEhFQ%tJGjɋeS O[-ڪZ)wH:ҭJs.4_˭=:MQ7AM"G5D"4 oUchj鴮){`vڕVIX:+Gz{?tQiY7UH9, A5ahHHn[4aBhӤIm۪"zCRmSNs+ w$QiVI8_yJVJӶjDr݇r1V-ۗ䍸Duq lcM+'M&c^ڥ5>Tڥ5>ToD% j,[Y.g+rDDZUk (Fllф[B#z# HTؚ[馿>ؚG,ߐaAѺc0UdJ[ PHBIU} ˘@)>]#jRQJ f`)*]#R*TMvl^p CW(g* _$Q@08H&Ap U+Ak5" ( X- `tң=:M )FUn7 AiD!IC)ȃ'oɦuUTU-@aəTFq #Nlc/'=6MYiH:)DH0 GSiSA]kר!=RiT*ʡ~FL@ &Eġ`lbZASYO,rr4aZa"\@  FE#(Q+bSP5b^h%`1ej %t bHZT46HS):M*MJyW*YDT%ѩnZ.qjWiJTȻa}4\WWh.1-]S+E6X]&{쒮D`KtiRGqЩ8>꒍qDG+\.GqKeB>O)7 + 9EPXPCG+aŢvPxy{`A7T@}t"}xnak#jx8bb,GCÞ3rn:jQ'vZ6Ě_F9 ^O&Y N]6|JGv&Yp "%]͟ 2\"E( 3 j|`$9$⬐b P1*jZcQc3E `E$4*aJENUbbduCU覴Fn*ݞN1"juTJPZ +4MIP@:e+IspuКk ƻaT;2s `^%`Ta@y( A0p\l<|AMD(Q &6 C"bXT1֪RРH$* &bp|kVU )t*T7H*jJ~5zUvM6lIpMNR*!mۆN +x ;ʣcR},w @FIVRUB xEupTp#ΐB@>ڕ=$v|4vep )9=%ZBM>̀ؤrŪ Z5b0F# jZ,a5T00TUը[ )NAУTZ0R$hNV@H8%*#hl\4.Q,B $$6HZt&&+\FS `oI0u@*U8СAqqCCs{@Xsb P'OggS@aP#C&15455*+:8(*)**+,;=;9:4,:=ު/-+SKhҲ9D -X&[7*dnb!U@,VAp nb84fmJtS FD܏;SJ~ LG-@ X MJB۴6TDdmEŽ=wsp1?yFuԔju#>A%l6m^DF_` Ӡ\%2h4cVBuo~=W4:#OHfOMΈS'R}P(\IY,UU Xc+FUĊjatSJ*]մNuvj)J#:-n%!T)Q=mS)!N*TE=ZhYUMUt*]`}@hohKo?*P.^e?`j ,% m7m\─cI&z8V:`NpHka%^S2xnYj )a|H)jVQ FbYZQ1XQGQڪN']Q9&j$Ū˨ׂv6u*"Jg46mbF!iOHT!N_,9GT b:M9kͩEo"PLWÂ?8ͧ.zdRVQWGpiA}|N`UT V:oDBʮVM4_kw1m 4MWCE5'u5;_8LZ ndɐ!P5ti6hiVV%SwƷ\u:v$͸I3dĭ2͔2P,bQMt:mDGѾӴ~ɜ䋏}DQ4@fԭU!@VꙴmۈVzP)otY~l%$jL"i\n1P+(ݔ4mB !VjNU翦:N~$ѨGozSI,j"MGTg!DSaC~D#B&2+ +"*Q(JiKKu#4\ǂ]ǂz# Y(C0E5Hi:MꆎR[I"/zPg\ۃh]ۃh}\pBkׁPK5=KvZթiF~QmHG$eK:h]\{ G!v֐}HsX3yp q.,h@}>yWgБeOͫ YVIJMB-Q&@$YIY6٦0k@Q+ *bbQ(jHLĀhl`Ѧ%M3t^I!B,Gku!D%) ҤISq"%JEv;)I&hM`7I'M5QJ A=(4ovׇp gv46wb?;\{8;Qц|['H5Mj꙾_&cIQ>K 9=|Cص M,b!VHUPiElV jE #LTҴT@ӣ'H)QSRڪT:=jDXLDHmݥQPxRzO[)m ؝:N*D&B"t8GU`:JjNvq׈+[0Q'CqA0X녥z1xPU,:[b Jeu5L/W EWk R9MY\UU" (6w&-BP'(;yGVS{=H^\4 \Gr+Qҭ$4NFSMC9Jq[zg8DZ۵[-:Pщ孲EN411. QhPC8þxӣ]ː-dc@Bv$I ̃(~x4Et|"I]i;<":S>]$=`de69*WU6iXŴXij5͊@ND;2F-Eҍ'"V>՜ש*:"UBa, EgEub@Uf_}04 -n ˇT6Aȭuc sVd%D)"y]6aۖvf>xS~i+-M6U62o'RZt'Nְ9-mM5 plq%,W5@ {)ׄ$Amȟ*4:Cs :!fѢcF!GEwvaͯҸpEi{OgZ~>(@Mtk=D_-M,60_lp 0( ~w$mDzyUs汈#h#˫3E}jeD$MXVUUXjQU*VSմ8LiT5EMg_|4hN W R{GD`+@ V;NVS])A7WJ50@袚.3hZ҈ LՁ@PkZ.x'Y}QݳM'YBWQMwb:|;B1Eb>I7ݸj\U 'X0 !>ш $ l#|&STMK)tT9/V1~d0(R a0):~v\\ZІnBӔ-bP x 2ݮq.l hzK_4i +'&V}bM4d#.֩!`~w$-/R 1G+e~w.0iVVUPSDL5GSP0-jU5L+ML41qFIhEф$U>wy˼]שPU%3چ@;H%=PJZp6)BѱӋ1kzv`v璚ݕ $"t`iG|KF l&)ppnlt\ wfQ~yURW"/JbM9nrŪGBhLbDF"&$&TVIYH(Ipeǫ bPaҴNW-iLETia$Z$O[UzL 15i6Хe`2^c, 6BF~Mlp K\cw%#>=2z{GN^;#Ӛ; veb9fPbbOL  F 8H @F*IutRAƃ/l 쿊P6)I*-%Jd|}H,ssrDU0!ILu>`Ft$\Cۆojw9G4M8Br6ID,!An1x0?09 xy! {g7q&˒UU5x;.>P(0aML˜F t, Paˆ NRmRᠾ鸭"W̒|YYDCo6@ճ*WLZ#eD5Mi&J+{GaMXr5PD}`87Qp0=r3H"(m1#*fף4>w$6eܑ`T#MR|̀`2\9A`X6Xm*BD zV@VJ(%*Ig$*TlM o%WHQm9i D>.}`[-{F]V4׭j@">nب I<`Z;'V?|n° Njb3]Isg ʱ9*ire0r>*^wn֐ܺ#v۴Ŝ>`0,˲,rUUCՊi bXi50n3D%D[|{@MNry6 -o \Ҍv<X0]`Z&.W]1ЎOWDu y vXTO@k>r8!BǔUzP>*]Pݕ瘎vSbSrmYjT4|,9b6xfV/Sr(hl D|\BQ8ֆmvr\KؿZLV9gUc\zXA İ͂4X^51S ZxBa@ _/mDU:e \Dp]0bycѧ.B@ړ )$ܭ/ڥmWW/^ȅ%ThYBKiaDbnG/V֏muG<BvcUށ]Z;vMl}* hVyVO_7n@@ @e @B`Bkuo VJG.@z Bk ={ %"E^E8߭{/wp B4{" 5\Q5Bw>S:l@ @Ӡ'B"(&D~+wo;]BjLBjLd0 4EEmi4N۳gϖ4ש%w=(So( 0 YK֨SQ xIjzU&Zȹ)J%i;T*J!R H1 CBƈ`mڦGۨTTm'ӆdMnGQ E\MPu$@ZMV$.FN{tGƴ ٻiuI)4-R8hj!Bb1 v$$HNIl*]jB"Wą( E ET@B Ċyv C|6寧:h)?IY'Hrp8!hA,(eStJt42I,I,nz &VbaJtR]q1H CN& zh=>}B߰gMXAhR `LX %bEEmImhRjSxjK5u=Wۤѵ@^$T>릓L Sd>rK_EʬVRO%RSA`,WT! (FXk1F ƀC%FS4MT۶m+ n 1mjTm4Lz|Z 7Χu qK&nbV+ƪ*+K<.  (P  qL:m*=mim4F(J*{M۴M[J)坟E6թ@ sy1 T|ڈEcꀖT$Qh:/8P*ThXBO|T9U55K7􉫵#՛xI]-h@ޣ9n cl@Z`iBd÷ ͝F*KK8||KZYs*\Unb( C$%pFu[=PmtCG0qG`IM@2iomms}{7$M499*4*Zt8^$XnLӾePڙLR 3O%22s>D#k0v]7{w!la&jpu*A5$vUtQã 0  6}HD$&[̛x`,Wʑ ZkPHHLb"v06Oy厮B1qW_ @MG`; Z&(IG)$Im: !%Yn+BcY#4^L.h2C"ƦhhEGEl*BkۋG`Q/{ҫ_' zS:b}-ū^]c~@kܦ2e:&6A`ǁ*D @fHכ 0EX`" Hz*>6bDp]I@QL<1/hҳ w8!NXx, \T PRMUt 4<V ={iS NÃ$4"覠VJWNC#CMكnJugѰgΞsqk=Mj9e݀D4G @NU @UŊX"(5@BZ"U"I=ITT) i0nhpc%ćtH6e:T@@]iNPF𤛨JU*tZ@<@"nQhP* A+2*+דn@L;72k ^ijX2ݥaO @I{Jdԯ7O[|U"\19mr ĪY(Pb19*zZE5H)TB'=)Eh{"iJLy%#'թnK->'V{z4rsuHmڮE :0j1.cݵBd)Tj>ij5v-Mp +0B8^5mM[؏c+v[Ǧ-G±l J9bb!h Pj`u4TDPjjXmVG1)<'Itڐ$=u>LhfeVUm5MώNnYyO$vJtVaClI#Jհmf?@H!Mm 4 U`|&RoQC2 -N+ LF`2MUoߙXMW..dv[wf2*kӕ hqklm, 9ˊb(b6.a:lVU 5ZLmH a+ER Zy d4MI"tN*)$0VNk6h,9ZEh$ykN WΆ7BhgЁx1v`g;rЭS eG26S~:vބйs+cnwM ;lPV,$+Z5685;+;49/86;;7(**'(*<755;6)*+(7:,,*(898;;9,,<6<<6;7*Րeք*ՐeքN=HY| ;d0,!\ QjUT@Zb9(i8:(v5UP t #@z(+^lB%D Tӕ P+R60Fiݪ(lK+m)&V /i05= f>8BibE|7j BHDHq `NޥDBm)&3Y/΂DSLfni_@-Q7nm$&$I\%Z@Z+F0Ms Ro 1 %t\Th hM TVN74Z@]6O J ;nu{v#MDZij;nEԛ6ߐdC ^rdQn}4~2΀ 0hu=G>nt~Q.թĦ_|Q.թĦ_|#6` \#ݪX 18 "L$a$4HTSHtyY7&6v;z馬gӪTRK+%SR+TJ5UMi+ZGTK6 2Rep1U&~H{uʆeC)r>tZ3 &t<D ^l)=Og]l)=Og}C,`!WrVU9VQ5ln i(jHH@ -pTHXtUi;l QЩV6MQթe{Yut~p=Ns ͜g F@n\g;t©7Qc"тHBy\MXm6lf;[Fqɰn=au69ճՓhVPL tߣN6IGfCXÌJ70ͺ2j3L B1jUS4m-uHy~i:M9Ve'E<^QexŶ}H  նN㵳Km 9Cx!'KF{m*$n'nߊj HIIե "Fڒ#Nz#E:$ME(/0+gB-!Դbգ =Tq(@:JffA2EBn hXM5@fcWWlMOZ@@NT0Vlv0GC-LzSQyF@!F5>fP}R͠MP0`s SEW_k[4y(|sU K>Laɇ {&! TDTtlU=+ITꃈ=EShPP4mL`6mhR%ݤg$Ի C>C>Ĝ (LkE%#*4m;OνP|P%eMGE$E/ VI(əDQVK=&R$E^|Ca Ca z-" LPD[=TiҐKҳA2qDKJUOn D4JH+v,K6ė%cge! rFU@5 ,ZVuZҳѕ TӦuE(-J+<SuS!EIXc5! qpw߅NJMeU&):%s˥P:{&Ds].Be4Y< F҅T`wv7IB]@!- D5QBR;ߧ LiqL&S 6BDxB7hs-k+l~O۠cm 'j>I=_$tg9fשd{PUEjM۴C4T /```:ГʋOl'KG[ܰd'r }T$i6XH J:v)T2G`%h&s!bYŪZd@HPPvUP U UZKUg1JTE 蕪6T'T' Kb k]̀)~ "@&DPX`&FZ4e]JOČ[F[{CE-wee}iTX`rki mvqGN;媪;֬z"'7h$\ϙ.@&ϤwEU(b0L]+"b&tE+Ƿ#G0>O:yq+`Plju4L8ХuP@X EfP kTE? bxCJFDayع@~ '1N:$'AU@kX⁹b*FUaŊ "jT: ҝz)DJ|ٞ5$wLIH*Cٵ*w|N܉JȨq,UχhNaTlG"F Y1Ɖ2ZDZ)؋n+`L"-IE02@$`7+le,W!1G*V#b-kZ`!>BbctSMSMUh*T:m)R_MJ='6Gt|i:"?[bO]PmH!d4ȼ8-76wg([:y}y8( 9T1pbp,ƽnr1%w Mȗ}8|а(sâp0F] JA@0&fX.5"`ԊXLQC,vLh MەDϨNTj;^:A`HP:eVI%Eڶ6TZUڦ]8htSA $#_o2`f3+fk.`2*ag:ا l ew PE$Z0p":qKK?8 `^$L{ -AtMlrURUVT(0!@hhCqih&mip>J:S.7? mi$Ӿ[}]-mipѽQP,=YbF貚<{!vڂ ~lJk,Rnև~=Pl!$C L'zB(hu|T>vuKxLaWg "ܓ,w4blQFQE F"r Ha(NVR!!C3>–lZZ1ϤE%BA)isS5]N e4t`^4))Mooso.9Ћ 8e86RmUhYͫKִ @*HÎR ejvzMSz"`>u^{k[|?,{]m.nL76&lcU`Uj0LiF .Ʊ&0jF! FaAumUo:No_u-BAYqj3L8h5bq4̡vEJ5iBZ 'Xqfwh}+Gsn i.ɻZIH: j-1u,?73DhӿKԑ1c:;wD"RTyGT#:H/@If ["d`TS* u2 DcH!p|"`TBJM4u"Q(N Hԥ Ґ*DƇVX]DcBCj7^V6]Fj3E7uN"$J(ղ{63ہmr#fh 4v:VȰF l()6/{^cbn#9YEMdP̺ؕ 9bB@0` ƀ"UPb; I N+ 9p @C䁪Bi!JR{0qBS*M*Q|-:[X[;`=]؂Ѐ |"h*\I8*;Oձ+LA{A(M!~J%-}T]8)YۗvQub+ZI+Y7 d1˹$B&`, 5jbn3)T,p $MݞRI։ ѕR7z~ͯN=f UդT'iCDx5仅J;r᪈V W6JSJi[Yqe3Fy,o>:#'c]V16:#'c]V1ʚ&F@'0 b.MTT5EUPcALa Ck!:AA( VPm> b{4m$M 横hS "! ;LS7wjU%stY-|3(ҶIj5*+.YGEo=*7BL^JψJfjSo %jTؕnѱ®N:Y7O9р~+IYt% N~%")n:Ѓ67 X60Q∈`X AQVP bv4Z1A+ jU|d1ȾhJ QBU LGc c#Ѹh`G #dA 'rn mztE@906Qo/HUKս6Qa53Eƈķ'kH=5E:Z&mEBT[iKU@`Ve=A`* ='uTDY&--lU$ȚJ"%Ċ**"(Dј)BRi۞"$$q!~)\ kTV|V/$,`XXl@&,TْҤ7mv 6"A(h^ g 4 B"zVӦ+@t$Uե*6uA6@D%S9:ٯT/lGzK)B%*#*JhJ7n6(JtUl3{F2N*$I#kD̫gС/t|̎ TٕVJTٕVJ* S#'D? |gPj3zts{N2e5!MܣfSߗa_LޕmpuV1 EU$MtCBIBUUU׹n۶VѱT74\ߝwj[-""CN1j(ݦb>T::(CN۴MφH lۓ߶'/*zb`Ŵ:hkBSS-(DW8(FچG #luSɺ)gY[&!weA5qDBtk0>$jԪnwPڧLE2o %kmh`ł::kT5U4*W`w_ENHT4+dl{򽪷v 4lva٣qVlӠG׊DEЩ t{]A^ X vMZx9okHCϞTנ/^]8Q$2`B(iVi&B~;l/w薭0")VYEA\\LL Qm϶B6vJڞhzvJ>UT]/^vỮRd1UAA:i;.4Zt*TMIuTRqnlߣ+J-k33Sˠ@+Ƙ$U:M4 &.Pú̓{곑餼WTER}oȪԧ# rxEbA+`Pf_u$Ui T*ڴE)iVi@uЊTQW1)ť|q~}q~mer vG:UzPnG шkU>홦BTSݦw%$\A+;sskDdKiQQ1V4%m٩ AN``e{ӤS*]9^v1=p3_ *)V=˂AeU9I`TTc5bV Ag"T7J"Dxp! vuҳۑ 4 ]Ђt{hBI M$@tBRW.}TTiv鈤N 0a_Zhʠ sC?M8hw8P4=f:5:,(]bN(u;VЮ@n@  vg2JzI4e0QSţ"XhK20WUjUQ+FQ5`Qfi TP@D$c+1"pۣNm8Q0PJmN`¾ݺ!rw#N:udH~翎zȈ|;l@V2 d}?ùE q:8&^5-n WrW7qKF JJ&DmR \6Ŷ)W "VTaG( X5q TSU~cFHj&]Et4o/: eX7]TDu$O$^D_)\8v Q(bE%dh ͘\u@O >E+Kv* @ &,8oހ<'Ɔ:?iQ* A** A*wIؼbr,DXbbC (FcrP"VNMK6-m*z֦c*dt"/i4*:i.hN\w}BDf~-E*$u~!)jo'#dm3]9aw}wVl;$!y!Wm`-i07 u J/!=g7)^BzhϺͶ2Yr̲XsUUA,VӦA0U xFMT tTӣĉ_\. %i  4 Q#_HnIR4T.Ǜn tDPj1G;(T/D;uT9;>0*,0*:;9<:>j!&j*@2fLbb $jjL"!Z "A]w, 9FB(V- VPQ5FĀZ:6И>!T@Dbb@U `@AUW[)ݶ4=ztIU5)2mLfRm$$%!,4;׭d0 $ \Za qe5zh!;7^Zlo4THuh6d*0%&nU.bU ,`kFbZEAժ () zEsMB m@ M`*$tJɋU+uꆪjB$H@ 5`dҶT鴒S4Q4Ѩ3MN#W,PJQ9tҶ: o;dlC# UFI=MQ :2T;*6?I 6c.!D*(A&@ X5 bE5M B -R4BHP-T""i@5=Pm@ TB NBJV7J 2AB8b! % 2<ZpG"!6Nh逊M-bL dh~h.i, -a۱3^J٥&q$R.5ӯ$@7҇C⦘X$  `@U ÂW0*c N =A5'գ@h;p<(NN \=h` [!:X 4նxš~7õŔ,Z*ƨ8d3$g,?@@ðLw8U`>*J3 bnTN?j*j->Ԕ@`c77ٲb G FD, F@XAՊ5 Fp0Pk[  (":@J;$ !ЪP@W4 b\*o?u[@ФUQY(17&tzuS(J2f/| 7#dmMMFTTz K2[I%15 WXUԨX+j ƪ+&C2-b0CXM*N`JuնUJ5(e)&rZ4V2]y`k$=hkd)N6N_Raɺ{U ;IԢE knė:T]N;Z253 ظ7u0bw="r`.T-oOkSܿ;<_"If@8pc1’ aEXԪ*D!(’"~قC#o?+QJ4mP.%]EP"Lj0#QBG!^W^'Z6}_ÿxhj&5CTIҦGn) w~$~o:|1+-G0_l>˝m> ,w"*^$L9bX!aZL5VQb㍣9KDD7mlV4*WDq6X'EOŁq%( "c!!{fI-RN`&=TIJȭDne!ۘ;Kh9ue8l&tF'^81 :n~N_Uo$d޺ D>7nG(#5 _P2e$Y{ qs1g1W VZ֠F1`8$T( M:HB$ Xx拸FDѤm4!ZHI%M70@YW w VvB E@uJy* $oEջ!骟2yHGh4| 30/jѶ3j^~~h[D5/vA?_Tj INc&q*D"VQX"֨AL)D 鴪ڊH^_$S:m*tU$ nl"ธ a1qA xbBԼL0c}D\=v 5 &/bMΤs MTIvIC[mCc5_&u|;#/)y:ntn6B^!A BCtS@!ճm$QJ*)U=jI&*Fr M_ylu[LhM6}c+@*MJ #ˣMY6BpB9$eLTvY=ܞm%Rȕ<6h+B|3՛E5)I6Y1s*VA"VUZ09"@0JMR ZUݠK"* SAtT4=}QS-4RZ):$CWDظ׈bҁD5<+:=srЊ.~DB.yn4/Rp-;NDp?e(*J]bWKf2V\UZ#bbTETvbZnu0-t=j:MM:hCBxVĨB1a`;B%}eU08c/߻&t`y@F7$EX;%,ӂqmehm3 te@>% (Y?֞\]8A+M$YiU.WCk T6GMg(RA|JGAѪv t0 :FEYI7'@QuDdV Vp-Q*F8WfsyBf; 90S*@l#J:Mb۝?r zz`=@XA j"' cS@=4$3SFI[tttHEuB#W7 f>+Ϋ&< Nnϓ䮫)By3r^Fn[~|k:)>.e~a2RG$ j:iTNIͣF⺴~Qh6ż,9:ln5mZ̦Y.A:_QSDMMVemՔHL0ǜe5 WŪ`cZk X)zLvUzѽe;/Oݓo")4e[BM+Cd/[L/[^u-%۩&DhFp}a~:6 j4J+w?RXVxkCY.d{wng}Q\<iɒ۫_ȝ,I=@[r6UU5QkTQ1c1!8^AE in+ ThP@#|DWB TuC4mUZ@Z@5tSiKG'cѥ ~O0vD%Jt4#~@1!a Y#?vNUIW4QWZ<gCQ7x<7ƀ,"-  jAr.ߥ*[AId|nInHl,WV΅ V t5MGGCMaqPSP L(@TI%N.BPtRIJ问NHy".~N **joI,8rDٓ ߍވj0 s<`;nBQjr{α:166%/$K_v:be,n Y@ʥY%2+(2Q@YSaO,r9U*P X ("Xhl!qG ݠb輡$ ZJOMt*D$ b({jVSEUiT1h-3STblhA0NIۮ04@؂ٴ'ֹǛiu`- cU*:*u~A (D)![IeX qUZA,"UԢEQ2iBUmi0QB*Si(mG+S@eZJIWU%7GVӴmDiΜk{, FIU2´y1U2}pxƱbVbAwDߎ!BM.&}>Dފ|!RWSwmŎle=IXc!,մ!$TU۩RR)Fzڦi;ũ&+tJm U [vŬm{:!*9Ѥѣiz$ 6lWK7Հ)Pj-VRգ.QMNu=Z4}JTa"a\2Jtlt3ҠgAF,(C1bEFe'|] a 2+FnQQD t~w~̟$W41F5,b'v:80HlD0tVS+:tOݍOO?Y (1"FBɑDJ + ;hXW/:N\3k wF8(C`4ԫBj4=zDd3>ф7lSG8L-hM-h}-UPPahTLST7ݴRU≉'jlQjzm/<^YlөW;xf1ON D)9cY"H.He!kbD@(RQZ`FXIuRiMċǴ8KX @"m \ʳQmgPctABe}^.4uQ}O;#~Z&02L ?M3D0.aW!h@=Ōmیbƶ׿WP$zZv$,+WUUE00 !ָظ0FA0H GzVJ7iQVTa@#H+sFQ|&BAZ J,"umT*:˽n(T:4iMՕ@D^E!Q%PGH|Eiž;Jw,͋z,Sx.S/rr_ c^VNXdlx´ع?4YlLlx´ع?4YlL D-Z0pe9wp.\E (`U)vGU5 1*ѪlKѪNTmmiqIRJ-H­i8x'׋л /`{CڶJӤ4Qmsh u[ c`<3Fz6ʟ4qκHZYMRzp<~>D{>@N888$ 9X KkQ9&f*bŪFSiK5ȴ̖he$08} lz{S)*ݦjh;it"#Q "Wua;_C)5[O1Dg]1;^F.贋t /#g`Rt}SY"eQfj$gY>rrEĨֈ5V aLT u@\$ /ˇڳ׊tۢ4W&rOP(%mթGv4rS%M&-RO H*V3Ȼ`ʺ慷 Mw|<=&<]'dF$[Hp wko9?R{n̶MK.bQ1bZ]aC ϖ4-?h'g @C^ S!G>o5@?z?=$MM 453zJ!XT7!.n'R] 4Q`M&e@htTi"+'uUmڭC<<3 ̃'ИXFUz҈<P %ض֟b+1RDU"6{L@uht$jVMYupkjCT#S}+̭.! PXmT)h;ݦHT6jj{t3m҆RI d[/lecnqtP&vtգU%b.MҴ= \ȗAmˠNe+[~!~)oǶHkTҳQ$\YH^D*""Xh^4$IIDmUՖQn[:ѱ֨6Nƀ|%Tk2!3Sc2h#Pp(RX`UTTŨXn;jW `jZE mK!^AePUJtJ[гg"hN mLF3Y@RL+aPϼsf0;N[ݶintS}@:iD*[ƃF#!{Bn9d.nQ//DHڦ[$9O1#Q3|~u۴(iM;!S:$ qּ8p 6 az}L(= (^M%}6o7ԛKl$n:PO`r`hBXZ$ ʑH|`F"A"ш ;Hޙ>ւ􎊴PE)mvNTZJJ[;[d[EaZtv.&˾GWhUtIճV GƂ3p_8EKdlpݹ7T&іT"yl#rM˼S4T0 .b ྂxģǿnq#;H" I!YHL56ð)M-:sP#!NnnR]ۭO (awt $2b I;m5l*.TAW[1L'/'t羽n= TLG5*j{@,OggS aPe7)599&*(476+55LMpw%Tk[35]PtH͂8,d9K$W9((*5ƘجV5B@D,cBA4(F$/JEniPСW'77)'}F[C+U ItRGpC@nnvᣑ}~uj ǞmISMRZ)2=i8 n@Ԣd6m.AѹnrJKLE)&ٰY7\@T0*ajTERgәH\(DPMuhSV|` V{v7(JN6Wt-|B+QiPQ4mmj-RDMIK ?%gtBԄlVNmU߷ܵ; Xn>cqazi4AYFy0DM,'keUUUD-jPlhH$V&$.@"HHׯ{ UMRtFjg0cZ@%҆N$E{ɌҢXh5h+% Mۭni4MJx$G\j\@L,JN5*6MbZA[өnN鴩N-qaADCEd"Ks/H=a)˩ "Y& +ca؎V[(z?*4-⥘ڄ:;)jNID o/$# "Q`LV|PITźloM={2 w 3Am`N|R  v86D@TX)->7N,Vrd伲 GXG$G0CLDP2A:UiH:DTHŸ(@SZP MtJ=8_MPڳbکZ`M:IvjݒmGZ8).8ɎC%q{oW [/W [/&@1dX1j E j#vQ4mhMZ=mRjzJm+A\ XyP IU5q船=SEBWR>X&=!<)CD͊m a ;"t{"mvJtFUGڶ96f]?;'2D5M6[]w|>y$jGN20Avq;9gscUՆZ4U݊X( lGЈY&Ev)Una[/T~0i:&U%uU?>q?3J81zLs' O>[zrIj:Vlu]OsVq3-no,ݪ z,U l.i#-VgAnhg ɛ,;HrY.*ƪi⨆)Ţ+JHENg^K_fvT:MJڶm]NkWBw: IIB(Tڮ(`$ZF;JZ5Ti!=5mM*D5ݦ#޼UNxߚ4 Lx ]MJ@i `KǮK*A#;Um80:oF^=%l &k Ğq`h㬸!ɊY9&U:ZհjECLQT iNAm&Z1HH?Bg[H WRA86 ~Iu=@J7y^  i`+? xL.BuSmU@4nUmSH*Jے֡ @H[nUp=ۭ#b  Za.T[|Ն?,7PLɫrNCT1>yUI,˲M"q@EBj A$&8LL4 Drlۣz4HmUڤn_BtC"5Ȕ>L-_Myk5|k ,ΚO͉dh)4|PUh: 4I(Ip5yӄu w qmH[UDu۶vXNd*>Jeim'Vػ,߳i6qe1MBUH5Xq9H4hg+aIöG[UEFyt;-IEu*81:e 0Ax9U">XGP nJD'MUSBbm/ UQXu/T^J$8 NFNJWij6%LNۈTLO esr TS0Ɉsօ I-OY 2AvU,V`^Yof9w N-L@ +B& uu&^n?E~7b8@*WVT*&bQEhBa|XD( `Cp$ANG$DcT4v*UxCR#铐š]Z{JHGT[.,? գT&!(՝JV[) "*1$Vڦ4RQG8SX~+'47Jz,u9ˁp\E9aT\vMbuv*.I~@ @+`p81GC CZjQ+V]]XjRMTim4ƱgH-hUBKI4=ۦQ zŸTK*jML(x!£BwO4h-˖$+~ iShڔT6ToAQ릡W^*&Bd A $.BTʀA^2%ԭ׈,/i'?L ܜ I9W$ŪLB4v5M ݆C(QPT =*n|oW*uJmRСbX8sM2+sEZ)#B5:j >ep`u(U-GK-M~SCБ0{("U>Ty9 2.oII׌3ȯT(d,HL_ StHG 5͍K0wBGuMs#Rq4s gJ!IsUbL00'EPFϦMI:#ݴJM_0" *.)S/"i{TTҴAUDG+hB)bpon5fJ~lZjiv*#͗&?Fv츲4zЌoB -64UQ[;fR9?~q8f9\9:`^xj.+cW{0w '\yρk\UUUZmj"6f3HDDEaݩjSIUCmۭ4ƠOn*FuӖ*9D_&mUOX>G2 iSD'F_? _N8V"zTZTBLi3H}Nr>9a=nFZn$h#9Sj"DjBpW=2 v-HpߎRN5dD 6V؊9UUUUEEFMZM=;mvQMI6nJIJ;FN7-jDxS}JQMS*%8w|}倰م `/ "2ALbGՓk'5衟evu:F}_7X:'G9#tU"=ZJ,+Ts~ˀFrљ NoPunCrjBm5@ U*v4rnTK|q Mε>?@^&B0 (Ω Rݾhida GYH)dhU FL\g7Z424Iڹ5+S"J ?;P@쭩@UkTn[MFFN'U%sgHj:nn%b0A,I3$q(R` 6={BPMGisTn$8ɕć Ja M$>lP:xcf>&F3M *PVYfXE,X-"jЩz,,2iNҦP$SPצ oPm&H*@im$Ѷhl r)&TT#Bj[0;轢9~:nlh?Ժߧw]2X3 PIR%5n~ m+R^fi8;GQ)/4{}:jzMM.$YVUHcjaİL& q$P"&JЫ RVGHԎo:RRTk0)! /LU餄mSD@S߳X s_rr!gtXtBI Z" I-l6Pn;W{ uqr7Muqr7M `lL H#(A,VGC "AjT" m b+ZDEgSB{Ոn,%Z餺U(mT:[-RbZCtN3] V"9}!)QR))<v=bdFUi:Ht" m#ou*` KU"L a0@n>87qQn>87qQ@YGDDb4Yd9Ud!AT`VEE1z\Q *@ꑦT4MRJ5ݴT)6ˣqIq!3$#:Vg@Ѿ0Gl%Őn4ҤD85Mh0<wg 4L #Ah]DjU(fJ;D3-+_ϴ($+lz>7[ͭ **JuT~s++1d]mܭ+ PF y+o2/Gd=Ґt:M'M2^+h:!t vx'UH(D@ tiЛL}tӳJiZr5l@ \zmSװ=5pN͒5 ǜceDe 0즃XM)  UcxdžEHMQ3+] ^< +ISN&RNxw0);n<.A44MS 6݂ѤbY&S~@mQ0VcYU*P5""ֈb( Dm53^\sR)vH*Ȇ:J-Pn'%\RAIb/ Mѳh$8+~`|Bs? pwM܄歶% RTuf=:Vg5b=q C hcm@,ZhȦ vO >nJZ;=nJZ;՚@D2}e9$1WUUTZ@ZUAUQD"VL(JUBu.LKfHtZM>דk# ˡ1D_F!JMK9:/"7@!Q#*m>UG BX^4>?(1% s$a9-$ W(ÇB>lKqjI-V줻+Ւ$[&I?fGrNM1UE@(PT h :RJ c(.nJ>TiPZ JJu"P>r0禡ѴUMS1PS:IUSGE˪4)JuuzVR UQp``FYq 6~4+@27T7"~tza2SX-@ a-ζR=x@{KTO?@MQBMpMβ$dWU@DcDQtVA8 q0}a mХZSKjɞ7#G[#@ m:FHa#kytV4ڠ Jm'-rZ81Ё^ J(*0A12Qzt)l(\ޑo󴆬ZnȮJNe*Z+I`9HM1 6]j7@ 01 ܈aH1H$ iGA5VJ:ڠwn>wWʧ뫆XvwT@$][$HG%Vu @7;a-F*Q0V toـ/D qkK(h!.GC=~\ "Dvi_U9)mn@uih`2:SeybnRH>Lڬe RA)cHUp8bi5bôZQۦ$]ݞݎN'HmEQIh[*tҶmU#Cpߗ=IT:IϺel+wgG"vDS xgld{5ݭI;"ñS&dZAVӎ'~$Vs095RƏY4dܹAˍ]^r#-i+7iͲYdsj"v&&6x$bBLTqV(ؠRCzْR-YwdNpگEKVju&[}\;97'k@3S)ݦjZiRMQTu;:r|I)9"B$2#̰#ttРk~5ܞ>}5ܞ>:eR(YͲl6*V0j@PĪb,"Fa 8gдT^JJZc 4"t  TJS49!>Yiy^trbPdӖJD5X>.pC&y+d۶'.q{gDZ\С뼇jMOggS@ aP4'6?=+2:8?޺mĒJL0C"ںmĒJL0C"@% ~@f%+HN"U*FQ@6ӴaE0X B$MZ~r\!tAT@mSHwܠ$i-%ޔ( I[%ҩB̂VI*)&T#i:U 8)fwsD DW%L%JG ؇wS7)L6XQVj_f~NP* 2`e9V!VE*kQbXMbX Ī6ATl:_Cgde 6(?-Tu U괩6C@KU5<\^! Č_ Vb}{HwV-O[Nhb %YdSYf|Si"c[C@Ck#_,H=qq;{$R;CCdY^fYʅ8\UPG0,]`혎ňwLĢ >@ap}3|+?"1Pt;*m%UUTPTӅJSV5jC@~^~j _nf1JkBjx&(7H4_ִ eCɪ={7=d\ޛK1F4A$$=]j 1)nO#᡻ڂpL odQJ`!01Wb+((8Hl1 ăRՐٳ}_J5 A5gJR3DyԆ Cw}mR:@&v5nϴDmph.mt8YQ/F Lc - ] |0Yl@x!Bp@ })X'Y5h5RzNAB$`q'YUXj1(Ċ H0&68b㈐ Bʅye\=!@zTE"Lstѐ4R/Mӳ񁽁IN=VtIO]R:-)4 Cy/I#jWhR [}z9$פ)a&C}!l ~*} cZO#[AqLm`csx@;h8 vl"!LTDp h00A4m*&ݴzMwS#TImFE]Mvl`X 0qUە$mSUI*J{$:ܚoR $`5IScxH- ܮ98ˆWYpk>xBej$J;xBej$J{v)69W.WU%18H4N„HġHl(6J( F#()UNTR:fsITt|GaUw {s-݆=P[iߦDAwP-ׂwnG]x'IzBpWhb'|azh۝m#rm>@4.#="a8k?Q2x|D.:s f@,YeUb1 XD&%a4E@"BA(JUmI&V9UK  jt 5I:  ZJ35m/_H=G#KE UO'MAɧ@+ Ъ3쑝]V{AC f;,1n">x~N5ħv7x~N5ħv Q@H&,wp,cUFQ#(WL1"p:R/6mQi:UmUܙBofCj^gͺe&wR"ҤIu۶4M:Iݶۖ1|Y =VuЯi[-hc?#ZB̸b=Ls ;[DCWu=cnb29Xf>lwfI8S>*`YΔk5wˀ8MŮ&vU$KćmѣST+* ^TȞjhfwDJ!D"&tT(UM =xN$r"PWVAzJ(PQCic kjOa9iBXT.?Gد;P?{f1n^8$I| .{r7͕NwnQ~;R[ w2a嘫bVUa`+Ɔwl!G"AV| JtRRWsWow輔T"' +E@&(Io$EmL_!*XN؂XfG&wfw6Zfd@j p Fqh!fSpPw$jOj w$jOj &y9f%U\Ui8)vDQD$(0vyڊQRTV _J ՠϏHon mA<SUpR7&Bb5_vp쌴MBJ+q &D""b|%9~vt1'W ~hd w$̑_Gi;HM/ݣZV0rYeUUjjSS&HLbH<Pa`)H b"1" { /U^E"I_gӹ.%(· dH):5;Zn+qQ!5Cmbh\ZN")c4HT2k8b,BTkeey gCь&Z(36$^xdm-Dw}ȒY[ ]3$ygr>XJVUI%@r$AAbb˜ qb"фaSDtݲo!mQ+j#xʀ_YK?D&]JHh+ !x g7Ғ/RNIWXhG@q- bUE4*~*MuS$E5*MUjEVeCm4&g{v]1wT=Le]&VfwT=Le]&Vf`$&<#̱rS Qi98X !HR&ht*e:%|>۫4JA:- 5 ,R]JRu, `%"pIF r :M8.c:Pw_>ˀYu%t}00r\1)Vâ a1MUӴ1MV!"qaH| &AH(;NyTVHn#sAN _ըN iD}Yo#]_`2ynmC4#tʽ[9p(]qeF諄Ԍd A658 xc#^wrxE 2M׺#c.+Ti $,X̱*jM1*`Z&8>&D츘(cD gG~CIiVE[j 1ڦJWtoTKZaRP #h#ۂ-Ҫ D&F{Bkn%p9ZPndP(  iNeϨg!tgmP^w$.R|H9 ~s]XHݜ@0Y\媪"jU STIL\*>JvK|0&拏y~a&tTU@JPF%=mIS]舔"G0l4c}` -ҥ9B}@Ơגuk5Hq6Yc4 Hj2@A"~87^w$u%$uGROK XWLR{lrU*xqqcxEJ "/{ G0M-IP$ic^(fWL4M4]D5me%A﹝AVr@"!hZ A ^4IR\C io8=Ĺn7o}ujpa&y9BDf( 4.4w$D/d%ܽ#%~&+mJ`P9,K !VUVv+&bD01&8ШJ[isql_Θh90 2qőM]FhA@j=ۦFp]O܍F\W h]!-STU $ͤh(gU[>4w;"YKw6 b >&SWY,AߙM'Q4mAU^ݑ4{$HTA{gc@eUjL\BĆ"qDllT "D0$(Mt;:fFiSv,'euUjS*MWUZ@p'?-VΒ~~.MnPl `׶b련/kN7 nx aU$^ف,t5oV]Q|5=>w%U)GR7w%U)GRwM!1\UU5PQ,vQ&x4ΈD@qALD\(%i;ET<~+DJ5)JPB(TiVR ɟuޭ.E*vP b҃ZAz.jl a.vbX/m=)s^ > yHc40PQi8#Gސ3wT}K`!qGE^@Gw&MbcVUHa,Z6A" .D¸h C+Al,1t˃UR:Z-"C m[Z`=ЍcQ`HZ &m#U4&MH7A@0EZd辠%ƂƜi [*ȝpS#9r.I\ nA7Tei*fBw0Q 9tgEAÞI Iv jUA9$lr79ǘX\5xEc+ cAL$0(шbÐؘhQ>P 6nof$%~4W Buwb Imj*QӲ-PEd:>%\k{0?:Ƀ":/:): =O3Cˎ9wXQEw#- &B YrC~@p$v̙ v̙  e "ceY̲XYrU@jbToaWsU#T"qMؘ0c{GӘk)'[ab fDUdQTb#*:C qsU vIz|: ODBaly0X #ݟ碽/jv¼U [, _WaH$Ċ.7< Hȴ>ƁqPIG|k]ʘ${u@A< 2 SJX#TS[IiN*;MeB ׬b!BOx#BOx#}Y)ڌ2Fi5ƨ5";3*Sn2ڑsX(N.o:"`IY-$L)P+1VAҩY @J7A,h0AcS@@ޚzI1XM7CdDZ B?8υ~pe- bUmt')Ds2!GC9bJ_'tNt %z34=t:iSL6&RV5EqZ39 )VVU 1($RDlM'&;.[ʳ_=+T~ 5HnۂB0V5m7m7bE-k5:71At'<ȁ; 7~L i-rοV msU.VHqMf vÆi`[:jT,BjM̵&QUT%6D Jdq4؜ y " 6땐h%AUO#iRmѪ;Oӧ*ɋà z*8|i݁ས#F4dp]`.XPs2 ~h^ TX@U8Opgβ,sU(Ѵ*&BLQDIL4T0 _8%jtEUQ]D$ꟷG&r JJcy!MY&$CbB.mPv-ox}.𼵺b V넱3ٸKB n;Ȓ}81=W9Q'K1 gh*KKCt|= Ȓ8MUrvF( bc"E A4BPR$:e"K+[HTQTUO\O@4AE5CE2@d3s H:-,"9i6Uj)Xy8H#'gܖpl & V ;hIUQiӮ4A%@)dekC~m^z$W+` Gru 2p69gUJ$1N4VCVA0%#tPڄ kET5:*w s=3=^i2_Q թ c_@QG=r ҋF GYsMsZA10DrøxK(Iu#*Zr26cǰ"bKM/x{㐰Zx[zХ(nR%%6TBER0qZo(zd&/.T`"P x%ŽiM`4y!TX-x'M@ D\pʨ7Gq4*]H"HUZG<>A0-&YbŜeժa8iwĞx4r4 06>666z.dE--cLH-;xLs߲mUANPJ JR QMWSQ~Y; V@>fߡ w(HʂP1|*I2,$5ީͲaEc΁XXxRt ZM`vkR/6<;פwl,T10nU Q I&8$&.A"XE4ghJiUmkM'yiBR3Y$ٴ#nZ|M|sH QA=:m:UIUR~q6RwDn-DiCdV%H1]w;m,D5Lv9SCc X_9R^m[U|hȺ/ҭZGF-=P jEXs'"c#5"ZPT(=TJң۳4G-"yBJpIM$*}O mҤӟ:!"(04@X[fH٤m[@t6R $E8D߀G}HZ9V ZPG\08N.MnB5j^zc4gװҳMܶOM9TL[) H(TU*iGa74 H BEH@г[i&&فTt:MPQ3'Q+WmNf)N s^/6Mu{lBhn&XVsD[>yA-tPՊ*i4Vtux8ft ؐ! @B5 G]k'%> HLa]M7H‚6=d,[! "``j؝0ER4zj*%(,;>@(@M!j bia * qEput Ru: Ap) q\F1Bc$q@ )Njv @ #BPzf}qѡQtJA%.B{`dBv?UMٵ^!. ]a MRaVUUeE$U 1  BHR%`Du{l&U${"TQ4Zpu$J#cFBT(M*]mI[)bOP*MJ-Ih;J0Ny^ # W*-ݙwCsAYA_ (s~G-pJ!i~e #P*2;2Y(}`W\ʹ"*ւ1," ;J:zQVSQTS G_ay3@allGp)y^UJmxF>-8z[&l&DEۭFҴM7"Fݹ@BVp0tn/@{jHrfCe0yBLP0KoBl$P1^Y$C^%<+$0sh٫Dg,oIsUUĴ[( БD >ڠhUIq^kցe^\i~A4mՃ)r btХeuUmJ񣠚TGI0_~#@ l+wLaT f U۸6j&P ix豎6ՋJ0:A]@'uZkk~}OggS@E aPAh(>6&$ABUtJ5w+<{"Gch08f{t4))I҈S%TtJ%R-э*.joh=jK-sEvj-;in@~pPЙ%,"DtɈg̝:u$+Riee)^ ᵟVV&^{,Y^YV ĎêaUQhMLH@lL)]C7mtӦgM%R|J|Act_V_L+ (#jBwyTZҦiuw:b}wĘ.Ѽ@XQM)_$g amuf'Ql{6>1ܽ,7I1*VU-V1DQ$8alLDcPl\41M7VsЃ(THtEӡD3lQ"nPN+@erp碤PNkTw.m*b(\KE\²AS/LXTS-cyvN ^h *+%43/jlި,q/@ Fe{rX~5)A5`qu45"Fqp4T-X NN4ݎNi%mS-P,cd/zj(B~>+v~CT*թDEѨT)ң]=ϹlږBZ6q hG~YsyiNPm%-^ֶm/ǤƄcTı>xle[arsO; L6YfYʲ\UӰ j!v5MӰitIu5Ճڦnhb2tVviSPI3*"%͈#<=0LEyl`Kڶ4}6 Y6SPtA#?bz(/:&2N#i0 b*Q*GWwQ~t\5$=^Sdj >xk# 0<ε~H &nrcXUUE,`EԂ8ZmFStҴU *m6B7'*@Q" nT$*݃0ƘP!ß]D)>bE4e}*H_a.7!rUq9υk ONgEf?ܦ6n.ÂRlV5ȫAm X) w,f3}bV8gIH,fYY9X*5X0 v`Vڢr14F*TQm#zvAXQ$'}w-_t|,˜P"+Ԏ(lf@dJ/5iA '{s 3Z3CQe-P'f# i)liIb,>'liIb,>' PId1h6,XjEEhbbD" b9>$DH/ Eą.c}"pgw8ݙFPNѤIuH*"6*-/bEIuKU[\m^Ct\E@_X\ 93E((niP_}{fjȫ~G2=BAh&D-(P xtiH]P *y٥.|~:~n؛6tS$,5v)bLf팽 `AlgX.TkR DRdXU"("XcՈbQa`QGQ4H\\DuRZRmDU(LCVT4K{6@ytitPBPDS}u#OjE/rmf˧ݟ@Ft8CW0hXP7';ݯ҃뜮هz0B*q}~ gsј}wD]/k-wD]/k-ldxTb1L`Va `bwTSj'|G*a;z+Vsj]@ԫ"M[Vn%pwLOk*Sh&t'=#fHI~8WRD}b6<6QG\7zjͷ"7쟕Xf;r;^ Ld1wd~ #{hWOW8k @D qc \ V*Zc,R@8QCB QLE#IE&Jt*tNvժЭ&;BJBm4ʡ SݝuE1n?8cZ.t*~Q8SWve/hOcj#$yf;2kBuKXJΈ@$ w~Z5nmj@JhHL6y&fXUUQ,(&P4L/cD0Qbq 76@T7" t:hVh;Dt &I*)T$U]άMP\H,,+\W^Zyy]"Rźү9{B9 վxCt&'Z{ .ky/5/¡j_!F~wtbIh#щ%SJpKD,rNbUUUUcU5- GGE A@8GCQIP6R!ve)ϧY0jؙ\үDtU*Eu$L'Z v8WJtql?W<%wJb93fa9`#,~D9&avNBf.vdng~w,q~,p%ΰڏ) d8++VE"h3-#A|bDX0+~,T  # HZ ոC2Z^5q + @GoIYm 1dOxV|_?WmOaWҀ,D̻5 ^f;[zλ=EwcKC^x ;;-_~7~> mLb dz n\$"ۜLsž_XkTs߅tP RLvj""&|$ R A|L(D]{.f̖CnlV KzKz 6dc&Xj.IbZ-v-A-,QcEHQF{ RTSMT_XKwryu#$*nPN[)UmJ76(?ouB(RNF *0!wh"rmP,4WAq   ; 0aJAFFBK +Bt1W$[=;%O嚣\s'SwkK`;MD&^1Q幨\{PIZ!?z؁.=[ QE5\t2H`jV{nߛK/x& O`O`  AU4=ܠiT.#қ M:CNk \)h/Bq몪5(:4] I'J N`F%٭(1A4~4eDWVDWV|LQeRj*VVJZ_Ue9a"v$AV:w6>rSbnMaHRVMu$խtGӊOm;PttJ)"\>>zʌ 3`k]=Ғv͓Bt|ZQN>z,I)KRzuj(XAv+EDT<鼤S C۩qO*:ų`1FTRIѿ|~}j: =zTH| l[T*: :E%a&ԘJzV[*\(OSM5/:m̺5 2J 7se^FV"PbhNbbsU Ej0S4F1ZH 00X UHSVnhg6{+z 4ժt$m i"r1Jݤ[%`p(vBB"{?]pQUI5$d)=Ż^*|@C,6juM_ МNaG`0G0"&CE. F^n &Z*MHUİBj# ޶:L *$Q ISVѳV"in@ ^/zMW@%u.@L,d+nN@KKR"?*ˆ2n/к7#3Wi!TJPfb̒)q\U 8 ("`EB$6&8ҳtb1l@5Nk4nfax)FIlw^@EnT%"ڱ; RmҭNtQLA=UEϢjXPxC(/>=#4Sp|}Dzoȡ CfPU̇aOU̇aO ));&K6\hpUUňZ VĊ"[ e&B+$馸,]6Mw:FCRHL4<w#dEJEZ*6Aı`E u2nR6&G!)নttEiRiVnm5ѐS=웹cM+<l6$g9v!ra 0W ~ʅi+g3"7ragJٌMoPG;@6YrȂ1U"IԊȂ 6vH0PJM TF WN4R锢mJj$MM/U rٶݶ%q3&3=UXس߻s}8eSx!8!74}J13lrE}C< vL=\ث1g =guzap*îK$ԽJkb ;ugceXsAh$a$Tl4ƄABBG""PrH@Щj۪n)M}ǭmbMIVIξ :4$ɾ$Aun4M[haqoijKTUUtqFwS[!@-o!1?1їto,KΟ&|T\ -$&1^T'= ņR: Wx,62TWt;G sv[D붴Aٖ80ME9 dmUiaȻ8,wj>GS-e x&`OuCB&Ble9+[YTT TU֨ QGCK[ѶIC}ΈuswR|]0Y=w錂P&މ9&iUtPX^XEQHH{jV% ع)fC(!{_n4,4O:0azFnjQ>_u Hf(]>T .V;T .V"MM\+Bj@-X(cHD0Gt*M[w~)R "Rw|BrbKUUK!QnHCԣ34r uxVug9M?jxN [֩>(t. s[jh]>'i?wc YրlB*%PQsi@(ZRUNӦ)VuݦGzUwwT?Ee4a%ٞOPSKu:MTy!9 80"J,@)RM "0ѿcJhXG j$2}=d _\͋PQܞ^Ie^Da("' +@JiPџBMݦ=BA/yv&AWp]UD@ EUKS>KUfr)!o"U Z+Q=!gx6# C 4C0 jj6 4"bELԮN*hRJxxcTj@R1w.Qs󢪫(|˵ KO KO K FSSS$Z JO"tofTtMlMlzY5B'T>5%t~_>a I;2AҎ I$%@oTjUݶhJMz! OD Z\P`-.([?!3ӎfJt{S틝J[)=(mBSD ! pYZJ$LcԠjXv]Q )\/dע8#<_ At -`-8CŨX+U&+ѕI[Ru/N}~zE)lpt)E@01b[UJGԨN4/r%>IGq$ɖ'ɖYF0$LjUvZiNm5U6m^D)ZmyCD )&ۈhVN1FD[mlH%9M*W"KY-DN&fVU$aGbIn,-Ͷ{m%|t Bj MU4>F3n;ٍ;zttmGΉ*UݤJ6F,](fHYV$!P,)EYVQtV#Tz)Jk[F+sׄۑĭg[w`푗\Jmy~/buVU2ok{{۶|%(kkJFB=Y£lL$KTs,Q VM A0(G< !. @It{v!ۣu`Pl{)ء$ŹYm)):!OQm:POIi7ӭն]mj]ߙhR ht"#x]k31h ~zgNꌙ^;=s3R'quL_H@*,\1j QkZ TzSKq#ğkXL"#T#p DbP|o_I32 (go t˝؞z&MzK62z&MzK62pӂ@$WUd2 `vGDvUhi%`T$@h"*h8@uP@N'6!NŴ(M:ՂT%A @)RM]M'j)QKUA>Kg R)MTotVtHʐ3=yL@܂NDd(Afrh{XE0 BHn.M =e 0Jd5-{XaQ^CVzNs8J*Y`c5 Q-U@VTz6&1J  8c ,t HTUT&=SmU*~R4m-V&$MN'NOP_DqO ,kQ02dN^@ 0Ċ)¦!4\6G~JvM{z&v9o޿$? @R1c!*QQEH1qDDUS8EM15@ N!nMπVEhHTG+\NRiP:E~" )GzDtS^Q"ac2I1-\&o 8nGP 즩|]@Pe:[Aw[ڪ>Q=NP4d#a4KZ4KZ|$,\EUdjKeoÜ4MH6MHެdT``uRkSA:H4 $I$ }FTh3EjV#=jNӣ1QW).j1~,KP`YWԍɖ`UDD:)=PkR=Z٫^mmWZ(T NUE * ɰ(7Q[)+fMHdYrU,WU F`ŀdW@ԲnMi5:ꔊx,ć(a:φe˪G^m+ѾjΘ jߪ#_ۚv{hBv`k% Jϱ|MH'-nѶT]Z[zWw rRZKȹpTqhFXbկ!~9d9H }, #YccMLG5n7,O$p FWTφۉjS_/KW~hRe\\u5rJV #7V !Mˊ5տ <\G"&)*J:ZE hscRko펁 ,Fq7}! nᱷLi0Y4+g/ F)T#Ըw 6c3 OggS@ aP=4< 68+,+7:9-)/-./79~IԚ %Qk*7Wz 3 `lrbѰ jSԨFa T%:%ENH5pt+m:;ZzPZ Ch(EE:miu iKBO<8Rf=_!N;քΐm *]]t! Q8]B/qǜ__(M2 ZJsL>Z$]=ټLC{LV6ДXU1 9 kD#vhGXlƓhTAF@[m5M:m9#nURIZ"l"IWbRܠwtTFS":E)73ʑ=sdM^ٞ Rcmf_c8-0prOm~|b Uz&::R%?0.9S:㘿@vs^.:^Ja101S.:^Ja101 )m{(Tf U+YO/F6 ˡ{#KbWs9$kU$+& ZDGbQ U, zSsM"MZ=+)vUv`a|%Rc*')zJkSD%mJ *" }t.ܼ(&gPaN&K^\/@K4_ eH@֍Q-̫UsUDYcQQQEt(`+f!AJTQt p*=5*V!NIҳhӪn탔;jKj=+DP7m4oהh:)RJfC]- 7(:.:rv^?R$ >n,b~tU,OɁ$Zm&.2ՎI-Caj(S|NMB& *vY+˲\Ur@VEQDD0" *@j  =v* PDҤ 4=)@,cm@[p46. !L4a$ 5@ք~i+zY z3ӠTR&r:ڂD[!tGa WAS% @Esu'a)pg Gw1@ ^jʲ2p!L,y%\߲HPM Y9-hr 4UeP$`UQZ5P^VHVm'@)ˆp"!(cC`pL(9"p@ !!|wD6*M)i>0 I tjz$  h:JF'!-*h|UugB/ޘ:TT4y@KdFZb~JmiCE4?m[DPq *Ȭ>5 6ؠ=gm\+rEPQE@ZcPS ),HpD@Gp2HAT¨C$1 3B*ն"+D酡Hz@e@7>mIh JG N)ңے i4R@75lhPT}π%C-PR6ĥ#Ҙstڷc\ iSugٖT ovF5ޒsL?(`h00ܬbb!PbDA,V@D0E1L :S =B@ )05Є7Pm#nu EtJ: cB"Ȳ `Bt҆xQe&\0Xv6'1}Nz7d"W2]@ >jП cҀ FRtj:iIAT`^E IF|e@SU߫ha=ɨo# @Q<`00a&lBx`b @U# b vqhŪ`6SЪ*%H7m N(tDTQQTR1Fa4.hc0pG|)N7T-I6c -D@TnϞݞR,|B%ER@4QOZ햶zv g@7%/0!8 ԝeubB5GUÓ2_O|P޽? ܥh]oxPBGM Q/@,?V8I!dDجi1N Q T,t{@ i;ER@USC97-bo2AhdcbHDTUۄFE2(t_q4& lGg/?is.L}լB{}W4u~Q>Lx tdVnOdF ATnOdF APȈ(@ecaT,2bUUcXjFsKaa-EETITvdrS"dX?>iI".ywwBnq{TFfEЭ3qvzZ"Ɋwεz('#q2(FuΛB>TU LdNv&sNYL0PbMJ'5EMCU=[˪mLSΆi0Xx-Z0@UQJ4-k]+}ǣtTԧ-t6ڙI L!)XAQɲ(w 5l"XcFDcH|"ĠHAg&vnUm*JFEBD7pt|&vdM|y- MEGC"h;MaIN%tڶm+wC@[0V餑PU*h6Vڮ͋V !3*]oaUSk,"Ҕ!PEĽX@m4^y䵄9Vq*0#*zY)b,ˊTl3DQ!!1:T4VWC4Mj{T'TASJC[ZjSZTHܼU %'HT@.ŏ*ATUITYO@a6:e?6栂f$VzdXHUյcszJeA[ct/Ṉ·!bd#jz 8 53tƃ:%"ְfxPG0=`MR@pPQ0t4jX i5bhNUm$Hh$mZFVGT(YK>U* V$|Jɼ'>JWzQt<E:Sv>9 B+oKmLBLJgf>DFT]%,ՆVqOo U_^t6(Lvڔ>.Bs8TY/*{Gd&d&Wŀ(`ĚxDc8IHQiҦ:0C{ي0m5T*"fZk %6f|u1Uz>jJM/蠉>ӒFbBRC?uey:9׏8stn@FSW "4^K>Xzֶ&g %Bh̟@k6>uڜֻ7,.!r6 pHWlER$&5qdY\j3Qfs0Tl jD HL6g6MGJl+TiTt.mےD[68`;-]:$*SZTn[҉K;)^:Uj)%xΦVgݡB=:]~[ҽ~Bvii@2ʹ (;'{;fCa/c(;fCa/c(#5&ЂX l2eyYUUPc-bX*i``!b3PLM5:T# Z.1q@aH#(J鴭` R4.'#d$rn]FY鄨#4Mj6sl 4 S=]!Z6Csi/sLaN0,qpl~U$JT=P~Zsr@?M L18gH"K@UG:( j( @U JiTT8PH@z (HP@B HRt 'L`AN@AHĥCY-a"_݉ܭqD>A*8t($"jblߟv1MTLk-ΧĬoKi$1?jk61&6I a$TUd,E vS- 耩^1 iUa@R& .6bh4@=A:"t)PնJ 7Ui }Yz۩J0@-jLQ촭vJi@KmZH<>˷[4L-?X ޺{O`ֵ$?'|ڏ@" M@$,tpI@E@E5@ "FLPUM R4R^01H|H T 2nS8 t"Io NUi,) L@dNeK$tTHEXH zA?tX"ÿ;4A 2Whv9[8, 0 Vj91T_zt8pK#7'K[ "NQ*EئM2)W$VdSe ``1MX 0*B ZUA(UMVT[?Oʠ{bjVOSS6Ҥ*6=-F!ՔVh+_6h+t5Tzhl*xܲ숔P j$8٤S*VV6h%%:2f&3x^Z/§7@d[smf@(5ڃi\%٦ӊx!ld$d(DVh_&IS\D]/T^\o\Y4ueԺ:"*dQ- &cBGd&b:H á@PUA bTm{v*I-C, TщM`\SH)$:J`kUĂEDm4RiҜnTu:N LQ%MQ%]T"PETMu0LT&&DHRݴ]"B.@B.@FP֔!"kH QY\YMUVjA-֪AjVU1VzTڴ iRT*=$IiN6Tҩe]$h.W gЎ&:mJЕ9@g$v2;};Qw'B[Jh2xq>ng$D}~=R&%!:dd7'ׄ'-"PT3r1aLZ\> !k+|yO*1CJn,xz" ()H,pcPŪZ"b1X1uT#$4}GD^r0:WY$Hn V:P{ڪ()Մ͑ tlU+9B7/RtQU-LU/aߑFձ")b4h+@srLuǨ(͸B88Ntej8x#.CCeJd&k+.vw3Dndkc*WŪ*j c"&APbVTlVth+U$ '2 )I*A $Jڶ.&*D"v>BEQM2O|(RO\qJ{/z&`[B!)⛎N϶A=v*H&0M]ۤ*E%3whUÄZ-yMǓ] G9'*eMs6]u8Zz;O0ql٬\7p1~b7p1~b#j ED슛,YbXA +VZQ:%B#4DJ0T( 1]M[ijгSmoTS}5@t_4U3fH{1DH(>Dz+7*NTp{.XZ9CO+e@`_)ec)8{}Žӧ6Dp2xbJ4Y#&fAA.ʛ"(Eysk(`XcA{@&4]Z=8-T?qCµS  κ:Y# ~wr~7!ag$Gp(ʺJ&#\@Qkbc-C.JHQSTW GK %'0 ,B0UuZ8/P _YC%MQ(GPGPήT@!3XQkQS n[E0"CDR֕(!FԈnT+vzχEbEb쮫GmhQTHjNҩnj\i:>QTGET![Q쭩e)ENx;M4]FT$mtkjRitmku7HRB!· RHRB!· REfYfԩ2&+6Y9f*fQPU`UM DB_jsVJCb͆oJMtnjJSmsXhdC "@\_ѳ&< /ґ&KgkFq D-~fTp@teP$ 6Tq@X&M1M1 jXU Ah O&)JFuqRBFNJSg_jmi+`wmp[ISiTvE܈-ILV-թ6E`7[Tt d#ܓ@tA(^'_mGD;p۬A3u]@r6F6>}=z!CH:}=z!CHDdjS5*UcDc#Fq1B$-Zo7*U juvOo*F5XgDH)T5mGzp,|ϋwv鈔8D#/V1crU ^ȋonґT݂熨414z1gjd*Xi@@}5Y>EhRn>ܚ,T"4)!"ˠ 0Xvz8k#ݪ5bZ1lV@ S44N4&ƎFBItjEL+ny|FwbRMnYt(Dv'MNtKRJ@qF"DM\V(`$MGh߷D6 ۪VX(R\IHJ['}+Ɇ5~Z-DݰWy$T?,?-z) X~[ʗ$%'9ZH>H9M4A%1  (  P(F8P1mAHPBmgi+idH_l;UtgmQCh( 8ݦ,AL-=4hH7*hIH$|YT) *TGUZ(Iۑ6R!NXG%~B`v4{\@PN|yOggS@ aPdn)'*(49764<7897:0/*,876*++78798657978757763464:9286877.9/74618(,(979;6*)7<=7:{%IBfՏ$,I2~4 )6fqp 8%ᬵ9 G1Lq0M0 cNcC&h,&DtoC%Ir6\H4趩\ Ѹ(R TN ۡB"DFc@E)$A""Z O 9'OKc 曲&ۙ_*V^~dב] ~f/?^H57)kSUbXG8q ( MQ4Cjڦm: %$HmۤjPKPD5-U$Ns;ځ!B4.P(U i{t591 LmĘ 0`˩6J5E34 hY 2iZR'vM.6xZ H9_t;c^LJ!i?зdͧGW !5 6PL1(AlbGBh$ 1a$A!(mM=yK-h$6 PG*h4mT*81hCS=4V yճJ9y?M[i[@`5=l$&Km2Cs XgefFhsдGx]-.*2i.5.AL[^xbx.Z"-f ,!;'I9e9WU SM[H'' ^F- hD %TMY nS)d#c egJvZE[h!QITTȁd> }k8%v n}(1i9gP8&ga}(4 ~xتVwPfòM1gUU#) >> &0 @b F2q|@UQF$]:q󝪤TFVitkḠ>%:4&&hGXav@q9V d@Sԑꉉ~ili@g_ +U(jXԮYDVmQ>jvt5{TFFXNbTհ3ԾkK- 1 b(Yj6^@y1琝 IH_8 iR3IkRjTI;.щ_ё~:$K4R{2ͭSDrFժ"tzd8.$/vmUu4<),8I@+ D[Z:θ|qHQQ $o.TO: Փj&-"4*XbL*@Jfj]W6gQE35ƊF@.HNݴ aF*dXY7֯Y-P$Ђ1Dk/۶4jۮjX^S4i"_|߁f׾@kU$!U# U- $. m<~6n*q"UW|_}_ݲnDQJ϶gUThj`}=G"7 l_/n_/XIJ# ZSU Hj:KAj*=3,v& w7]*iβp|GU3Ӝebg@5Rf &`$1g٬+(UDFV ƨ֨VojjfR:hxMVOz! д% Tˤ7 I$X@Bn6)ցj_9o `^4m6tK#1rWJAdAI?n6L-RV:_c8I˕eohfޚ&ا,]si}5.0  ܀e+IVc˹r!.dblUPh @U J74tPM [nOJW\iXQjڦAt@0~hM"ȻQiЩڴV@°Si&mj (AI3S\o;2׍ZLZћHt FIS _Hh!zmA{VٶA[1R@fx3g,E"!%QD5c@J^Rի6$EH#ITQE=ÒBAvpM%K$N!UM[M?0@'|ݑJU$BHH@_۳M(|i+mmDo <2nh,Mrd?ľ0q۟jBk`}!e*3:n찄kOeT2VǍrIJU/@tӡl* (D0ӆUCD&hu ZKc#쏖-RGuӳMEH$ft,DDe@J)V%6 #CT * CD"4IT$i:OU0栗@O π*ƹњd2*0m9a. ꪸPܚ2_&kAN5PVIZ$KVR1r Z1Aj1 0H[C RQRnhAsicHUȓ: >$$"4RdK+9hMtFnB9t&uj"  KZ|oP"JV}7/iF" c;Q:6\0v!' 9DD!!6K:@U, UA`QUUkj.L fR)a"@TNt/:ˎGTIMpx[j b9睮+VoE;!AQ:ztۦP?͚ht&mJ,ظ^9Sɡ/olC)@GoC@u T?v6d mT{3V۳g[}Dѳ=Ẳn%%.U#bCB=] r kQ%<,f uRS ;URĜgU9eDit=Ǻ@FY,V RՅЧPw|Ie4O1< [[S1P ƈHUmgϞnVUݶ-$i:4R4sm:HA"$K}$K}zj LZFu,t5KQRTTN6Ta ,K$,uȄ3K f (Fu[ $/DDo/4r YK:YBUwKz##ZA{DM0h* T&L/4RnSᜎ_.7:}${UtB]P'JmU(VUH&>SAeH˳{Wvlmmm}<j0WTTS-ṲnUȿIjȽwRЧ(@c@:im1P`莹an0HB5AAވz°+A-=Cr6EZ\쁈q nS H0cwR0.PĊv騶D۳gh5PE%I$IzK(' ' ߨV-@bAңKڦՉ "@%$H=R*gM|7zTT tzO{,k b%ȩJZYtMWItg+={g!tbQUxˡ'$E]hoZk~6M[tU(_*D|DOY& jUP\ y|/R1(ѸNS$Mj3|݄#uv @mZt͋OM*;h7T7РW| s_QZ6Z54!&!F*=zFEr\w--)/&tz/uz/ݬ* :TcZ-Dnv&t攐D8\\cxkoiAR84Tn**~}d6T'ij؟L` 7/ d.f?w[dJb i4-#-M*Չ{Z*Ji+m[=SVg-|߄o}6K0NjZ`v&UhģNAqXx+R\"[Ewքz U@BI -TK"ǒN:M7Ȍi\9mnUbQ#bґ&MGjZfs:M"Sl[8'SH0h"UDziE4a1 ٗ~cFu~Bt#.smR]6~)PX.o(CQ ,gIXl*(**0***URkC5U-i6Nӳc5!zT"NTeH"^*mi*=A]EҪ }"t])mti+M8!!\GS[;Rv.b,M&> MvҜxl,ݨJ$[ (fȑ = d^-֙JڌqT[3hRL"kM,KVU\ ` FZ+B4 C cögnQni4@n[UUUjtZ^}6zpDa ٥w߬jkҠ[IӤht 3k¿z;i"}vw d8*?`h;סl25 LO/Ȭ\/eR~@5 Apjr\V9R!U5" T5bV4bU; &G%:ޔD4/Tb@[[Mv.5P׻}HݳiZi 뼩< TΛT8l`>8fH%s*N_bC>sC>֔,9Q:֔,9Qz( VZMbre˹*`ZPXVPqpP U*eV$mUZNf5[ rRM5T[t+qC*_ 8ut[4v5&vEEwЉ-; K:.8Ն[5 ;@>ڥ"(v)d ->e@F`0현sKr@Xb )Z1EcE@ꈢzEkΔ)iQՀt4 TiIN8@A4Ni 5 / Ĉ1wI.Jn Ѝ% T4; .UD'mHRw[P%8wbW0~x2zN.%Oa/^FɥDTpY1E*WQEEM` ݂a7,&8*"JD y7w; ֕_ҥM{irh э#kTQiC'A@LB|1k1O7P:ՀPߥDmݡ{L0P!`hC 8"1L#JtDǢ VX$d=%d̀6`S\XVв$Hl1HHi=Je^IA&)HuHsR@K_v*0;8^ݖ[\c݄lV:M 4µJTEF.š#ߕjΝ$BzW T5A^k-7~UjwI;, szٱ=tBTŊB}6Ѐ-/Ux l7y ـYb6b5*ŊͪX&FX4mGӴU3EAt؎5I"UT۩+͚DC.G-{ͭ#imCҩTvɪxO_r0P?/Wj$JU~XYI2R|ol 7ЂQotuO)zH\dĈ_Rf;il.# t#R .# >LEb(&"@1Ra9RʊYjXt4 q  "qa8ˊJ1 &pq$t*`&R&ӖsM JK񉯌whgnVҶڌJt%b0ڟWG0ـNȩT! Qx G3Q!t[ ʞfCm {킒AvS*e.B"aB̅QhX$,ߝ1k`TnUѰUZ-AXq$1BDŽAlczDZw.LdW{~P]nٸR鄎x!CWMTRmTGJ$T*Nu(RT ~yJ˫U7c{6Q:jhh %}C$C!bXkvbF{y ΀OIKx)\QuuQ6 uuQ6 "9˛,b\DI@``s0a jS\F5X( Q;A!ڔFݤvU݇JEIS!~A_h:| &^>)-=:fHzKUIAݦ<]/tMSLH`:NtKuGul$W3)\ͤLX$&BQt4MvڪQnmuD_ҩpZGF,.JS@֨56wk^ס``%-i+Wo|26(TEt{t6֫8R)͂+)y5J4om/~,V%4(tIY&tȼum~0/:2Ɵ4ݺMӭ+|!ADj"mmiRUt8E_وYjVLʜB 4/ۄBzPS=BWQ_zH%+4cs'ی͝)%P,,TԪ0MMjMCs jDD I- +3QvȞ(VF6}`˯7s g Eilfy~X~@ k6_ > >..?@ qI`%+XM1Kr bZLAmF\M(6.>PLBDcB(A@4-4zvʩѕ t6BUzQ<-X>3::MU*j;+R sb:ϣ0' GZ^wU&rjSEN5TE2᪄JS3鸯2Ne /in6Jd 2*ДJޚ}ܦ~xX5hMz,f\b1 "&xVLl$xl$$ģHl q zmVIM?hIp4WRRNJ^~V4HT:IUBiUu3d '}/D+uԉؘD|ts3Mw"^L5+#Sm1dhN_um2=M$\3V)=63AcOKT$LXUd! FDD0X,T D'cECC$6"tKI= >)If>A^rmMJ)s=-VX:2u4 %It5@`Z<ʬqbw{}jė5bL5)XK۞+][/uDNS^@(HlYw$O9xY{K*Os>4 |z4/)lXVz`3/0=F?Ex" zpO,Hiӣ&,`qmr\UDaGf5. Z=tghz4mt]BVG/$q)1Aݖ4 U%" [DqāmDPAQ4\G9<膗lmhT@ -_CO D:UQyo@n[jcK`p }u }bZNۃ4>=>1=>DJUDd K0&*ǘ"UV "( `i#]m i#SZ W *EU~Ԥ@*T:Oˈ\;kNnJB&@ƀ$$:iJ7ȅhkz+8Mz8TMJ!DLœcG& 4><:ը`؀C2#<Y@fÇ`$gC{ ZE{7!,@ ȹ*2mj1ut:bvb`*! mi*i"C $nd-ur''jIJ:x~j[R&mm*m*$.* x )6ah68$,%}.Yԙڡ;(+j6m$d/E`rw{z޵9vuphV`qOggS@RaP ^8+.,:8),3/0*=9:>+rg8?aD$-wL$ PT!*UdYJx e3sVUA5 AŪj `1Ub (NF:01(HT6b*2_TAK%Qx4=@]0,ǻ74T'UYw%2mбm`U;ܶi(FIV4ElM j A*^K2~ 1ÇV_bk>Y@++XrEV*XE Dab PL,`%.„=Cu{*m >x?6[|Emit#gN4Qi@^F$(LdhE`0>]fdhhO>%4 @ƑS,yvhצs@L:*.ij7,f:'eLR߰뜔1_5eBu2 sWdUFU5*" c@Zc´Mn4A.Ej)knMvWȆҏ}"HUnӒ괁%R@N)@ITK E"})<]J=ɖkkGj1Kߣ2/]q~ث%8>ib?.+RӇ]MTH7ӳm%ي9f\U!:X#Ѹ0$& e;h{QFzt~ՖNTPHQj!_")<) ϳk^8v P noS5m jO[IcCS3U w+.Jjjjˣŀau@@F[a u>dTXx:o2p`P+Dd3U+$ T`ZA Qau )YMT"M*2-9ڪNEJ~|Z+"RTH/}tk皈?BVQ`@' RUQY*d VFY'9:2`zѢшr& >mÚ,"v ^~{Hfѽ$-7]%/@+acL<@bX15%R`WiZE(RcjTuMZTiYMT` 0&F a;`DX$M]xg?΃x6#\l[hj%M RUQA+g֕-(EPCCQ+^`윁{g9`48^uW&^Fwio:9)Ji;i>-. Փ|-\ț:ڤyb {dI{JN)<U+=gF\[ԭHJc+"L"rCÛv۱q=U0g@0dgsamrhr*[eT)j@ G C"A  bXiSF4mM}XK=]{.wNsFuuTJҳQM6E#L<)I^$$̋3d:=I=M<Eub(PnvlOSy t `t:Q2>":ފ<&Y,Sn poZ&+q帪bj:vlDa46>`}DNMr~phtKdsx64m mRcB`r鈄%%t]8 ,lWhJJي _aQADžbf{G..p@ CT#m ^}Z|]DX/Ӓ2$~@VY[ l@YkmrEBW!`1bFnwPPJb dU)U]齦wtpImө-IAn롥!Pkv+PtTh&.\Tv*쯳TI5*MqJ3 3\@qjp0m\hr(.T<- W3G;Tp"[WJU:Ҥ2586#(/s9 oL9@~i.[l媘U& 4 UQk" sbP4XH[&@H 'b!mpxK˲GX}QJ6(C>iK{V\ҲE >0āg=&~ dmH4b2As liۭ;q:%0@*w狸6=%.t~P 21VEĢo5cK1 ېZT U7ç0 B kLVuJȞV,T=T6.mRE`Ԩ %=T{@Ak:ѣzVō|.ooV[v0@%NJ݅.z4:y[A$. ɁJC tiNW %7יj&6գE4M~:K&chl؄hԾE\`oRP`n!ʹtFq-]DpMͤJij\: vN>XATMmt{SDHE7зt5nTRV5t/Ћ*bG(k^U/!M+KVr*G".6lj3E^`ƈѨh| Q4 cb9m"Hs9Z"I@W{ Tit+Qڪ q@X#߱>R)gFḨ m(XPO!Ꞥ֫zxRNL@&h?)諱M^XD#x1սM\b'n p ּ+1I&6yWb*.ʢBbS2.0nM,X,kVTMa7,VG1ED1S:{.բJpHURUF=TCpL&V>qJ'tR=DHUi Wmm`GS wu| R|`9(0C ]7*%[02;v A5;#!a#e Ef;e Ef;Yl,$TLnZ4a3TjlQhRUȨvrQ}]қf DtW7cV9TpuҪҴӴV m o=RER+:VkAN6/1ChsTt;6Պ,RjK`*r:|Gbhҽ57>Wbu#uw"zjyTܺ:;}AQ," ,[I95R`ԠFZ#"*XED  С@V!GIߡtPUThF|Mg?gK'B̺*ͤ(4Lz眺)-Zf&=s;z]kk~@bth:MR&ʡwj Ђ:d1%Y<O1bK-$#կ[n'75m[n+PYU2Ġ="U e(L&Ji 2   ұ;JuUb ^A8[};u@RE!"o(FF 2XiRmzգSiZx>duT6 Mf osHXV Mvf-%*jC$Ѽa=7 v4?@Q QM6P7`9ǜI<" kQ5(4HB7@T.1 !-tE:@G4% sQzi*h; H5ѶH(Fy-4ܕ#x.w!M +Wԇ_ꤢmhRiqdl98 Lz^` M ~Fn&5>4R Mj(| aŸr\5"Y!$0XUQJU(ZAI XSQբ>Whr^:`Sr-/0RU!jH Pj1@GTU:;W=u5\e `FҤM$JI$@,-d"2MU*Ii锪4MEJio6}5Exyݜ1?(˂\=:ǫ^,ȕ9ܣ3[|"B50YI1ṮʹX#ZQ(!^T:hzjۆj{FUBJҨ$MjqChմрSJ> *MmC}LJ 6动㣢& lx;4*nm#i"]*c 3;+Ōo Nپ=ڭj7 ߸‡ʕzcʕzcJ-6aܬ$7UHhvQh(6A5al$* AnI!MS yUʄXMz6C VC.U/㉦Iti*IڮzD|XBѪVbFM!iz jxv%J@LSVJy9AtOFr]h!m7|Iiԇ"A\.0#P -ܜ.i"m]nN߈y>M$r%,ǘŸb,`X-PY6H*Q(gJ]-WŤt@3uckG*zI"F'nv? 0lw_ALߪnZ *4(oMu*mj\]-tG'UmTR!Mb"gxlUpTnk2[Jʭ7r+ĥA(_P %DԜf6Ɋc@VU1(DT4$h2F8PU**("@izNۆ:((8LZ*жTuRBܰVJK:mHeҤST'+EUq\t,+( 0-鋊i0pJ j"JIUjT׵MUVm'ieG Wd4)Y P\qeP D 2%b^!.Bj U(&bu@A"1Qɉ$,cGa+>~p\AG P KSiXC!ÐWۭH}O{#W_pQIUG=22RoMo>˽r"<ɞ:˽r"<ɞzӲfq$q :$U,UAD01aLT$$ 㢆0.CڦmqEv1dAy7RvZmP, JݡBihgtu2Tt*]E[U*mR*a#]hA.=6U k@~Vi:)a[c8_ʽeJ&r/g"n!oM(6fMXs.B\F5VcAT&pDaXA(!MҶ:?m'^*y=V/Km2IOml6'\UM5  @$T0bĄQ;DJTzVhӗ[J6mUtP P-=1BǁG@=22"^!FxU HX #&/er^$F E9}Nt"BcO(5{]9 ~y<GGa5= ?> qzdureUb:Xv J$ ØHaLHL rڻ.T* M (HTL<: W$Mz\ Qz$(K/˛h* u.ѴMRmc$4eVuCr6+H!mpZj#xAU7+N;x\! Rx2NqucK8}g$MYέrUA4. lABBɎ b`vBmڴhnQ!L]rU*@Ro/C zzv!iQB'﹩VG Vӂr*uTL )8T\@0EGun$e&szl1j)ffmLل5Н[xħ g[Ǜ)@|p{&Yb˱rŪRLL8$DD"a"!Vl|BٱH4A TT #J-~>X8JCLG9KمJ-!|+!>j!ltÇ/=,!@z޻ZFZ`-䳊-H0ad܎w(&c .C:I=f0^5cb䤮足N xpWlb励 QQH1Jġ ¶Ttz6хy+4XiFdJ[Uz?TKR3OYJCaN#ȱ~ݙɏեcSzL & ,OTL{ 4X'[57N pw+f`[Wepڈ>x=<^MJW>D0+Y̲c9U"bZ UlaHĎ*!٨(6vmp[ݛY* 2SEpp rIk;EQdmfq5f\_fe'Kh1?<UAtGV˯[eaGU)Rnvw5fX;wꔥ/wꔥ/pe1Ǫ*X1Tl8GfQCC XF8鴥,K @ Q!5җ(JԢ6R"|-wDw Yhen~[ݢN}hwbt4eۋ//t[,Q$I"Ձ[fG[u!4 186#$AD6ם \pC|vwcÂ"9x >,h);lbXUUM DET5&b$Z[j{ѣ[cA)JVDj~hKG#>AoxM51zWR%VmϮNETUU~]t }K/fT ms^v ՝۝'gC'0͢v͒ƁGG~ih[4o[ѰЍ*d w~n`!gIr>8+9W&L 8Pl  QڊQQaQHzt=a/cjn:|,{7[&;ukI m6HgSIKTU"(jQR):3S#$c<}LF?%us- M0( 4NS5B@TӐ%!9Gcz}.T V`m@@ ~v$¦{v$¦{ $4YYUUU`-トbIFAqN7T+Q݊(p|MJmhhA 9G@pD6*a%`]ܯo*H!(uele+8=etV]S J|FL@ּbXÅ 1^wd:S`1uG&P>pfwv3+WUe9R QD ! - (v|  RP%)4mU*ThU,eK i/~Q>TrRP098^.kWm-6/?.M6,'~@8.N*$-U^7vd Dqp0EfkgZjjt:9iA؄޵]c[wf <߸0Khx0f󦬘UUU51b#qa9 .>.X bQ HH Q()RM: `GJAZ㏵WRnnc $kcJ<#YR nBS(kVh yh5ZOmliX5s 6nns#l~]8^ܬw^ t&twqJ,Hp>N)Ͳ,bL,`Z4UL"" #1*& )i;m42ߓ_S8Ng@ ~ +C[H m;))iP{e.%Q!*5W P@8b!G>\A26jz6$eց6Lx| ZDwfwEC!^q}! xg!,WUUD#ØX"O11&6XuҳgIiMSM[n*TSJqRM!OEE%J⨒PI`B IRu7(R0?yx9~lz>P`mZ~HWCLz vlWixs&S, l7 ~#HO v#HO vO9K6ݦ!8,((9aԎAHtn&VtTiRm4Mݶ}Ӥ~1i㑷0|!_k3c?[ʾ~/^־}qNKtI4QUUH-#6 űZZH 9a7ιgu8:C;Aw xf!hnؿ X|v5wM1; pXB`)(5^ c:$@D+G-vU*m[:۶fC%Dv7QELzJ>%42Ɓl0',X42~HTpa$kq/\,Z: `Y>&WY" bPQ5BDEB@a0G$!h$LiiJ~:.BEB_i5dP=pt#/QiDEiR"WJNުjQ)/=o޷NO;3 h971Il74lqRMNX~ Re!&o<`!?8{.o*HҕbKy#Z HWK-Mw&jeU*8Z! $T%RAhEp4PntZMiU^zZZNkRTM5U:,Q <iQҍIII*_RVum3(H=MoJ:-ҵGuX"2BNB%e2ZA?sŻ 81l-w^VyY2ne!X ٶăeX֋@4m,F(B( b"8UtЦM)~B|KE鴡 ) Sg?m(R=HҥDjUh! y@MI>Ͼ`>.h-#  @ =et.n χ a@Z"F50> > SG,@UE%e?Q3vp*Yv<y'P`ZEkCHiѯ醂Wn@4B M "n!fDB0k `*Vz&k@(a|4dc>߷V)%tz1q4J'%(- ѪVi۪VЀJHBjkT( 0-ІcA·7<Ӝ)f%YS̀;,$r{YQM"&rbs.dbAA E CDԦ"jqP@VpOPRtT(0V]U4Ҵj`A| ݊D* 0ۙ'@(-2l~ڕJOnόMNRMz;I"$D>&A "1fmbV &AHe9$U+1XDm*F5 bT&MԊI3}U g+UA ~Yu#qCY!IUMP@C{"#y5V.~6QUt6-m@.(-mĭ=\4*r8N>`ybmU$ *뵂`v޺5bں5b0̂I6nbLMIU%`wTpSM`pC#& UuHӳG+Żh()RMZ#}mV?'oxNVUhGJ*]݆/HqHm|֫$ÆJ:U)#%MjUE8lV itPR]]Ʈ&J| `wsR7`*iv,JJGA/K(7IeWU*`1LEq0p#;b b#1@\|'R^^}5AEn(tI$F*D#J^M%h0i7E~">Mw ,fz d=ߚɷx4?fYF u -׽4~4|U i€f_jc5H*as$orV9bSPEP($j9h :M۴*z Gtō}!Gh K֬ܤVFiʭ HTIiK) @rA >eA(Efl/+*/R:̽0tkX,Xo-Cc`^:-R#*_Ed0tZFTl*`Hm+fU\UUq)&bZLSLa"#J065蘒y*%iK 'u#&Z0JKRGA*MSСOUe;ԣ9nqw|UQ-t= N˂Aݦ^AfWϲP!42[Gs}:؆˘!'@?v4R,;u@#iXv7hF/hX̲cYrZ `8aU fw٭`Vb1-tBc`ZD_MjB#+%$FF }6w# &ǀ0sȧztO|U]G黗=C+3 M˗/uի➁٨Q5wr>KPŨ=A.te[+a(}.mYnϾiTf[lLj2#z % 1Gsٹ| g&1L!>FGlctĮ(Td%|9Ds6>Gc}o`V6Q Y͗qJ&p^A3QqĪi28tϞ/f9Օ/_eGd ẚ2 B_H 4X>RDb &p=ylE.@51KU@ AQ)U@t)]xE 썺Y@2S Ԉm[h\L|hLУP]mB7TW"l5&,EXH Tj)TA(aHLթJz3F&* MuQe Vs'C5r-d[ $nY$EEM嬜Bs*2C#b Tjf& 018,TTTPm4I+$R?94qQ RBG4Ce.+-p'kw7|3)~6 nxjN/, YitD{Ǫ_B7e;㨛thǍlpPfZ^x0 ~J&›2"rJ&›2" Ȩʄ'=Yy0jUkPX 3FE:mH v;" +AT*]ĝ VS@< V&hSz:6[2]&M&v.q8%PբhNR(:siPv fC& V[#QE(biit*$ՓtJu ).l$sf(%o/wJ547VISU2%J `zuDB{O$S\\UEpt"vP, 0jZi4 5Eit@A<, @'$ ڦ2FDyDm J-k@x !AԈhҾI5C`R%BiTST*҆}lT{/ZűvhlLؚpnNseևi(.doI C|5BP4L,Dj2]^*+ET'(Sʃ"]|!^P@*jd1˙XU ֢jUDUZXU5VAEAXkp„K!.n!nҕ-rnl[q_`%\ hՆ-A96T|\$ #4 N~t;J~i韭Yeg,g bW˅⽆ Y'1ӄ 2 e.57OȐ{'O?F% ybuIeBmg4&sUpM4&P" qAl\ETӳ)մMQmjVT 4z" Pt& ੹~%0-ʾMۣэg)8{.Տ-OZ3:`\VbfԐ;'okgװL |hv\6КeLPl0J^{O+1Yʔg+SJ I,PlTQ1զ GPCL+#&XۡbJӄvZ %z0xҡҖ/jM m콶GȓTZ)J46NvT/I QNwYb3ɷ2Lu~8z1}.X3(D[iTJS6nU e 1DN^G4&cu M,gPKGdM&~I3C~AmY@ }4ͦC<)ET *+F,j*bQ; )`N4-BխC%H'G@ T"68B"Ui4@B*JJRI۳nGvD-; 6Xev٘(OѐJc--P%Qt5JGNۈnC"Λf3}aLրat)ZbM# \UUn"I䪒d@&eEIFslr˲FP˪* "bstQXPQ0%&(1HI S)h+x)\Ꙧt(D,TuCS8e[(VRތ jYHXQ +hɈ7AѕT *#! @#gT+ADF!hBrq\Fv= 9@@ 6 ~Mܚ,1{rSndܓdzY ZPֹGl@"08$ jb(bѦx b,zv*% UjtSnp[Q%zv3h\+T(:z4RbG@4x-:ѴjT]s}Ye.d-SJ?bEvIhVE4}5;l2`, S ¹4R >56XJ>ṂZco% :20ӻMxR98$R1 av,aE„FcP$b2$Ec%żS4FU#DŽ8ku*RJDҦJ6MbV ~&p`CR)đ %]lL0rNZ*MQtRffhYig)֧9-T֯n y -+_P@P$@C$,'U, !e!QkTT1((1knS@X ppPuBx16q\m7UUFlϬ^+[ cݻm4M7gR>OHM:Eh7T> h DKY^Yp^um-I.H Rm{eCF/?}AQUPIdȚl@3`c!H + (j c@TU@X삚VG+8b TJ OWM1"૊.i RRr&m*Mm)K `p'bH/dQm0H݀ t~U>|W紑Q0.C5- J]{)&b2wwy<ڼdsr&GbjA 6-X GBPM7hiq5tzF$ ¨&"Ttc*GW Tk NV42B ,xBy C|1딒q24NLju\2~j}=qsj}=qhkYl@c+jͮ6A0łb8UtF'aQ\40'B_2v ҡ ֩SDF)jZR!G",cA]Fa ޺CB _̱*Eh|JvWJHlLxg!irM?0vI=i\Rr@h""*ʬd!"lŪsUUUA5jŊ"j4DIbaئhT)}"=r*]JQ(+?tҔR)C<:Ti;]Hhe~qmB{0iņ ui0 ܃eR.+W*Fn]GzKM3'ծN]E5]ӳq>f$S'L2uܺuL)Xk"%MOImu}o|^_Kڟ< mDպJRc &S^׋99wӘ~})K|, 㥚AzB斪"TEhRMjxڙĥyB_v#{ 3+KrF7ɢ2CY[7#3 "M>Y9,Q#j jֈVh'JG4ڪjt$yo4hzBҩVi:򵅱JVZrԷDsQ 0|-nzf|yyy&JqYf9'sٺ<˅T;r#! \uUَof柙IAn|!B3*^6OggS@aP M---639.6:5;B>?7A9<<<:<;;<;5478;4,/.(87 % C +GP_4(2!50)I6fŕ!,W,*b*&5" =*t[b HzVooB(J'DU**ՉPHh8!8zoSQm϶B5"ѶD J i0[fEuE @~$B@u 4WtY{FTMCԁ;`ڇ]]ж|-B(Z ږ%ob]($ukd7MDryUrb V5րbEX8V"LBwЈV#P(P_:m+tNMK',ɼu|NTiI*(U_EO 馍4h{ɦk5V3x_Za["-t|m??fM$(lGzM^]<b̥ܕ˳!oQ=P72AM!Ya,r$j5*0: vP(pTUh;E5ƒBxZH@R"w W_)HTMzvh+%0sLISB6=۞mQDT7 G2w6Ӡ H\s(oq?t6-u%5hH-<ϟ/}4ޚݲy PBkv1CI $SOb؀hX\,R18:Za5M UI D㢑9pR-x pW6 )Jީ|ĖUk;ҡ*{m IU(2LV6bXRU5j1bDQ"bD:0K'IJGT#7Ebpϥj*TEOE4.lCSe־(MG€5Ze_¹y}! ur;\|x#: T**mdBu@,Bcy7@o ^~x%cDo^H$I qeY.*WU-8j$LLI<0 PCA|bQNE%%aVQ,[R5Z%U]M~1{PV P%0 n0n*rQ Ю] +Fx̱Ѷ<]#LػgCv>^Nyؤ}Bwx>8px%˃˚ !xB27x)G!Ӆd&8 KbXUUCj$% M4X -}b%^N*m Xb+d ;݉!9-ݦ\uI?u7uy;JJ@Qs1hk7k}2<0'EV!m*|cvŘseY9+jwT18vMUnEp9JQŋhHm: UIt4D0|A鋗҈eZ!qVdu)$ ŕ]R_GsWaa@K6=!/wwP[@J@w;mhŠENNPcM}9C_<n Yn&WadwS@C %}U!F ocN*y;s. $n`*MqVxMv@wjd|{ǫY@H0 Kh,gYX*VG ]Mj(*a`q0"#Bq$&;!T+/,5η QTi[R]MRJEI5 }2"w*JԀ|V*\ڒ4umw-%o?>Wc2Zb9*Ar{vN ?pT+fgi!@GW\4^w\dqH Yr~ `79YVXհa61G؄DbrA\hnIF+jGIVWȋ?hh!LeJ Ex }f]a,پV;\2|H1Df/gQSTw!7Nv1.tY-YGr҅ii[U.{lOxnmCx6vL+K[;&V%b&ˆ,Y.*WU"Vq"5t)=KvU`P`[c+7@DyB$- X[dQkX-P$8li6|WP$[ :E|cJS4ۺ(dV$lVfa̳Sh8;MF ~wtj58 wkd&r1VU566N A&P@Rq*p xN ֐JB:xgn{ /VY:k`TyY<+l1Yϱ>s-,SERϟj8л-USGPqN=HNq]iٝCE"ES4ByrH_~w]M Hʽ;ή$Rg LVd\,˱j3M;b XTFHh 5Pa$8$_TM'"I̚SJWxi._O#jnjV\MNcV$Jj#U% " j\Y;Os|ESo>{AI݆I *F5SlehM^Nuppmw\JIHR~ MbcXjQv@Ւbhx4! ZJD! A %Fy Wvd,2V mTC:_`[8c"X%:"* I{:YE&x~(1AHƟecnE؞؎U"5# z֮A>IZqa:rw ɽ]13oeg/ݢt~wXJTI{{$`"1k/ ǮuZ~P\G3a7wSj7849M9˲,sVX5>becpLaDc BGztz4=jvI* ՔRMt+M DR.uDA* o!q(MҊ8hb=!w!-RN>TހIGcnSk 7T߽=$r-X DA"l^Kwv=QIXriw{Lps &fr*VUѦvD0lI$a I$/G TT`E-C*Rmv+F+U$1~ "dd5^u 5Ǫq~w,ݲ%%"`7Aؑ$;?-.|ĻH^ wֹ }W 'D QB'"Pۺt>x{K'{OeUŪݴ,a1l`S`JDA%ۣV&z4ISIT-M K[,\b{Ud4nԁt kFmL'Vʅ0wRVTFDtwW`0ӡEs"q\$tc/:t`|< ADT#l= 4bw n=ڼO>Ʋ9ϖX V/4" &h19DpEAs='5O"}f)e}S3 Il~qWggvFSw\~Pr|yuLP%(wŘ7Ŝ1WUt`5԰:(60D%NumRiim;MDiYnU zҍ]P'''PMmRm_D2-Wtsp#H󎠳 93E-tݸ" ;Hǎ럌_FMzFAP bn>Uit:UmJ*ճIK$McLirNJv,T/zCV.KI"0`lE#)1EHmht[pC!<0>1bPDa( _$l4fI&][;碌gP"]c &m}9Va^ *]'X z17-+f:Ŝc*UfpPCl bEɎw@W[)@EKn7ٺIتم6Xalya#ҖtA `Dg3ͫ@~K ve)q똣㹞;ңo _!I٭;497P08~vUhL}u`-/z \߇[gOf¥!A'!&`bL9+Ƽrr!E5ZVqD+ Vq4mg M4t[ [:L i6F P\āA>h|En[+(:6=4ĴQL5*׾EDI'ls\[8C"CFfo@NNi6B`ڏU lYv|""SVz; XyeI aHE*ZcZkZ, p Y-Є4i=۞&M+_ hжtQwՍHQRZjJ7#%mU9 $PMTOz HMn3aѣg)Tʗbðԗ]ܮ|[U +4\޼p\_!H{ !C05hF)DV$ Gڛ \ㅑ # @!ba1CT%o= -ZF\z,X1Tu{vUؘFCKnUUkcqtߍs `n0T)D:g[=:)< mj$FUHdESƢ)B@ktVmJ*5'ꔫגilyAjޭ[-"Pq hłC&UmEa "X'΃j$mnϮ*d[f[:u#`BiC=!1T@*No|㗃/Mj&qbmhttC89GT[ݮڞR|[WިMj&3JVG*mS:AC7Rt'מO=wEm&*@cPPP\|TQDM@$& ~kE&,nu,?xtT /@1iHT4a0 n5I6[ )Hly ov/@3(Bՠ d"gj?0H ALN'y[qa0HF0|[UWZu"yD,c*Vv+MTNh:V}*R dڞmMc(9JW(]uk*0-E5jDLt{T[6ӴI #P'IUt;a|'8ff03HsDJp ߍtӨ|H}-tm0tn+ot`$@(",-t;)6IY~sm*E1ZNT!Țon&"*4#r4i-N;R/,ߑ}I)d1VTƊD`H$@ PӤx_q ;R:H]-,3Ftn+A$.."!4HHuےhn% Ʃc~u[lͩǷ7Y(!:MMWmt*U=ҩBA m en;rG6-50@QUQ=UVUnR)ن6[]j8٥ިX,5FQT+M&h Mbta=nV2S ǧ[d X zURmOUՕngZ M0!BѴKUM:J-囊 7{wZDnK֨BGi P:m"Em(ZD *P20o`{J5]FT&-6Pi:it#ݦ(yZͫ:P ![!j+i jSEx3:UVHB4kdlKlʞc[`pl׷ºc(-}ǖ?:42\-}ǖ?:42\ @'&$;wF'խ*m |͆ Y-suNtͳ[꜐P[ʚo =] `&qL"RdSj-8ZTTD@ bQ! jh# `1T-4@'aEi\ Au@ J5\2h= iU揄NUR6}p IJ)CIF*XS𓬺 @簯KUڴ&ݦ6U,  .UWu}D)cŽn߾Y( * %{^ʊ Q"JSⒽebeń(܃ 8M\ )T LôVP 4 mq1hi;JYDYۖqLUitTU E+t}MSmm!=R J*"--m'bMH'()|M$6v( %'Q4qT6XDl+ OggS@_aP .˶bcJ ɤn |Xu%LgdRi@x0YY$P #RGD b5-jSS "  UN< Q.G$a  CK9Qh  ٢HH϶:m *V*NP !\M;.o@;Wܕ ڮj:TT[ q䟡6(l:݄ EVu+$FJ] A^*&R)arf8l0˗m:MہĞ 'M@Z+O(r|9hS,۱)=\fSCYZ^ۚ#M:xkE)9IuMc˜ºԀj5@5o`hq2*B9 UU(Ĵ * `њ 1 @ b@E hh@@@bb P' $ him@8!0t | NT&P)R X j@G Z4{~+ƟQO)J lF=JBQPg#Ma@^:aAaMѶt̃Â`GÚS5-6/bGmbVn@$,G`#VL0U&GnJt:%S$U!頪ZAU*JThSr+ =5yDC3`>ּ=R@ZܺZ5,fAaZ"vhiTGMLʲ.MU)T P+hR^=;E:=MgiTj+H{@+ J>v |Dh?LD+#*JP6ܵ)4Rj РDŽmMDiQhċ^DMtQ fd A3.هH O蒪j:!tڦЎǙ*%iI.,kഊ$ w}zJ,6A e `s*+P`QUc55 #`;^諸mI.L[ףI>P8{#ȏ/*eiRIǡ|3z6-EiѤ졇JEbnybǿC-YnQtwotݸ}pc'2<l9% ?Z5rv ~+VM`_P+5(;@ gYH!UjUPƈ"J "mIVׂ;H6Ba9UN$;n CFURNLоHJݶ-n lbe=ȓ~] tCqUKlF!5ބ\L( W@S9)4ڥV(>\2_.%@"*}@Q vs&$[9\EL0jk ja"jhRe IHEҤTu{`@P"颪IӴP([[*"TJU~ آThh*=:ݦ*(nS.!˒wdGZv6L|HޅҪn)MItJiXLH7.av2 * D8?"`E'k >@뗏okW{v_>P(HZcp\sβ !biSG{ڞEgJiUMR r"Ы_K*:զT=bcSi ɅNw\a"0Q˞Kŭ)Thdp`hx =AF{Y\S|jH5P@d oVIHn4 03elT&`nu"P&WT$80~ꁲ%XWrA5*EE1 f+&șViڎ$Bi@RMH±ڽZ?4mW5h! ʤm SB&0s2}h; tF"޴AAdC\/1CaTbAVvty:z^ )fpd9a-h7>lbMMt%V9WYUUvhզb`X5!8A" 鴊p<:IURm[Pr%^c e ~b2IxkaR'%kQO@i{(թ67m8R.hQf#` M M_ 8 HT"v44u|(:]>v ʝQ,{qc W:dًK{O@,Y,\bBrAX+XAb*6{CP KAĪ MN5,ѓ^FLSUIh 4JS[mG%4`b?MRMښF)XnQ&[hZCPtY~#^3nunkߢ+tHi8$K< vQS ޺m.[V^%@j@&. rBB VE FESUVbV1U1:hJ T?>{">_Xur.l8 ^R4 TnFS(i]cni$$¸o>e|Ȥl-"T*JTU[;3@ըD.@S,@L7 ճtb φ })c!"񘍅-jU;1 [< *AY2e9 Ίd b 1PLڭ`5M6 G+EX6r"BVY{#R+r02Nl;C*mJLXV I 20 TS:B ?r˳b k)U v $+@^æBuM1@ެk(2(zf=8`EoDD-2n`Y,Qqs  ZjM5 X %8N\&$.T,BZE4D:=UDGWH+M+KC餭HQ-o!%jUF ccls% DHpZfOI*4mMҔl>h+\jT7^]>$8.!dG0" m19;6]AMzyN^+hI/:zI4@YeIru0 *VTPvfCX( jTmꦉ֡9d$7d;&|J62_)BSNkTzI[qTj8V ZYiC3sTQmituN ZM*M4 DO ֊EO{G,%d5=YAXfOVa/~%=W;z%f˲$'IyRl6G1VkTQUHI0 mۦv0@%RiV+UQlaQ\!@)wF| is1[%5J" SUa)sC]lG[GwslڏŠ#.~YVtq˂4ŽYA%1g9*TcĊ"Pш1D*J $Ym; ֔ܟI[mju흚SЄOB#B^(DM[JT4J 4Bel[U!Q3U eذЅ&TgJn€] ƒDMŪ=ؚuO= ũD3p҈Z~IFH=`,MY,6YXU@QlPi7TÎiUAum$=ӕLTG[8Vek^t9CTIh&w@ ˣO~b"zRUzٝ괭Z+Ĩ!/b1U,E &ޏ GU7c}I@$({ ފ=pk^,^:+׊=pk^,^:+!&Y<ŁfHn5M VTӰ8>HH$t$.|کRQ8L?OJj"+Br+r)%R SIR(KeJUy4̣i1e5IT=ZmuTSJ}c@Ej[Ӊ5[BmtE 3Yr"&9Y!O^ݙO2~+H43d >V,%ʊr̹ bԊD,g"QbQhTB0mIӳr6pj zm>~]ҕ6 JR*RQBݒFWN&n:BI_{ڀ1S @LJ nqO[TEUҶVRTC{*Fԯ0j[7 [$ =޷4mzhK#6-tFi 0@^bKR\>th4;ŖH|zRHX27˒$fah*V.fQ (V"DbBKs(m:H'4޷:vmDmG ߯ݥT'IRNLIA[#Aǯ1Жevb@VR:5$*2J|Aַgdr.J{F*keS* ҊDe3Y``?j}ӧVMT+Hm>ej&f+U""6SH#ECG쀘ؐ8B١hT#m=;5eTMIu*e$x"NmW)#g6LL "P XӥJSBn؁woD2G,/JT0V2W^`B091.ty @5{Fѐ>}I>}Iw )TX aU݂UUb1Ha0H$  ]NMh5/JDw!PJ:i۞"bPA @JڦI DTh 4vmST[4MҥI"J{%.2rlelZ:M\`Mz6U A'[~x4:BW߶*{x4:BW߶*{&+ƲM*EPHKEAT8a$Q"1ȁ#J8 "V"+C#DAnJQo_͑r >Җ#T T8^Mi6RkCOYg gf @&TsL)D&Y*E\T=/ˁ6x\+P>-x\+P>-{6Y$,UU%Pvlʊ :B"A*w@r\`H% LDl.nUOp-I F?-^pҸ@%A:+vFy+G=CePC0o׮%n )fbޗ1Cx1[Z=\$ʂ 1jwHLa&w좖B^;w좖B^{OsXXUQ jS c1E\E," 46#UZXoj;P-4vچ4KhDr4h^L &t6ٟ۟9 U`@æ5˅H/a\+n!!o Anh>H %7-4̦lw}'}ܽ?Te,U#b!$D-9ATvJ϶ӨjUNSmBMO=e6.F;l2uިrln A 8Fnvο"L8]?ۂXkZ$1P5U#UPvDtRGgk-w\JR." degU,WUH\Bq(T18 6tPaDSд:< ;%OrW)FD2)m k[ͳrn׻BhwT7i#AQ-dűQI3r8NaibՀ)D~ǟCV/p,lBB,[qmC6w\{ >@w\{ >@{65 +NJ1"js%vH:+GЪڦnآ_-bgzi1v{@ ӦiB)!E;xr|, uf~d{NA㩮[TPM7n )" ڛ{;jښk!)Z,T 8Tw[I)>4dܾJJ!+fe,U\媪6q0MSL4VG*"1ѠSEt+ 8R-Ruj-_#]WӳGIN'aNwqРкO@\Q[{ ҍ$jgLҥ%sQFi$Fj/[\z*]Ѻo)s|0+[ ~wǔĥ68)K5m٘$*sUEN$FQ+1c116&H % CitIkIGa=wGjo,\[J[4@&,!+rUaCP eCD3.!zYCCî, T;M6~w\jJ ʽ;.5 Mòrι\UU&aBA4bqADB;΁"a b BqR !Uf UY_]z7wkeGP>ӣVj$Rk)xpwt_i#1`ZQv7[BSaX]iմؗ_QR(fϔC ,T}l-3똬(5ZUR:-`^w\zLR*<eY1bj!bCŮ%H$D GU b hmt*5\(8hiڋ*q8"1ʪTGY鴮0&^QL׷LC_bI& wU;4t]t È Od . 8SK 9XA>w\p*\cD㜘1! H$FĨvM:Ճ*%MJG4> d%z6{< *tlnF!.%c779yE qDu<0.iw\`3Q|'Kcv* j)?_sJ,WAH ;NZƪ gr|4]0b w\@quN}媲c\ݴ 6ӂbe'#^٥Qҳi/LOt0/qϝ}8eųpd;s}e_,7;;&<+ʢ@ jz#Z ѝ*Tu tB +A`_<ħIzX^BtL6H(͡%|KZWPm |%_Tsbβb !ZTGI@|LĎjW^FUuZ=*JII)6p#<;??n)O41<^3vS)]]H*gNkˁ;'INKa:u2S̔03OggS waPa/ipNC55ޕY{h @Wg)`7a,) ) @GPm<-Ca'Bn>K(_P'l5A*#$dK * Q w/ _1 I`1,FHhP ]Yt;HU*HGȑDي# b x+  p >o[B %ۖpoR)PcX` .$OD#vTY-% h( %t ,F$h"rL@JDڡ B7 8^YJ䯥.I#T6h A>_0_0K$6&6TNU ffmpegfs-2.50/test/srcdir/cuesheet/0000755000175000017500000000000015052412650013050 5ffmpegfs-2.50/test/srcdir/cuesheet/raven_e_cuesheet.flac0000644000175000017500001345464715052412650017153 fLaC"3s Cp=Qn x&-VNi Lavf58.20.100ARTIST=Edgar Allan PoeTITLE=The RavenENCODER=Lavf58.20.100 CUESHEET=REM COMMENT "fre:ac - free audio converter " PERFORMER "Edgar Allan Poe" TITLE "The Raven" FILE "raven_e_cuesheet.flac" WAVE TRACK 01 AUDIO TITLE "01-raven_e" INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "02-raven_e" INDEX 01 00:04:57 TRACK 03 AUDIO TITLE "03-raven_e" INDEX 01 00:07:74 TRACK 04 AUDIO TITLE "04-raven_e" INDEX 01 00:09:36 TRACK 05 AUDIO TITLE "05-raven_e" INDEX 01 00:11:44 TRACK 06 AUDIO TITLE "06-raven_e" INDEX 01 00:13:67 TRACK 07 AUDIO TITLE "07-raven_e" INDEX 01 00:16:01 TRACK 08 AUDIO TITLE "08-raven_e" INDEX 01 00:18:37 TRACK 09 AUDIO TITLE "09-raven_e" INDEX 01 00:21:03 TRACK 10 AUDIO TITLE "10-raven_e" INDEX 01 00:22:43 YpLZ f绨dow_4'f'$X8}a80b^++-:ucCT3Qغ*KD #nii08f|@ UժB}%\91mQX\ڍ[ӿUdz;vȅ,fo42 \ΌAiIO< 9>1K(E%l v!P+KC$VU$F"{9?WҍS * UY7X`]<4zVҼ/Zgf'Fh٧k*QR(tOAB5|@ ,*>5:dϙDn&7Yz9 HڈmQ|pwÙĵ۝u |E}R_!ƻ9 shi|Kxg F$ vMԉO]9;)2VKi,sXd|QӬrdGM8䥸]xIж9EB5AO3OrR=L@V@bxN 6#͓ N]'6+.I-q(64f#}zbf rSe]xk6@Œ*c٫ ˨hl3& )i "y_ycTr5_4 L-Q;v!Eܱiё^FQ(BQVFRl[0jkSLkIf*E٬4cYt镕z؊\Dm+A9)_c3Ȯ*&ɩ;t7ޣԃ[1ZbW sb!@S!4_ m!!@M@竴G " v}>7^yQH\RJWZ4bWɺm% [)BRO DIg/`4TIsk֒C::.H*ضrI}eOVy 3g3u0$YjFƍjI9QTrdپ]YL1I'n*!8w8'Y6Ź]9_]S٢ i |,/U)tٿBYS42N4!A, $MThVζx~\.L "ђ7R̰muKZ :' q8k%du))\)vr1nZAVxAEUGmD[F ^U߻5HDk22cUq|oNlI*AImYSˍw=W[EߠW.WɌL:,96m{+QМ잶BQ XTWUh](O"D2Qff;Hl;?gy.b?!oSVPR) jT'KԿ?AL0z3؎k+ޗ=\uC lOƈE70|LMGjLGzXf"B[lK&9\\^O %´{&1 X7U|HIDܝKO.#u]"7\[ρR #։ځq Dqu4)INRvIqP*IA޻sp%ћfwd9,0x-fӱH4PaVzɦeDy\5}\@ ,XdP!b8SvVGU"y$Gzɛ FLt嬼Ч^e2[iHDAbfkہ|w#3!;ʷ ۝G^_|HŅFine>JǪIal KEF'sqp'tҚ-)$@??=_D3+g8_I`(YrMOO^syhK (#NBj Uih:9>IL*=ޞT|jr?NDf{8h-%?ﯜ$`YAa[Ҫ`wZkpZ. ^PN mª iP>I@'0s5ڹ.9vbO[ĕ7Ms1;7&'p.\&{1;:25Rf1rfYl u1U+O)ӂ⡨+ގ")) }p.fLBl˟8)ySuhJ-B5 (+cr]*@l%%G5VqAC 0˾c}Ϸrz׀h316MذM}ᠦg) ƷG%;XtIBk;XT!7eos#Wnp9Xa~M̅+{G&3:X+/,QJqnu;9겢}o$_A _u) :M₤uc:'߷C=^,?WJIRVUBΑ0B9HVC)A,zD@MaBȮ`K6 ^ڜVQX~fJ)Lfp5Hs:I}ŋXnG%n(7ڡ#)N @c,T.sq3ō)!8]hN-a<.X/hL $*v&1vGʵ-!9/:)KOTOW̪;Q -gs 99}HG?s;:.Ȋ*ӌU Nj H'=7rR\"a^&NIp]ks-Q-EOnt@UeThZle37Ė[(ym7dĤC($gq ߕa4BTFMK|{$\*YPc6zS)=\MYdK4RVA#j 1"9(MkaM - ֻ_!)-Ի;!ɪ~y"oƐpcK:'ğ8+l"j!}BǴM+M:]RzJSʹ+c#A1ac֕%F}9ͲIvY)߽Z7z$eJ=j4xC&iT#V,Q+3QC<([B|w/-29oHDC9Po)91ѵ >/])bTD'Yhk.VH"3[fG!tTEA6}Tf)InA9%ɂ3,"`ţ {VΞ(B5I(WQgw>h% v%MhRX_ ֖|6Nn>-Ru]B{%*n6-b/-Gs)τNpw\b*a(u5LYNgl|d\Юe呡dz08._yYr̢ʳL)bDnh\$܍_b\ʹ6:Vml={je1[aLފqn1N,ڒv K1/턴` [(=Zpa#r}섹?fk`wᙜa8XZ'u  jQk;jgn'RMeD{4I ti$ >sg)}t~..lDKZm-* Da#MB&.쭩M;(t媍,i氰^Ѯi?'CHhi6K=E-9SZi7B},WUhLFETzl(<,~L$4_ w/1Di`/ɚۢnj2I.Kqj5qJ"8ǓZkiV(^AͿs-Gh@-;^yU :WY, YA$!I;g b0ef.&` nDX*mQ?L6A(;+n 6)O90Qi_wzXvM@W{4V|\8^41^8BW8{ϣ|`߀Z<{W4_C_}[;ZsF,WwKn`R!GhA@݌LT(E7^/$փ Q31)er$9nP+ $p2T:]ĞC,'/t%ڨP[ui?WjlTys4IjFl7,DGx˽Suڞe#q |I̱,=Oۓ\Ƹ5\} f#nD'de02SMa<:mI:̴NJ kui%,B?#d6' 7zHZNTqFMiC9t 8*Ӳdt(-t/]>Jq'D,ȭ~b!PQ2zE'(8. fQA^+.īZ%d#?"7 .[PSe-{"cTLcxSY>{澁7a\c(8?yI|-x͈v%=hÜD@1c G \f^kvxUp@^) (CEhsuä;[Vt!4tݹ5(CqfF4 vA')@[4`)#G_He@R8WYgEƭP]6!U=Oިd]zX֕4Ŭ?$#\tAp"h0"PYbQq 1 oX%lEȐJRgbhWHW݃:!:4o'ƴv~ִBT{?kc`,m8 +Js&*$lyr&L$yL9fdy$?'3$O'd2fNLI3'992O眜grs$2I$d$$992~L3$9$2dϓrO&L̟'&g$2rI$fI&g̜̓$$O$ɓ$$'$̞I䜓fs3&I9L9$$ɒ|&y&O&O䓓9?2䓒Ig?3'33fO$$dL&L2s&I'<$$$$$$?''3ɒIs&L̙9rdII%Id'I3d$$?d''$2I$$ɓ'L̓$3ϓ̙I'L癓$M&|$$2I$3&s9I&~rfdI2y'$O'&fO9?3d9$&Ng3&3'sdI2fLI%9'ɒ|O3I3əNdII?|?̙9NNL3&I?g'L9&I&I<$d9d339gI&s䓜III?yL9$2LOs2dɟ&Lϒ~I2NNd'$dfdIfI~NyɓIL''$Kgg'g9L'ɓ2I'3y9'2rI$Rs'$32I&g9Ly$$̞yI$$̓ɒII'rd$$YL%i5&FһGrCbw9&6!"G5W h'd||oejD ]{#F6R˩θR&k2WQ:E \Hz|rЏoZ"zܢ *)<nZUl)W%g+j7m!U8B/!tHiאI-:,Yp3|GzYZ3g;[ht@$9aaYyn]Ǜe:C;}X^be|q/sI޺p,b:qbS~V8+\N~쨑H1'޲Ѧm9WLs6ͅy34yoR "kij8-:hVaۖU9_V.7$wRA%8P"³XBqod3yII% P|!T.TknfS4ۋ4l^vծW1¨d*rW> TF5F"M- ?Wxb|}͇s Rl8;esFш 6E= v[}YlH? ,ba #xk|JD<7/"Q4s(f~qII|HH_%h#Dd878wTIv䖷ʶ3m2Er 5b%Ťqs a BoSC+ #@C,}6xD+̈D^5I~Π_Eab 2OL^(IisUh*5ݳB,>E$[&_\7y3Av9ʡ錻7E4#[N@n&ڎrn,7-U=6R'X)vxB!B#b[r3 ia~,cAwSf]D9Sl4!H˺4BO|#(F~?g4E.MiRo.ЦP#=z.ʰy6Fdغ,Nխv,$:Wg !SK@0$bӝ`T=Ў,xx];EvvQQH[aʡKM) ۆ@ii.ϫ"NZ -"7)JsNu] ޥ)$b^u"y\ QPKRD(r4cy1z\~k`B^%$^ȆVhOyA; mCCt6fnpR/< K,9Oe[`Nml_IE茣,Z#ڴ {1܆7u:ћxlv+&.l5=l% jߧV4]\ Q U4_Ь@1g?zva &sa=:>MdOI?[O S=Lf3ŕٓN-r[s^u|DyPw>]<cPDHS2' $5LR%y[liZjk_z$a _ʩX9eG}u+=벫75܋hul~x=e,I΍x^ÌUB4GO5;Sd4fղ9ҧK}ѽ`Vh_)?\cJ8o#jOiSҜUҟ+'t D]iqIjijBbl+tڒF"pt"[U(IT@oG&lGp*SF~^GŽ*,~(;DiL5^zwiTPɀ+0LxrȐ0)f$5w;5sm#$KÏ ѓ|Ju/ Ne+FM3ºMpmcgiM yP偷n'4EBcmbzzX܊]ŏХlU-I.tAvD~A;Bɂ(D qE oI[܆G?Gb_-+pK0R+9a7b!4T d6)]P~j?C 0ayU >`k9+6jwq? mC H5#vU 'YK^C>}&HtϞAR<!tR'`qӜ ?x:ɭFCmL #P`Kogo5dZ/LI!%SiA-Fā,4儔Skj\Pw@&M϶>d%3?C&X?Im13tJHٻdXQ"ZATI^ȩEf9F95&LN}Ǻ869řT:C]Iǔ$+EIQۯⷚUeO+30ˆUW/&DB w.΂.}S #'sD! FmѲerx ,d@]30ywF323׎,2Mqyss>Sh=FrTqAK%0{])A(]g%ilX"Dʘ-٪hT`b*$jc cv_IՙpӢS!IJF# D%:1ZέwPbd>s^@3X'bQߥ=kI!uwKz?[Г)?&{ vgE%DS ?G;J1vSIQt[?b^SC0O*tl +WeD\39ZBj}2cʻ`(_,KIPryk}՚Z|( 14~RD"Ȅ{-|IYJ:q^(4pMMìT&Q+u[PKaJh\`S\̣,бa碸QR8pSꂈG[u>:#$I9LڱnžKDD L1ilLS-•Bу r6UsHx/]eH%]7 M% A"Z'j 3W{+4lXp5D+9%6Ղ_PgJzSfbT~m$Xɀ#tT H30r4HsP=\U!ڀ X_hy.VٶFD OQ7` B+wj\,@. !~TƠ?q@T.'c8/Wvfٶ?L@>j ޘICR]p+W$}iP G:˛ /}s-Ng>Š&H_[[SeI;ty׾zL;R&3YؙwHu 74CV'e}Bjm )i-X+slWwzd754!sRP8)W J 4AYg yO?0k,b)2?Xha*%)~ :Ny990'è_oxe"(xlnWH[[d%l"X"s/eF$i)Bwp+T'd@?0YX>a 爫Y"-ZI#'ôPS#zS')/dJWLlyۊxEIƅkq6ESc VBk'w精gk.LIOkbD`RNj}F-T!A&$}EW>R_d暀t51jVpLy<̡kgDE]Hܒ~]*goNT'RK4baJ,N<ĚQ&aLC/w^0&$EibC`8^a XkҢڗHb3h3V059S*bDvUGtvX8Lx 8L(b  `,/R471Z-C# IvU !N&I`NqOp ʴ?dn4~ 5d(:TE>9MCV$7FF6-])XY]`0Z5d1F:/EMnCwm Fd(JXD GgI6WU"אXۺi' hW?I*n*vĎlGJf_HǫwR#r 򢸪D2&#A{d?LX?mG飴 x2ުj- !k _TvR.ÿ1*))Vٯ<߯&?aMdcsFp`8i skKrE 6m_.{kW Iܹ|y'V@dCX > r )q^\ >Ȱ5)+#inS;=>_/mD>d5!gKv3eDcvqĄlpd]lcEbyFp탠۠IYt?^}k3z#N^Q"g0ƒ C)!I(V gbwPTzS-l߆P6&R țӨvhSStko< B?JٜiiV{8P'lRtl〳|!+ӄ,orˏk[2<7q-{|EuÖʛ'L7HXKc ,ؙ[Nyăak 9L" 5J0"7lM#U  Dc jpYDt̉k1+N^j]/R EQP7)#M,<GBA4-P9N-)Td2 y;qg?ٕa6Ǥck,!>#'<ަXl"owi4J-'2w I. !1t/6bL&Jxg3I}e+s~Cp[9)ݦLa/2Fv[VX$3]4}#FujD"OkG3DAS1tKKM8ÉA*G7]^T" p ٨a œ#3𳛬EvڻSYIqkapI[ fC^cJv>:G,;\WuSvwo?S cqЂ!ߪ7A)JP(QXM9# 8oۖ l$SF C/R>v. H ъ["a4(H?7Ǩ~Q5\$p\f,~H쵑#lxbBLUmjn[6g@ C;A簐@nl}@$D}@8vr) (l5a e9+DH}GPsa^r_ `X[ZcFx@lm)$MёJwd`:nB~Z_ E jVyDI},&ѷ&O{ y%ut0 S#:HUFa Rx#zDg&>_풇7)ERzlz?h2W. MDb@cP!CTz{|йחz,aneP/1mq1(@آS_SgQnnx F!R:c!nICa{lU(r~ԅ$MڼȖωͺ qxrfYm1[UL/]DАbl[GN.*d^N%IKQ%(op)!/nC4&@=J_#Zpdj?bD^nD7g,Ҿ,"!=Ut3^kĉ]R]PCtcޥ'eyV2oVN/ e}ܓ,U'Hu%SVH{!;?$cXus׼9"!*]qgWz{(U~3Qbh4?hJB #8l>L3$&g9ɓ$ɓ3I'$$$̟$L3&dNIg2I$rIIILry3䙙3y>I̓$ə'3I9&NLds$ɓ'$$̙9$~L$d3'$dI2I<$&I$2I'2I2I&drL$drLrry$<'fg$$$I'2Nd2sdI&INg$?|2I'&O$s$O$$OffO&I'Ld2LI:2s<̙II2s>I$rd9yy99&fL$ILɒrrr|$I>NII$2I9s>s$rNIy&dL|I''$g$$&g$s&d$LLI2OI$$2sɒ3$I3$I<$L<2|ɒg&d&O&I$\2LgIL$Is&I0=<$$I|92NIg<ɟ$I!*VMջLPmZq4zv p3þ&_v3(` o<(#‹g__|p:N<8$RH!7D]O-Թ ?YX"RyRbvi762WU{uRfB}˙fjAV>3Tl}kQQEDmk akIv.{3)Ys'܈ց @O.8Kr6Obkh%l?QJ4|Bm b0ds}<%=ka6t)Z+@,rƩGܪ_sK,BBf 愘뒢]=˶KC^K>^s(2Ar䂍p̪GJPXOuN!3pDZWR2HpH&ꑓ~<.D o^oF|7j6 { | 󢾼ޅU%E?8Q_D~}>}^_C6 5KH4 e)C=tEIr}5Ώmɦfɗ1^1gx`⺍-I.GnJeJ=NLӑ,!taѦ u.}Pnd,*ȈK A<=g7,s¯#` `*u*}晙2~ \d-hfNQs)|uST弝ҒV3ܝ=(GAF\( ܾ1,q,@QqEx >e _?d _N.˒׮dY@omw#7j^[ɜpP1E, zbprR͌1P>GC=," =ybzU& 9 INH IU2r'jC@9$3KPE8Ѳ $fѲ݈Owm;X%iЬdG2`b7HQUILE"yG1#9l2dbwf➙/T/_&4N=1rrZ#Wjėˆ|d%8DZL8( :#~=):}X:e|1xU-t#5H/Ux`i'>F\^r\D8+քJ3"76囉8>VGP4'>pox,ꆠ]2|x$mr% ")aMYn;WQ=)NL{זߥ`Ii9zu~K*d?P0X , ꅎW3ml]SB:a@B0@9СMIH6rݴ ݃Vˡ?6E{( xzAMЎdZhU۪'E2ua[uz]RS~`@6WMџ3n:{Xkc~2w[c:\.\d@L>ǥA0>vNZwֿZUUc?5= )%Yygk ȃ?@ea+8RH-]I/wPVjqQhg'ny֠K֍SG# +0㡎}>83-$; 5] 28DW./sMmD$svk9n (Y%u$!Y~Aahgn},x2;UP dqD@ϫc#Ue$VW. #[/~M #5bUg ZkD?EΩcd-w &7F}S{" epTޓz@ o"ǨNx|x)} ,6ZITMN.%LM IR R3gqw=}񑹫(.7sÐVՌ.ޖb̡=Pӫ>BFm4Ϣ7yNc o^z*t1=YW/ƖsS^@ "O%~Y-gXZH&v_T/==BʎM i|TA@nP ԉWE`×WuQ$d ‘`b^Ɂ2E Dl~H{yGJ eFJ<Tךa@[/Ui%RU_Ӑؠ8,ږ{V o84/u_S4r6C<:qϽ ̯YP@%5`WJ,@)`뒃̉;Ñ-ᯊy!ٟ1Vσ3;vF2 QxZF9eyFs4mAfuSFEgr \٤YD!oqIQ H!)-elqJ8q\J-5%c6\LW6&+j STFh匹m-Z ="?ڤa^k{զ'>WCaRL.{ )DjCJM!o'Chv%\g;bզH(3)[JHΨ"w(mcY0Q1rBT]4,wݪ$GASvS.3d.~P4 11n )ӕnRӐQ@(S/P~s^|(e9zA3_6C,JkWzxXm+O5+FMZr}kk{*d$ =ipo#@3|Ȯ$jpe(;嶟Rb3GIָ"փՕ?!-c cÀMIMFuOއ 8H[?>n>(FHuI";qa3awfl+p 0ly~y,#x\X~ȣyBߺbZA#˻voiw,Q'xF;A2J%xh`\6`T(XJ t[r _/w ;t9ώߙ/td=)d_#>T<%\V4Y(X˗n Գ QKZ}7,!~!Wd<TLU4A qp١TԭkjD7Q6LA!K< RӰ-=_1b1n9-E(<+dV-5I$%dR!wcwt6AF.q6UyCx@Un3Z ͈:wpNr)W$ Ob NݣeĈ3i Ԙxݢ`T$GZ28_)WJ &pD20ռo( oʌZq~{]^>$x= B~ UB||ۇǎi6љx(zHOIQp-E,y̒vƙ$L,t K7YoA E@j,0dWGw!/ ͑^>J._ .cnB iKͰ+J;5+&HNK'Fnw{n{ ^;躼t j崃0R# M=*fc\Tي8+k`kpbWpM*0i-TgfY= "wX_c'16yeYTSzISfr8-Yk,ípFU?U) BǮʿ8I!˩G0cymW2VUWGDepΊ6=s54]úƒC/˅OV vX޺H`P.JS[S#EEB a" ih P ,t* m5VGuk߿ fLnYa.{A1P&@4y a Ky/|twU K˺gP9+UQiΈ/>c0=-o/|h%UF Qu$[H6t%L~ESY vT+ qq'!T`ɔk^ $K++L " T%ž H敲l3 [,BUߎM|-6ȕqDZ<({bX֯ //9,w3Q#S蕷GoT'r5 dF!bG+[#/\21#܅d +TpƄsF4œ" <`)26v d>}yS9 YC=L]r-SG>7!o,=;9 g5é[sq's*z?lFlbďܫAHE"€m2^A :=HKmv$cK>gsjENd XZ^PM1CxFXH[($Ԏ)QJм.0^[Ó-ıl ٸHG~JN؄A+i(p#AV!Z?EKDmg:B, ױ)"7c_ " eU+X6,1+~êӧ/cDtͧ]رJiΟ~lSd074IIvaT' V+ԩ2`M+< R濸3}x3nQ0&NHP@ IMt L#rӎ<%NK_csh(BN" \݅$qՈF6Vb{I4[TlcW=MYꮆ}sߟT9sBd+>doBf%d~N`y onn^l6Z ^NiKB"I gd<M_EdT/n(1Ȓ @(5Ei8@H]BP[yy&aHV…1D]Sö:xSϮ]tʩc|PJqs>q7*8~=ȮJbX rZK %C$r'ͧsWc{mǸ0rpEZ?_߮6<.,+[KsV(ބ8V3S܂/Ш'YDXdeP"ُ. Rg|AK*"Z޴Hyoȵ]te$ E5 RB2 yLG&ma ,Ibu+D.Im`F:E2n鰕;q-0=dz#xd(c6 17/"/Jkopm.DL0@E-s>ڔ"5+߲r] S3Dd7 WΙ?P,{qA+}MIU`m𫴤}q~AAHf.ߕ{pXdiClcCšǐ~! x Fe88z,rܘ 6 {pomJ#7ݏeYx!3Ajm Tn~z<ވHɹf^Z#]հ9a76Y8cule,C$LxN ƻl耓ip锳6 cg ?PPKҊ[5ڇ&6D%`râcA *6}%Շb CŹV0fSlt4?BEi!qmmУH3T`04uhgST|˛xۍ7f&9ARf asRh*#)Y}% ;n"iᙰI[j꬝EŹ| bN6 <|\d.QF޳Ą 9Щ[m[fZ5"f$B-=>Url)F>BoR|,ņ\BHd"Ū~r*!g˾bõ+R]oPAH`:;j1L!G.)ڰjAlQ[ bӬ'B*Zs;FU`i)oo:V$;K^>lJo޵7yk#,L3I++[>~}9yT@Husf|c}LSG(A_;!ΰ2vdZNӇ8jzmb0|  >Gi@cZrx7̉|1xy3;11ُ(D 5^̃IHbɯ2<@, qQqjYI#,9 {"+o ؑ|^R{qfC p^JÝSXes ^(<0:HbE`WC )) KSt$|ڢJ= _|2dᾴ[(B![2ćlj D,d)7@uv(Y ]3||A+6UcMϫ챵X#Y q%>f4@ zy4:.|o(Hc)Ts/ΥU1&6?m[OT[&Pv\dhĢ% zi c^]K؄xNlAM8t ZLH{Io^vي.O9$uO/oΣS2WwH6 EO{ q=N`,y&fn含,2C7TrD FD]+Yk j)V=8ReW:7 :$6Jf ;Fo!Y2C!Nd5j` Ww3&bVUO4j~Tn&jRrd="I_D,tURv0{0-̧X`^b֖x16BN$wQv8kpĐ\/쯬պZХ!$ˤEy :BZA_@[vcIOu +.h$DTW 8֏Kzl F_sg0; J> YQpme&h |ԵyBr~2:(; .1Vj$'J[e9Ƌ KuۧC|Dyp[in/"$#%v' 䟺V\1 eI(B+d_Vb%_&s{Y HL$'9L$IɓvL&I$~NNdY2I/>|9&s9dRsd$&fL&də&Lə2I)'$3g$I%$IM3'92L$i3&dIIfg3'2g&d>d~'9$$3$dY'9<9fsw$̒Jf|2NO$2I9&diə$IpM9'2g$LrLM2I:9,dS&II9&O$d䙒LdILɓɞ|Ϟg92I='$32I7&L'$ɜ&I'&pI'3'9$$I$I3$ɓ?>fI32|̓I?$O2LO$ɓ2I/2LJI2d䙓&IdɒrL$3̙$9$$2d32LM9$I9N$dI,$9II=$d2L'̙O$LNrə$y$$ܓdL$$?'&I32gfI̒d&I'2IIrNI?L̜39&f|$̟$9$I&y2|̞d'2I=$dK93'$dI9&NfL$dgy'$L|̒y$NI'2I:d$NI%̞II'L|'$NfIL2I6L&s$ϙ$JL3'&L?d$L9L2s9$YN.]CtG/#>Q lrt]$'vT sNpPcU gun{!gAmTE@ߡUОB i"\@30)^w[)9g5̛L,daizc]i3bI[MT])vvdM^٣F>i!PAb$`#MzҔ?Sm>n2_h@=ufMJ{ yVPѢKAܫa"591t[}0hf.*g>|>/YcHX)G6c h{? 4,fɩ h_ N?+Kﭰ7XG{5 KP+ɝPew$PD5A ?˭ \t/j5ʲe#; )38&5) :SzCB%G/ u][kiosE)QȘ0ݒ\f~;MdqDdZ Eˠ)KMa@zJj_z&P#$ \*\@`ɞÙSEx1 GctcHm1SCQKsD G9%ϕ Ɓ"${}f0Ն#a ~;] Kah-Ԯ?=ekRʴΊ;ݧD\JTn r"ηF}ޏO͕Z>f81;֠Vu~Fqj7x8Ntt斻Y-?7kB 7͏BMդcW>s ?q.͏˒K> I4-pMD\d WN 7> e>2ٚ Ԟ+7ӋFY3#T.oC>Lh"=/HzUڕVʏD*^11?jBW@$#N '!z 8l&=.$0%T+E H}-%sg6'uHaL ب_ F10CIn0ۤ]0oӎ9rV ^dVsUnQe7iL #jiGbg3 jH9[T |C#v?KM-FmށoݥR=_/;4맋Q8 A"d5;ɩ(V`Dm ʨЙSve5$YG@y"yj"ZXn#C$q + s_MUKp׷ xt[E q[ 4+),Bo+5.I~A2K/!;7C!%&*@S _mOΟGWwkaE=kfM`DII% ڌ?J$B}}{b\)fBU"T_cFE QitJ}?3!(EEI| ɡ6 jdU rٲ|.L|rrȔ&1t5E-fvUncc|Ę$^ "@fd"gd'gIƓYtMwxv%G4xwAZqK,r͟>J`T(F*][͠;wh j-'kvY_|؞cBPjڎJzўVQ H*NY) N6k$_2.,RgM0':&gİOC4Sadݗp$EnW)!Px3n W :Ȉ@ۇ *LɛP0_vĔ,6$FY8yYر~F Qe(1_D=W^@!(cW[_h@akiX5\ODAeصM_cBIA dolǢ0h + GGTLvH ;M[$s|tXZr:|nH$HȉeΟ(-[9<93fR, ųpǔT>]n-S6 $lG& vE$l+6pa¸tgע2m_5B8! &oؙ&e ڗӅ2NeGժ|Ě%sf$o= oWI`ӜGJ`'qWbNL6DWTsΥ2"Tl?;pgl04=v`TBL5wub` _( =&㞿9rW~,b 9ӻ a;dPOiPm%S܉ d׏ϋPP dKFh$8H"\8 y$^ɦ/XcÜ?b0O ϳlD.i181-B(WeLa^?֥*SrptZTvUtyMe`XbO;xr9R/=;j&S8`"6_f)\DFYLX=_S5qlnlDV9ԅ9vmtֿ N}$;Pl(/HN.A!W2&Goѝ;>J fn穋G ƫ4fj٨aW7f} Ϳ,HО 0zE3ictF[nŒ2' <ÿ;/Ip0Z\iPO)Ԋ^.Ǎ2\ bqmW#S+Qg6#? ZtX 2*!$HMj'i梠ӧG8 B>tu|&(wY1<\;I%nNHcYO+TaobccƦX7<5%/ 'I$+N_+:7r@={1mn]joR],r\F7~* Q#U}s\(&L~{ah^X/lQ4T*\IQ:ߠȘT(k>S9&^5p|G8tЅt %/tu8CQH߀Sz2yoUHD1ȟBIqEXh屟((VdA'ݔ vJh=u芢y!)(X!'[*IE*YP"")vqXE`'ZGMeND҆39I ki.A${G")Aty|`' }MP緋MvH7P)Xm߲ +)' / W ܟ$NᅯuMtek̿m_[I So^8XBvd\bhY1f8cAF_t1t=s(l ̅CuM{Hߨv!427nb~sHνSj>HPY.ka8 bx!S%Y3XRA*i3aP+ 8{oe2PvZGCu84yVY5gÓyŸps=H "}$!0 C31 _1V`jax1|1?]0{-]zY˵+p`J!'" C8%f4m_L0UI::=nS~Ù $|C./#'SE^ErAKOI4\M)i3K!#̧7tAbe2* c&l1YJ#ҥw )7 B`&SATxGIUQ"貝yK*jSqi;Mw~+LPaz,QDE "2i}sGIr [`7l}> Q4JW.1gEP83G#Ԡ /$+;Ɣb/Ea+ZQk.IQq|- *&@4C|?7N S_Hץ KMyzؒF?lCPs_B*d}!^bIY_3bL%(EɼC_/5%Q=tGZHwu>2KϪqўDa=+O_FIn?>s\PsP%~nqu(՝5M [̞fJ^C_r ~ʴ-#2k%CJ:/F"uJHIkp$\=N U'w+sG(Q~eM{._W fcwx:tQ *B6r;nL4eF?raS:l%(KX2] Y\@ 9=X0zl؆EY@B#gEZ)|Z)?⨡JOQL/;$YBc(^,X RIDL>2{j'_g&+xwBL{ 2ŲѾP5[\+ htP:͉LXC$H"p)&ύ CAѡHzO (yL%{Jox0,%wW19&ZU^h}Z@X*&\l1wS͗E N.MyW*Q-l7y`{FH 7A,5+wO H==jF o"$h REaCV$/KQuqglV2S%O$sj0LD}2 1HbP~w(}U5yS{NLE a"z%U ]gcw]2I)rNQ-W]4FT\<AjF4 iPCņTc+ % ly%D\Exo_@Hx@-f @ KZ-d9?ܴR.&[gњ2*=uFo[)o˸z8lӝh֊g[2rI>= `w8#@DlMRq 6_\8*t._.v՘Ple]C8WQRӥO'+4*Vnxɬ'~`bxo9= -qS4]I,T`j߾Oqa"|ș+I7%O -m1e"OmuV LkV4UDjѲQT`ˉTȽ͋B2ڤge|G('M]Ddl";T. af`iN{aM@NGB Ȅ>[i@ ?}Rg蟫נ ,ZS8&jBŒv-syi tYGŧeZ)>f]dnP:elmJ )C5TN6SH,;T5Za8iC]lh4&hN``L%i7> up@).\_tb!|2e)eG!jbqgUQ‹"7(5ruGkmET6PE,XʝjpZJGHN/$ f$(70!4`ͼY{`rYbKv9e%:m<캜N}ǓīpGjH+ M3UHRX^XoX~RHA "!& sA5kWthzKY !9iyk<'0OאK#N[ h=s7NpbFBST&:*,dgx7Xm'#6 sZW&B/W(o.՗oM>fEBBfUpCT? kֻ [ ,9+qө zD[%̵2:YgaVZc uEZ"mU"y˕'`389v+ּM3YĤ\ fT\gn T֊<aP(UĊ | Z/I@)~D&|hqcPUEqÛT䵫))oyҁc1'HU0 U]ȃ`9?Unab:sHCs&}M&{(-INkc bsR2edIt$$Sz `F8YJmjb ]߶ (8K54몫fY3S7=4CB=ɳ*\lZV*MpS¥s)zYS"Kg'ސ=mVolPބJ{b#{ ;֮z70zTG1{:SHn9=3u`#!0&3j@!"jʓ Ղv(Vl]]?jX6-⏘G)\0UX`9ݯu^і9FKA=[PF…#Jodp`2 y(PN[f蟱F/= D^C 6H3|n+Oҷ&`f߯JIɸ {*leCyLx3HIOpynMjC][ʽgXVۆea؛ ~.8y|0bQTm]K؛)E/n+dcP lLԪEA{fF],=n4-ed_kLp!MDH޲3sWڽ8+R]3fA([^54EG ޹j_/ i-<k*26L=Iu YDOxqKaX88N+3Y*fQRE0V1륙3{iʄ}Eǭx z柢࠹Fe[%nŮ5:eS;A7SgQAO07bI ϟ@47O8t B;_&j`[r𒌗>[n z*kLHtMAI%)C~W!h{2KKidpg g֒%Nv`m:_ѳ3oe~,/v H~1$,Bgxdnr;II&992dNg$I''$2fI$>dILI$$>dɒL2LL3&INs2L̒I;NO2fLI$dzyrdy&dɓI/$Nϙ9I,&g3s&L2I$ɒNs3'92g9L$93fI932I>Ns$s$Ng$II$ɞd䓒yLg$ə9392LNdL'ɜ̆fL'3$9̒Od&NId3&I$93$<$w̙3I2Nd&I3?'&fI3d̒Nd̜s2ILɜsrfd$2y<ə$$̒fsɒI2yOI&dI&d2NI$'$LNI<993LLy3I<$$&L9IrI393$'$2LɒIϒfs99I$$$tɒg&LI>&dI3NLsfIɜrLINd<9$&s$ɒs99'&II=$$93$frd>Is$ZghG, AѳjDDIJD};p?V]]_B^Kls[6mm^Ĺa:f! K4G2DAU-wc +*ߌ} (4 ׻昨6MXf[k v-w\''3ӱ Xv:Y4S3?Rį8O\;`CّX(pge[Ie3lFӣ ~Ijl >#ڣP<ǦvޙMh\*SMUn%$ ֵڨ~TVu=H +Tnr}oQu%~>zk7`g=6P9wߴr mUT )wm#R++lU7gbT*ud|eGXΝHxU= }I2!d9-݁c`{%Mh9v)w6ihc7ìq&5,0 qL%Ɗ< 6wGhZe -9p'We ]qX v"jN| ?iWger7mQJL4u9inʪ_I|01Gy[:bOf4Z'gZg _sJ-{ޟ4 i ־kq"e f(Ȱ-#(OqyS\A{`")E#Y/`~x7(3߃.(Hqv>C-JfC_GĽb=4z)@% x5 ?B R7zHwq4x ${Ge㪤1A~OK!d|{B:e U7wq%Ơ܆0Qςz͖̉E!UY)4T_-֚=T~ >̮c%,> c+*;ę^)Bfd%E͍LHsȄڼ:ε.7fWwGQfa|*o_j:m~'k^FWiXȹ{_WσMRkw×;] x*x%A./rVxgɍGz^ DV -P%pg,2]*`ޅE P42Hnu'c^׬k ^F󖷱X}b9V#>E.5TT.i__#ƀ}ڵ٧o?IbaCZ~aeՕ3BPMd{k#I'J@g"Yl}ܜJuTBoЃ_ӎ̗&_sb猅2 ]I_Sw='Ο/+lt9N|'Vq;N o-;CQ7$>[-#1&3_-=M<(])}( [p$`=76]o6HpȖs"qpд$?%x)QI~{hz._gh,XؚE3yKTТr"pOfXyRn AwT6G(BebZ! -F$g0I[E˺\m<5&ZXp r_U%H~!mߵi9Ox霷/9e^ 9` kt vׇ{^50TNe뇍U8H,J)_r׸h׊ã}ůf(?DC _bev_4WVTBGCd~r辧j)'K`=Q9s'QH"c^,թSƕ+Ϣ ]k>Z?(R2҆Gan *TD{;9G$:U&$m8`=Zv#ap>Gum[=y~u ῂHT]]woC z>gD2xz'̜=~Lf9 <:i"P&}'r!LjdN!M1J71ƱJBeE¬GRT3E&\@Z^o[6l@>慀\NJceB+J pSXL;t_6[wςz aM]̱Ɗ)` ]hgT4!_j,j>q4Oh^Nƕ)-)UDD 9NP*7>VJ"h_9d"vzWEQD _Hy)zˆHdB4(<ޛɒ8O}cId jGbWb&DF8_p=3ɐD7 -( FCdg>la}'+?ĸu9FwS3ybشݪMl >+~ѓ,A:f ICvq"ő>l8<ĺQ~%d.T D4 \Pk,nP#Kୃ2$H3i.eD8qR%6L(Ey#;DX~D$m׹th|"kBJ;ܐM&E(Wb?п( * "ƈձaf"tQ%J|ęl1XRZK7%;7^>k[mi__"=(nNR+m{yZ\zQj .F}֢J%CPI3:eԣn"_unɵv!O!%NjVj[W`z/ܩR` cT; _=Q:EqC0,cI_?{ IL$"h4˴B\Ĝ| dUl,<m!Bxc2'zif5̅I1F '1hmGxړXa0 *تK#Tg1m:> jp3n]fnH V+MVͣAcF v+jl3x߱U'A J\0ъ*R GT $ZS:díJ`RtDi=`?˸Q$HaFWN:93%9$Hz@Jde:WOPLB<8" wb TG*|@K4'NS#&UXz6xg))f`/Rg5_<:Z:N$$ c4iԼF9۟MbrSR}/u.M G6{S}Ҙ0qtMXJ`؟-30A:+(yf—]pZ PCC-Xb6'TñVj%i'~l}P1>\mR𱪍j26Zyw5 BJVDLC1}{NHԁ4XYDLZ])/ mtQ(-*h]Skl0E},,l 1ڵVJZқ+qn(c Xj4LZ87fxkH uIL%Zn/ C 8C9} %OX~4NqY[mFU2ЋC{Kcq7X?V %Z!j]4rL^yd$dPay. 8kkBd<"(u"^efAYpCHPgĐ\b5DtQT/ |AJa8ԭu_"ANnvDUN׏MIvT4 r*])dvɗ2##cr9,RU)3#Ѯ⮡(jf68;<;b 3V?Q͜( EQV5[zA?K3}sBDw/ !І&04FLSECA{6].zX [qSu.)B &Yl}HgdTz{@w"* cbpځ/eP2X^4]B8*+b.#{M{2TT#ִ#J0͇mE s Q ^>[ﳖ7)%j|-yAIqgj|݈M9WnD4n3UC6Q Lq$/J?i1(#ރ ҕ/6[ݲMb;}.SA *5ά"Q<)'爁ZAM:Ȉ͉bb<~8#Qdž:r,@t᝶o8W6jظD{GJUOݨ"Tj3%7"U[ /Zɰ @)BBR5ܠxIyVt 쮝1߬jۙ|Q23R|ѭLD,DBm& f;2`K߿(h{H"N*lDBiȇ[o:L01)$> 3U8M` ~vJ2՝PJ bŒQW!gZUy5?rnѪYF0\ jr F/qFw1C%\J% 8jN7UrnL]5R+~]in^{FF{@;UPkWX!{URK/"ri{#jKH`8b3;#S [^JqDo>A˃߅=lP32UMdiT/Dd`EEWiYV6z7'M"͜elU}` eTݶ Q\#u[B_=0 tF8P@J~X}a@mbvS+r@ /2oJ)/JGbj\XSAӖ8CBwl I*m`JVAy 4DkiQ"L]96t:fhZ#-,׿ ygգ<{O٧Abt7noSPPDO\砤GU[.*q%,OmB 69P/d]*iUK^hocj& 2%CT0uLY N'|A:02B)0^,5GNW=7_q6KqHp_H35M`7FW]7$Q~0+mhT;u!B cZ0Ly.圬CtD?_ߪSApEڇon43G_2n6ʊm| 3^bHi{H3QRhSUwiYE"1 7n( `` *Lf5 qO"!X< ò(%p ;(C.in_E-AN@ܽbk)%bC S3: ^x⼂>,(ơ/2n{4xj. ZmXAF;gyNL0FFx_nL rmD0(=>n%؀R}Q}:K*{Is8oQ[>bbs;MY gjȡ1 㬐t>%`%,(1e7(<15ޔi(5KT_/3V-T%3}(ŁNpQ-EWyhRZdPfƩo-u&V' Ync4qorرm5U4uE$mX?A(%MNxcҷlnf8LI+T$>r \ȯj^p0]w!s~uox X{w!'.K jHj%!hm3?W@<F(*H X`yDsY 䁼gyńv$Ϟ3ȩ:Lܪ݂2fnzB{hie$UHF>iU!n(yˊLS_q`# l,ԓ5j л?wŐWwvLqXTs%*u1U9f^z[[9QxKK!sb#`YE5\1Q4Mǁwu䈙HHDa~酱ͣK;K}`xzKH'Vk Wv{ 2cXບ ؊Iou+n `IoG,~ya$npM7H6@8>LAQF,QgDN_۲"ѹߞU]<"@wjOu?-لwNIg{Y$FG@ޢB i6쓓H#Zb/mB$\қ&Iܠ'`AQqd+qv1uzunYd9"N:veg1KEsxW)D'/b䥃 1?olu,Xȍ?џ}+DMg :\%';Ce+~emEu3 /| $HN%;R9ڋj6P*N,5l_F^\Rk >괡≮&Ar]ί %3<9܋*' 瓟&I9ɓ̜$̒|$$̜LI''$2LNL&dIy2Id'$I$2dO$'$2dy$2~gd32I&gɓ&də?ɓ&frdIO$$OIdfds$3$$<̙&I'9$S$3LLd$y2NgIdNI$$də$2s$'3$L2LϒId$̟9I9NI$LfgLI3$>gfI$2O$O&s'2Is|L'$I39'dg''9&Id̞ddL'&r|g&d̒rI<'932I's$s2I$s3d$$I9̒I4|̙ə'?̙$y9I<ɟI$3$$I$3&I$&dLrIII9'&I?2LI.~|I?ܙ&I2d99̜$nIɒO$fL3I%I%&|fI>LI&II2|&O3fI$I9'&d92L9s9$>|&Ns$s<2s&d2NI2LI2~d̙?3$$fg9'$$dd$'$I9&rI$&fNId&II$II&yIrd̒I=$d9I$$$$$I̓I;'I'fI̓$ϒd?$9$I'̙3$O$$>I䜙&y99I$$II9&s&LuYNchr쳾pdW!P;7} (=.HLxŶfm^A QT!X~>~rd$?CU(GI{lE@TppC' |8{e"g$6i}oD1_?G-Xfu6=bEc aus4тRt%{geMBo.@Sv*fM?>wm֋.(IDŽ"uI3s|Du /LoW>ZZl`"1vEL ypKw0j")lk%QV듖DI^Y`koKDc\zL:n]0_xFRij"#74oY ,j@jDL6BeE&zD~u=aݣ2Zк [ L„U`[F/baE!Y<#Tz-'sbr!ة{]H)&제Fo@_SBBNNrFbPYM^0}$vщu @+e9ǨrXr jN(`w_49=ӑ&JHWU˥ țoJV$Vyb=<BCgB6:4 32j|N |\]}qzi;e 8nzA䦸ׅ˅`!k#i!քg=d  :5XCZ*mflQs`+((ᆌh[,60b˹O)F "l_IXuyM`H@l;:[N+#r颉+(pQ$dTkӐM@JPdPbYsE D$AĔI&YJԕpt+^ V%^5&¦5|RGdf0 C_qSx(ЄOh;_Yb%gg5aJ'zOFk6RʣI9`b(ⅆ*Cm.U~D@5fо3%Eֆ&%W|j`V'/**9%@TK)[ј?+|9S6P-j95v3q\zcFn4%B~KM$U5'(US$PNcykp x>~<8(9Skȅ -:WF~)`L /|^x}rchul:< _ `yE̤םKF@n @z&J}!}6,Wh,LXy/F4P#EkpD]W v5iAsE/6+궖%&/S*a Ev 4 "zY5*3XK$It!C@|^ؘOE/b_D  z?#xd5@OdRDxf:?w‡ō{c'`jxKJ01H(sg=%o0% 9RP&Ez8V/tnt7+i֒R`{IU>>gSDi _|QLǦʩB?NH(wS-~ UV ^E6kF ]'*UsqW֙({@چݺX7KԨRZE9/FX4\|y';Ɋ =x#^uqt1T .u5?F&YGͷqz[Yg#@tlGacc 8U+/- ' +x*fdwaGr P5_J/B1¬ˢz2ּpM#Au` y= Z2Zz&&P@HPT% V>^B+RV W9/)kPͶ:vO|{O}\gw{# 3OfBM)Blb/FV=e5dAѤ 'wx/l1aK~-Mc \bga@2X= S..3f@˯C-'^;G+jR1԰f:JY y1[9@r`#mW *_m9 Jߋ:h}&iC+g<O/_Tff!(0blk__e7/I妵"!㣸c0?+OTH]>pv!O zX\׎*CO. }ditM+xHLvr29f"J  ߎc;JMjQKoPw/~E* Ssx1Qo1 \L:%=9[ oIH%#d 7F=Ф. &-v>̴T|Ջ#P.N$Q4dō$R(SbLzFU{2Sn=,BmP#(v%>]<µ!(1/OPɵu ȩ4˩l|,x-Pv╡~xƧ"nYɏaŰ+ m%d9<اsS0.`iWVAd!:޲Ł4JZZ0'9s`6Ǐ`L50A>{^f Nz'26dFZRIlboW91QzǤu7FZI/8٨Myw :.#Rkjg),(+Zm5\,[ļpW$mJwΉ1v_ j ըJ7&0d[-; YK:"RzwZ9o fqq ˹?_֎qzus(Y/-tMOV#@V=nAQ lyl QȎ 6YdM)nxs>YYPcπPV{uoQ RZ  -k!󹫱ʆ)UǗ-$!5@YGoKX/"d#XoYcd[&od46D5R@Q_̅1Q ):(jbQb3;@H MX)-Е94 Xj T796e7I{FFxy&umMEˋٕl(NjtރkR6tVe㮾H iA3p~ܩ(NIJ L/e}u(հ ˷?Sb<ġ3}Qc=.:'L҆Lv}GԻ6"-t牷?Fx/j( ]1O ֽY+Z7x%R}]9%%S UfnT'xB,ؒW qpSd>0mN3oVݥeD3Q,HS(:3HLdIK>aM^u3s$r bb>gV_Pk;efsRD#@F;o=D1O"s2ԁ,{,pK* 'SV쒊ւ7Bncc "ʙ)>=xa|__lު[lvBW*)1%anh{3w&<9z RD-(9$Z7˿¯~s6S! D0)')~%M:Qv|ʑ$^7$l.P,8]76T  n7]$^rcxrJLN~Fe]]i2Aڗnqg|S":w,= / %մ }0'QksJ<(:^P`UKDۗ !3TA_QfSBej[{A pIcv\\z"'3+.ׇ@&:+(+kVyrVr2-QoJ14 ;wߊ=mh{nGE C#btn}10H4Q)^FŘj(`O'`5=wҢ̱!Q^c/h,4INnf/koİ[^.2Ih2&²AWSz:\ aS$/,dW-E/aT<we欏iaos2_w' )t97]H;+M s.Jdv3U//e!yzx71eѸAE nmF \?F[Rsg(t/I }*.Oz L:aAX|5Gl˘A [^[U|D'ڮU4lH[nZ`Ni>UHD<ˑM`jt|kFC\(W+ d#ʓOS\@aѷ\"eVI)]T * `{L0msљVo$XLNJJvuF$ؤ3YXvu0DX:Jɂ!79ݬ7?롳*;}k}SzENneaAIXxl8@x"K)v0 ga@-X'AyfHr|]/t5Tpdg;;*t*S&EJ9~Cb^-hK9F8L ጉޗY_n]@ &׃ԔCS5PgIRE`|Z|tT6jQr:_lHLh WVGrxQvD%aj؛M;ǾxX FU_80YƐ.K٧P`&P|"Ck@s<~/qa= E`C:B"?ΫIހZ}J bA8ў~P ]߈PJ;1  "v)gZi niqPeOod8S| P2&b ao!eK_@pX[KH?ZbIS #B) 82l0O[-&AGN*$6]/V^s42v9o 'E`uCs7[rpZ`t~{{ oo3ikH/dQ D4;5(ctub1!MV5ި.T&AK zxB] 6Jp1%֥ J48\!n –(Tɣ^oX*L_4:Uic&V".UAE'YM&k7s*$Lj_lM럇(7oI)?~.RBJ|;>ytFkSoكTP;3D@֔e__aN2=0z @L&l2&jꄀA Qtשh oI)m Ɣe }71"gB_J-rQZХ NЫ&Y2Y~ '(ƵSSt pa){UTX/*0h2x4W*QɞAT ;=DUDSJG+sᯒܢŜ˚0VHnEc)Zɨ><0N^JK,s9ށK8@'8u5u+4X4]p}aލ{xŅiCE_iwV|EeNDn_&̅St=;)ĥo]=gfլ; zRUop㭍LEw7 I|عnԒfXuhRR5~ 6ovwF5S/AsS\Qo6}ę)I@,qr|H׍o-BAC#kO2~Sgfr?{ЊYtB+0W19yHSh8'C̝ȾT)vΌL9nd!$El sJ-L[HԕG '(.nT#ïQR9 )#ħQ[OJFTĴҧet< wRK,b#:X*f*hK=aYBqlUhgCW-tmȊ )],Þ[,Ê}C~C|_i :+A De`Ұޜqc`)vEٯ(PZbuoN)2 IۇT ݫ u={#*#0QNord`O|X 6袞YĂ}(i+(3ߖnSǗ3 Q{%/lOE|s{?Ǿq;Cqvx>;sKU5o}')11sC# *Jta &;J+o[NY%HAZ{K.MWKby&lB9^# 9Waԙ+V#j%;>޸7eUmn~1 bҬm,JM~x`E~h"Qu3Q'n$*HV6 ] :|lu:sJȘKS+Y˰S%ix&.:xU@9MS:Kf"țK,y c?#ڑl`R2`2 F7R)W >L4_ 2*gi9!(&ՙJ>exc,`D| k4oV%k̠ 8iM|tu X"OCšŦ:t󹙰7Lʶ<qp!]0~1\i0 ;WtQ %QIE?/}ގGs"s^vNuq[G0Ȇr8ɿNL֎] *4#,T0lmvM pR*x#(&H(Y"ܮ"xRQ$KG&d^ge; iaoBP&i[MC\ ".7&Ieeaf~2孊,Bvb1h/Ҥ2:.wBT8U oݐaAAk؛O/˨?Ɔs&lj8%M6%(]ssI& s!<̥8~#A{me+H,lRRãv%5+h&hcNn9OJE"0HXd-kD~Bo-UQ!phGOmAɰR\Pj?49deQʹm3!N/$G[Q$%rLpvDx5-X2NdX34˟>d֔aB\V*8 @_+﫩H$zKdΪ(Ʌd^[/=lxBax=ѠZ>YU Dߎe!IF5ՔAgy^Į輗xZbu?&l;UjݟӞT&irZXȮᖢh%V}ȿRl@Ƣe<ꈨߎf;X8w))\̻u PIJ5/PPBpB~ } M.O>afzv/m6[|S P+[`nA JlyJC}[w0麤X,AIq|E A&.}q׈T@*0uۼb^}gN.X_ ]2g#GPCtMc SH" 3PbQHeDžwNC'[=y|1gHAMfN "u #0"RR r\vN6 DĜ6̷'h_ '4CnH꧑ꔲ|^vL}9#ykO dxNsGQ #\2CZݥG)e`K쪘)ūˁ `o{!]baƬ bUZ<9i2FW ^taRȭM CI*KK(d @mօ1 T-MG3xL^t1i h=PH+O)x}(rGu#izDE $:'aӄLrI3'&d$$dɓ2d&~NL'93&|d9$I3sd$I'd̒d!d2NI&d2I9LI9'&LI$̒fs92I2g$3ɜ&d2I$'|䜒L䙙<99>II&I$s<&rdLs&d̒L̙$̙!?dI.dL<ɞNg$dI&LII$$2Ny&ILd<&I&s9$NfL&NdI32də9&s$$I2dNs̒I '$$̓̓>dI2gI'$d̟II$IS2I'I99$ɒNy$i3'd䙓$̙9s&L>I$3$ILLɓ$$!2d$9̒d$9$I9sI$3y$9$I''g!2NI$'I3I̙$zHxNd̙s$NI$2NI$$2IϒNI$I?$<&d!333drNLIs$$N$$ϜI$dL$'<''&III'$I$I2L&Iə2I9$$^L'&Id$INL&I$fLLɒIL$$fJd&I9&L2y9$̜II2I2I92g&I2II$$I92I2d&I2I3$I3䤙ɜ'$d$ɓdɞdI2I2~fg9$i2I$2LI'y&I$I:d&d'3dɒOI>ys32O2I<&3&IL3&O$$L2I3L2fLI&I3$ffd$̙䓒|2ffds$~I>g$N~g'&I2s YLaZa"qBG7C# =Ch!2d,itM$2L1CL)@%E?~rN9 36/-֢/s5z8S++7@^P[R.鷮º@HvRguvInBsf.[ Ӯ9l'O9쯞N|0#(>iv9҈j#`܆ &S97i)a&\V@0;@rf,56;1Xơ6A_~I6l2vuYs-˲x{-d.]x*<k \Ek^뫼!5A/y'VB6sMVUy)%g*dz5O[x(N&z%>W$@KTM4HF }Mh~Yd X$*P o F) ,NMfֿSڇ{x hNc'n\G˷z#c7[Z2r9@=5]5H7g5vv+>5O!yw߲8h l2\A; %Kq' e0arMG&aM7s=9l+e-s,[.!M`mc&{v J/Q4"x"1D $ ME|BS rUK.p57LD!}lxI OUvqE}/1ȉu}!旤ͺNCW^5uѾ[o¼]@M[ə;:mp(mpgx,ٝ)h~՝Nr6t'!BFB0=LS@0Vqu`$p=: 95@lH]3$;iBNјrpɔ&6[2/_^4Df%E<ѲV[ͤlX]~6.TŊf缗ŪT5N{Ȝ:툎_ƑJ";/?O.é -gJ76#Ke_Ow2cD`hA-\W${!OցYisx wfx~F{N+;o]p"Z%XWv[ t1e:c$\ҳ0AjOp ߅WAUFw=j[C*X'Te lPzGL'2k1BMAR"c=_5J} R7l\,_g ú!N<">b©%PIdi=dZjLܯBsKwh V2XR oT}#*SqƘb ޶) !%HD$L@幍{+yU;)тnK Q#=Vա%ڣrX&WHS+4-0V̼ݓZtWB\W=L$(S2,r{slˣ_zMwa0E+U 7} ,0>MD:A/2y\k8|#L0SXPjv@?R,6ǟ&!>HPw”z y>.k%d6Ge!( "&FA]?5הnWu7hn+Jl#L6[ZU6/u7v_6r8*w#YCcnuK=P<'f Uۑ|S/ Y^Xȸ>XzϴbHnm^iWQ(Nh*4 a=7EHݟm&jn=}&bJf1A0r?_Ն2c WOWU&<ӆ:١(cb+|ɔ`aS^1FRi('.JeSmL[nԄAk!ISƥe8ઉY50Ɔ;Ie);+:´ZȓsQO_-9- |qX⛃uM`K'Pg&"BVƧ''~1P&!W)x{oYed5jUwv'ͱv2%a ޒtu-ۓCIZ</iK{P'12O,T s 4!$S9M`rNښK9N##'awǗy3 ^x`GtX/F.J'Gaiiehq;uguDRSh+3@gV*bf!D1 7Q]BgǦ&HI"}47~9ŀvr' j=%[U}9fkbd/ԏ ~+u8 Y&O,:5ҝC$qzNnd jt;& m!K7;L(2908+#D4R/CQ 3</l㬿f5ini> VTe7xVM"ŀt23a)* |:Y$,6aL5/pt*丶{eS$š=K+a<hzGlpFQw^|$N\93!abK@4W"+C-7w|COO_WTƗ']oif81ڧT|ADdLMXɗM0K4q =nT[Y0%܀  \RPD-`') tL(Kpz\1)QMP:)G*J*9lϬf'ZSZ%P6 IGأbNR~eR|Cb"ż ;NL{v)'9+0KQB]f _̄YUY t΀ Hsp$KWp-3MRv AW8ǿ2B $˲PA;dgPfuue.ċ6,70( jʋ"O=hn##jD-C0M <;`J".X)JgPn|4iI9=_iOa_GD# ᗌ=3T+DH &1O$瓽 jl>0ulMͣ ЦW0>c =oL)Q hm_w=}BYCpƝ)Z)YTֈY؅jWLj3#Ah"S*^ E+kz"FHj*V~2n0^HN%$]\S1cȆA. IE.|Zoɂ[{ /1Rrg0?L C6.jQHiH=3x4[ ;SzPDʧ@ ;5%0'z _Mf@#)ϓ\(&fm5km)"j>tk%vOF3Mlqb%Q]YL,+ݽ 6.U˭Rb.5_wZiy#L1bxfh)maC?1 6Ow6|*X\uJMWLC,Lp]k&3{,fԏ~=}xZѦnvVqvEpÍE:k\i;W3APS1ULi" Y߱?S)/ U;^&YzfRW贼Uwr- @-+m 4QrIBb}z-ԋ\whJA8}OˠlQ)kqM YM/(hN[C FCԓ C]Ó/E,j~e&*ń(cd a8p>gbÄoldrwPyc| Eɭ>`(,P_P^J~yL=RAh:i?uRȻGmrf6JC wȰ^ h).#"' 1@&T#c b#/U `ōkfNIze GM\;N<^Wtx<К4^o7wDR)|P׷JCv +4C d{҈,1*Kz :䲯٫zl0~ kz'Dv\31Jm{?J\.jjmإZ3h`8Qr^J 5b0GeX )"ʼn+kRX@ܚ'>[3ݕ>xv zJK.KԜHm(k)OUTn`)phf1eg.n1 ~VXI].liܭыg]GEЀ'gȏиғ8 hXBwTS,,EVG7ܾBɹI m2I~"$5sIYfwj0)O٣hqiOB0wqd[k*`QLt,0X ):%|(=MrJ,]Skh<#Kg셴+}D)oy-8xmCR>}o!zb I郗ٕjЭ,gTqF&]ͶQH!JH+JFW)az~b~u;=DJnhM) Idlbܟ4_)hm+1اcXXLENctFgU[[O(N.9\E"wc !yϢm;"8 a";;rb▕kSSy`g!|.3"鑻&g.PB10Mܷ~r j+J@a糀WdD܋ X56BނsI%ٕS>'yO'8W'IWF( >yuJlMY٫&ʤhcv*#D"G0]t]X.uԙ% 8J ,B $WrRQ30FY51o)؋)H~6`8> IPL+ 69?(|=saVgC0A@"I.k6iW€uި #Tn3ĨFˑn5(a霆8kX{[Hy$68 2*ygY ;s}HvlBD![0>{g2=6+Q Z Sb?hn UReI:"Z [\oW薙y%q{9@l7]YYҠ:|##[3&4:wq]xO$=xT]Bp 1WPUD|;Ը~ gy~MB3ʘ ;j Fg؂}rҵGXc*(ɂaׂ|0둍 9QMG: ob#|y1&VJ f*Kb2qJRB-hn3}:GPIob{:c|q+N!\\GZAl%:HQ|ˈn 30 pu]"fA7" xd%G|hzF$*݁K1Yo)f2EXd]N8zu%eg 8ccnmX )0f ¥y'PNvbb-%FVǷ /NR1}KBy ]Eé-~6f럃Юm@I܇'zM[4K&*3M"Hau1O4IlU81"P N3MG)V QHad QW[*'gu|D%kB*ulf4*/pggt;AGdV@{؇0o*io^6JJVϭ[dpP+g O3! ĭCPƢfn&7foK1ȁYl+ q@.?}}gqܖ]OSKO{ *s6 k|JjWZٕHU#4'wPw=0Ϯ?)c Lߵo][?ڛiۖqͤzHZ Q2TUenE8>iJˑ !kn²JtPΤ‹48TC;̻{ X vHެr* H0͘~LJ S!g@J袥.m/RDp q̏}:CG}t-wl ޹V`XbYݓ+óÖM]Hc6!6Η@QFem'oj TROA}7w?"hiG mTg&bGו)7UziAh_I؍8F';?9=ay qǒ;祸"5&'NV V㚍4VV,hV,lmCc<an׃"x crXlyK3]QU( 4N-+W mpXQU;?_ΰiϝ)"0uu"qiÙi3C$}AJL>e)N㭡jWf70ƀr" LԘTUYdG, ?NuJ|>k!!qmjgW ǧ2{sv{Fe-$"՞i-blDapZOM2L5Cӧ,Z+Md ?QjLVrԶv~DPeg\]3]iȉǻJflNx(`X}"moe]SM+=!X(COG'P!F2'MR偛JzCw$bFЍ*GoLZ߭/_3!3JqDroE[h e{ vk?F ?s`O$I33''IdI$̓OϒI9LI̞O2dI$32s$2p ,rI'$<2dI)$Jd&I~rO3>rIəI9$rC3&Nd~rIs2I̙&O3$fI'99$3$$$dsg$3Ls̟>rg$$rLL$&I9ɒNsI$$2I&|$dϒLL$s$939$|L$$?'$$NdfNdI|O$ϜNI'O9dI2d̙rI3̒I%NI2I$2I92gdd$ϞI|2II&I&O&dO39?&I&rI̓Iy'ɒO&~NIy>O$'3y3&LNfI2d$rrI&rs2d9'ɒ~Lg>I$2Ifs$I&L32dO&g|ϒO9'$$2d2g2dLɒNId<9$$fIs'32y$?3$̓39$9$N>L2s92I33$$&IsfI?&NLdə2sL'ɟ2yI39ɓ$̒g$$&I''$39$'3dIds2I3dry32fOI3ə'3'>Lryrfd9&LI9'>sII'2d̙9$d&I2~d'&|2LI9O&rLd$sdɜ9$''$9&IO$̒gI$y$d39ɜd$2I2L92LI9$M$Ly9də&dI92fI9>9I2g?9$$̓I3d399$̜&I99L2I2d~dɓ'L$'32rO$O<'RgYL% Q@ P(!vG"? P&៘T+1) bkTyc("d@GBq:VpYdq\Q4"B\Jy4l)jr"{x[jWִb@aAǚr œ5&y) @\6a>s7e_j+ޟY`ge$|&Ĵ+C,Ψ^#`y UߴmAhe!mLFO:?ZD1hqe'4xMɍESyŠ=oκ P)x%'veL{OoLG 4ʞ*ȿ7j>OE4D`H1Skғ!CV?K[NҾ̽>٪==&I#kEVcUsWdޖOQQE*H$ɗ;COd:?$$)a U\/8)WYgQ- p-H""v]!`|\¦pjKJC AL=\Q[]ˆvhsCc{Hl&C|~*qvMJBT ϣRɿR)B_msRƹe!o쀂/a X _&V.5 RgBBuۿqLIb6RrrСj)UYNҬe,ZUN )`t2ͧ]R#[# [䨹%俓GR,̗ [Wo46|Zz?#4->BC='(pٓJ9H/hS%O}[੕VOeZ3ox'y$oŋ7f]4„MY@\ǝ/G.p2\~]YL GIխJ1LGeae}vlrP5%^k+VP?OVIT=qM vo2'mvAK5uS^X2LgxBW3CXS8a.Ɩ[.SZ@CRSxDyB =#uyS} r)6Eޤv>o[Z5݀J-2_60*ˎ-kЦS~ѱȭ.UnQkݘ}5M"7EUk6)Y땿v^Ʉ CԱAS*ha6Ę:e&2LehHt'&o+vɵjrˁ O;W\Kiڔ{cGj]GX3(5Լ ;W59CRw~΋ JI*JORPKoݝ{ 8\ HBnjN찟Z2\{f8tYpk LV4r {gCHkdyZDKܺZ.m Y/-sU.ynMD#fWЩfhu'K>݈-W|.S^P?3q.z5Zqpa)~vi+ n*> ]Olnl=92^]{;YQ)sd:l1y#e>"Z ٩/fL;.tjҬ~P{J)^4dN>fne9QC>oPo<̇Pi)9yFRЪ1&2UuI vt&. \L]^8{>)JGt/u=l%8QA`&a1@@֧4eM`OVPJ?2%Mw "ZϤK!=-śm1AFomEY"fD ʏ?Zq{T3*Y&nC#|r3Ͱ ǂdz|ڝO$d3dn2#+9@? -H^wYaW6wR̊Xݳ=dIP`V]3ב^1eVuLx#L)X\Lт/vy1Pf<܇*EKYHՃyq Jrܔ(Lw9H/uczfy:-?ܺ$Yjp95/ך/lb6BfA^ ~a2l(jEhwTJ붤4%婩XIh[OK*#B@y*2zr,m'4;4*7 "MW7M;"Rl(ZN t.hmDyLx"Vb`i`kApQR8x{@$L]Rb8}.1j`ѺGɂhGnW?}w@|aUGpa成lR.aH̥ن蔜s>2ی$t4M $vu ^Yp%*gg4k=҂<RA@\:8ݪKg}m/p7 }/LT넔 =4FtPϷ- J~_jCKloCbAW+|ٲ[j͆'9e3, Z]տ%?MF xdr)FUZŠmվZ${ h: v1=ݥE{DE(6ዚe0$[0MjajoTFkN!x fĜNB|T(Ir6++՛5핖2/!e sZrH9W^-KA琮w*aH~89QΥm"̖Pש85G[R>罐g{XNk.TV ;z i삣oe%e ΫYcYCE4Kz9!& 㵍ՕLhG<*J~?ÝErg;໽~+8L$󠴅tgtyPvI~w㾩U?(CQQțKT *sD䳽;ʆdU΢VDbctP,PȆnQkPW'jZ16A.A81lӐȭ!"6~z^{3`rfghD{BB5ZH0h`l!R*Hg:dom Y'h4q|{ ;_p C?W{Iq)M#D{WSfCkN;h\ cٛ4SE` nY]yTcd[fAPLj?fhO); UlUӂZuftcl^ ȢBQ&H, Dm]H}0U%$T=N> p2z"fjjH8w#Ei$I4.LkфR!,IсP'f+RF Ѯ&OR|]'NA{-eaHGiNځv.;,^y229~T'ڛw"# zQA"4φLhelIF:I]j ?uBj!vvaiFx5'U\DmFm) r;4O;\(-Ƀ44?cӵљy'syfHR i 0d--(#xsZu_ZbS(4|;;H<І'/d#cH(2^f4˗R{2|h4ɯSeR=1}%u`*oSKlמJ]+cV)Reb*zs)Mf?K`8+% ,yX'Whˠ,bc{0cT/^kho)mMDc4ҽ\fVfFoK~D MטF蠖KwT̖%u47_BM^7^z8O}SZ Qrua1Un A"hcxІo E@٘K*ThUU!?4<2NF1xӂDҨjS]ys+Qoo ˪J 矞OeoY^ ol|WRgAW"i9oj'ATV;gfLSvCƄ7)X,ij !4rPwA9Z& (OЈk ^ )33úZj˯1RMH[ص'@%X$Uo:5 Pc\p5&{SSb8&t;&V8ːyl՘*e#"L/gn'%v\8 ͝d_mNR!-#C$k*U?9Oq ikUץnٰJEP!8!a%dNR |mRKb#j, ^F$.ޥ9Jړz͡!G ƾU %Lkf,jHd6R3G碴,8" iAd:csD3dJG5uKjYhIL^AX TLR{'?1Z 4^ULnf+#~,udCWdȨNnG91+[z`I){nd^kn#F~+ 4H=ىNM7°B+1F('fm5E>(*`s@y,(W(;:ͱLnWxP"![ Na/TEAQYgu81LhXuj6w=swc^ZVk njxT_j-I`|43i|x#>2l ,|#]9(TXNGQM S dv-a]PmqSn!Fh&ÿ֒%3(V-Ң(r%Im8R^Z̎!2Z=\"pcb1+b7,*99 *p}Z4!יtV?(t_S.s ډA"C6モഡB%9f˹#x'XJ+I Wd/[-限}e?G ]]GNzwK #.YD&.[ +8fQ#kҀ0 E2 7z/T#&{Z(XY|6cɀy+E8m{E( lg>j f+1% @N:?u7ofA %[1%0w7nQ՟Z񥦷S[f AqfT iG DyA &۸[TVį@5W14^{ FMtnqHƁ<BUQo`DlV2> qUϞJr2>wtq)9_vr⫅G(f-L_)v|ғuhL0,K-ZVfW/΄9e)#UˠQJBC.a0vZDzu!Lnb8 gkMIMg:V:GgV:쬳M#@rh@vY&SvʞN$L&ʽ4JKfpT'P&=\5sBG(D]#|a rd0wےI^zԖo9na%~6?#n~Q4'-`rE X &~5>|(Kj =%IKj|<m/g":|9"=vj]7 g4c2v ;0NTxGQ ׅIKr>Kc*O-wlR%3$ǯ/Xb؜)iDX"NԆp) |f݈X2iNcb{|sa"yՑ^@+%Rޫ%[E/{^obbuu\q <׺72Fw.˜su!n/V]ŖZ,Tn @yH#ɮin~{Qa7H6K/"@K;"%e?<%L&M92wHR7}EX'pjAN[cc!Y9],=qxV4:5׌LF|#:K'Qs_XxZEw(ܫJ֢9黸Ʀf$٫7x%+\bB6 ؙm…4 6P&Nzq7&fZ5\ҕԕieI~']E}jBDu#Jĵ>Xdg[YR/4{. . Gx乩5|T2 UXM* IDRx;Uf.ົnM( A$_Wo MŸRrsX"<3 IgV0F$jZ, G]Ωb6&MJ/5)7:6:e^qgZ_-$GP:!.癇g*%Aٱ+S57'$9bՂwo6:# ҄uA6Xh1`mcuF|:QXcw37N 0@]֕VplY*4Gk-GB(h+'Q%fcE >}(;igbx7ڨ+b1 c$;K$ndʔP"'9A4yqń#|dN6hz,֙xf Ckrk`xtaw@gVFٴ)tTqcbfltZGf2Yo|YIpBϪqŽsm>^o Md ^2ALߖ|'RQ~BtlG$ń wٝϖGY\Ny-o qmɱ+k襨ќ5wמW}!DuvKǹm݌.Ȝ!F㢼)"Iiv(雀w>m|~PwgGEb\"p/TPS͊7奵X¿=#zjN,G M¼Qg'%AҸkzq6VGZ<ᮐ\5Ȧa~G@2԰3wr<LlM8e %U f2݁L}pUuM'Zv.l+ТNj5C4l7UZEIA~/ӷWM=308lϙ/j?VIe`0efeZ N\AO)tҊ@Bc"U ~q"C>IƘl^ w$[x;kY]wj ЍN^Ds Mϓp fY1o BMWF7I}\pF6x?pl;()o%:i>Z߲)$*7X$,eW57dG̓``6H{Ey}$4GL Mm/L"ɑ=\0|T+(c A( $ #D@8W#>X~~3boZc*MXnBIrp\K\Pm ͏!XyD2p@)),ysDu4hV W%6p OTVVz}^)YT*o^Ž[&b`3ա^D|es;Y)(xM\f^ |!N= ll#Hc9(;{N8-;Y~@f{d괗ٞ3j$S Ds,:fS $3{"#$uzs.# g{t?(&f)uZ[\dauf9xk%OK:}E/Pέbf*7-(x~|EEf[ Qn˷[ > w\jjΜEAⰛ3Eńr/1Ryvҕ6aʎ4ZzхԈg -J?JE.U96wgSR3Y bv޷4?z| (^r-pW;N;-@Ҋ ϨL^ 'kN]uj]tQ.#mCcdȪ^Ir)\S{UUR.\όu owI3*X_K[Z܃>!#|]q)rB5Ndi aw:G 1( )eź_*w ,UKl=ikl@.c5g #  5nd|=,{^c'>[eN%L{jpD14)-zl,p_އޖe-r&h]reANfk)@"\kJ1"d?˼ӆ[Um{ ̗]$抚vYU ~Dkgz@.Z{l<M$v+%s$1GS1T_jpSVzZW=eKr~+vv]υa]uQbz/Yd6G7qnu~h~OdXbG zP"V)N׷i#!]2C?mCGW^W3nYށ:+.٭vϟ[ztmRPBD@M3~p C*AϕP_!nFi+U3rqb^t&N-^9#_qƵvoսxEƊ EsӶf`oOl4:s]*t8Y1#~z2vyt"YGt-WLDUΗ%Hd{0Ž{b  5FhXFNf f%EY:Ɛ0b^V[jpHN{>PtιbxEgYr't덀!*rTƪA%Īgm[F~۬S@WQ&ѱ,աȐI}EW~Sr,o(ke~L=YR*bɣ s$LyL>r~y$̒rL'I'$gI9$o$fI$9$̟$fOI$2L'$dɞy$d'g9O䓒O?3I2O&dI|3$tɓ$3?I'9$3dLL~g$䜙fy&Iɜy3$ə'$3'939$'$rdIs9|3̙93gɒd&IL2dINI'2I&rI̓3939&I92rrs&g$$I<''2dLLg̒Od$y9$NO?3$I'393>I'L$̒I)3&L9̙$2I2I$I&srLy3I$$L̜OI&rL>d3$$ɒII2LNI<9??O2y'ɓ̒dL9'ɒs$3&I &d$2L92O2I$ܓ'$I癓32L3Ny$yIl9'd$d~I>Id'L$̙I%2I'>d̜2s33<&L&Idy9|&I3>O&dL'$I$L9̙2|2frs?fdrdd93$99y$霜I,9ɓ2y''$rd9L$>IL3&NNN$3$dϞd'2Ng&dNfd>Nd$S&d&I3&I2NdɒrrI9'&I瓒I%@U%J DtU.#$Y0۲r"pJgeMTCQsI;tL5ef1T~3 Awf` |/+@P=vcz+oI+D{8O5a,9v{?=H'UUm̧HMǦt\p5A#+.@3TNjE]D~/hfd)`0mNut)sG?]< w!E@ԢYc!ݺً 8dy;by-xdPUQHI!̚>RU!NVoz91̚b5%䉫)|Sa$5X@lFG^ ΁. xXIυLI"s*qa{\cTUdv94 $eP%GY MɒmH&)$ hݶ-1!G &5ր\A_B³5jkMb![vyw|?w*SZNrw:DZ'"^HR,rc"&.;ʙ>"/ ' Ez6HSul=Z$JvDDÉX)QIBjt9B5[xtZcޟo#J6U|`n܂n P;}0Q/Ky CD"S}9:U:"--מ؇d]Ô%'\J:5I>e yߠ[I4fVM)K//%~7~C,M]Ƥƞ60XM<6 ?0H9ƓԠ02^=ZR\dܚ=Yx1>)+4\0 Ho*@q-5їa?x T?d@^W~-CX &Z ҞGЁsŷW(=ƍ5{NEO,}6>gJTlL2&e>B ݊UV6YJڭJ v|%| i!,9Yu?*Kȇ H. rϊB @V KK?TE"@nyrIx%-bմ~}GĮ>Mz(r791LR# ` S3b'NG5{ߏHwNJODACm]Q}yTT箜“exaz?ZmF--\c"-^!{x-^0SJNLTIS? I<s_LSEE$hA/UrG0˨#!,g vW!*Bsqٍe6T :VUƯ9vg;J-)n6P'"rLX/\A2[Ǥ 0"Ҁ+N=O +X]E:@ *wRbԩ@%>/$Ey!ZfFv-":(\p٬my;qNU|$a@ۚ\FEixZ_cxQXij7D },I'^=[>cm &Aٛ\[L5*J9[N(p 4=}ۃj|_GAshJ@E S.liZ{ QP %y nGPYs_nXk#Z@(lc[F20Xtlc+@ơD]BrW |Om0v w}eaM=AP(#y,Rcd%3e\r`sS^? 8 ތp1 I93GCn'p-Ή2ba)%R^oHWTʾҽG_7~ O)yѹ&O.gjŮ Նk?H]Խl)QѳIv*>ZC jݏƤ isAhrr:*5Cqth9X{X@0ga$9Hݏک.CPMR4f] ]I[yu'>l?N>Sb2Nc2{AɤwoV=u6WV[(ےJ(+c䗶I|"S X 5ֶ )2/WE&.j4-P@L➿shC#SlmVj|X*Y{TA1/ŕd^ ^hfǝ倄|0GXSML'к*av{@fΫs&\XLs; )2ߛMVxʞѫ>hK;QI VCRGXg\CnX@O>ئF (Th=/ycJ31F +WAµpX)̀"@-wOx(jU=ƊpcmJu5bc$JYUqr-70e3M8/"Ë E>ȕ]=n9%}RR rU)txɠ7,H"vUjEL-lxu|8HƓͭu%ʰ#Zb|]w{f0wbR"l鬃"l 0MMD@Z i BՄ0bgS଴&.`K$ aJ:AيJm{*;v䕺3_ug08aiw\~3W#d()co: 85$ʂ>OJ=_]"u"\\.vU z4T&Xä,%.Deŭ}(PRj"*ڽl4>^[O.7/X $CP'r-Ml1S3„w x8,/Ndb5Xpw @W^A#hdw2q(\,L[Y} KB\$Nȁ]HY< h=HXLBQ,('9Ҿ$0Ifƕ0ߧ޻m%6$k"lvW0XRƸ,ۦV0Vfe ;5i)O0n3aA}F[EQ>3H/?WA(pbE 3]SU8s.8R|)K!'FWa5_hrSJ:Pj1 3gC|*JiKD[с5Y55@lEʡa6?'2> C;;+uNb١۵NH!qC7̼s` +=*;V*JWb+tx[λڐO6 yi܌#5 ?q](+WUM&U`!C . `k^O(yřk5Fur3G&M*Ɖp1lUD+?Gv'A厠nPLE eYU@$oJRthcTWUUÉ'ӯY·w%r<&SM2S-4b)_aDQNs<5eeyaߊN=0Ζ_EljUqcHCslϋ?!l X38pakI}ܞs%+kRP"icXAϻHIg6n{+霐zWA-B2tϼqzW O]'af3]紿eXOPubXN:W-*ǾlZA߭NTPU@[tyW -h1vH[G'y& ^kcY%uօ+ɪusw'$)cLTΪ~q҂ lˬzsR5x_!yM>VAk䆗Wch(K;>Y9*[F.gg/'؋+B9SX~#)#IW҆3A =]k^C[@فv/џetspHtDBZ+J"y,B˓B]\OyFN p㈓pQZv9&h8jUakHrH8 V×Rf屙B{IBB12ZP}b\AW&x ?5Y kV3u JяkIPXGGJ<_7{{FT!`f"r׎B*hy'2aP#]p6QVwӳ"xe"`=OdyFqGXƍ=,tQ1]~Ke XNm0/2[g)/sQ*\2X`2bO1eFI쉗UR9Y 00?(館^S#.:u:Lm"l*ܑKڟnf=#$w ܢÞt *'}7߷DžQ%O'DNɃBv<ѴuޚǸ@VDd@ZO-?(o!ﮰa5Wdߢ\ ]i OB˂q)=>pxYcB)[@&ɒ/{5Hp="r7a NP\sa3F8Qs/QlE_-j4Ca d'ݑپy)4ӝ4gKipLrfKK)#6L0&KHUe"h ~Z( dFP+Ud]$1D\'I a<;ahdsg_$ȮI%KJM5<1ۢסX3dx'b$Hw8ݏJwJͦ m\Pxm>¬P{iVaY&&RfD%yOhL&0[i={ WdžN%(s|)Ep| \0LC}(sJ>Q\VE!S"&3^hZp!"۶A ,r:78&Ǥ)#MwMT H n0SٌI\OR4IFYtnԘpGURrC :.uS9~!<}с[U$5%MRKQe=d95b4a~Uڸ+%/]M.l*94;e%εG B&hėqT;ݽ,/Uw7% $#pn\i-jVVɵh VS^QuU?o<0Zt 6ĚΊm8]'5#1NTK;Ii RUd.y{C?L6HV"xiQg׺%zўuf7l^pn1)Ar? %g`SKW;[Cü`~^B:K `WfĢ"Q6+/pK>^>޵Zk^iE5>9 ޻YgM#STC p1 ނg]ҍqV]mhC+)>_Qi\~TEPfDGbL7 a=KN洔^Rb ;BiZ<EW\0'i1 e8"x{TveAG$QP" Ϲnv5No0n #8>`LǯJ%]ՕګLhG6~8<K* _| Qfi8$M?GYGZ,F\T6I8Q~U.%Zg^Y,el(XæBy|ssWxpšw9j&o?/oҵE6CQZ-kz){*9NL$`u~heof*"go+kA*ao TD0gYy|$ÇYpÂ[rRI4Y1!L= 9D~mp#P Sn('HHgүȮd%sgoUxdXNS,J T;(-Sn7av. r/}G~hIE Y1,zb*K /^=Bد*P6wUA!L^[^S#rv;c#0&'G+)(nRWuIx2!tzDޯ "aeg JgƢ{sĹ*I~rlw1M,`#8h"W.lJLkbDXXے~"##հH\ͪQğؼBzAx%U[;'K0Vi7b`)e(G ~hgpo!+I8 `E&}&ҽ^/|"S(#&4q{.0Ƭ،7ei4VcꋞA$ktZT"-f؁6}+0v7El[~V߀>am”| gy ݝh0% ssk1[h6Iv It},nWFuQs_=l?:ÂJcz1;Y ŒeTYѽ-F0qM~Gq快x`& UcsEwBD(K Ho|IZmPR/XЫSldԁ$YaiIx,)<zj=FEQKH6sw,c$׉lQ8Eܰ +c^mSTW 2?\us[9[lMŮ[U`2G)N55.N^"x"aB\/1 Z>}X5 ?1#uk1j>|g"iQDT{Aʵp^ˎ tJDZɜh Fߕ~>Xɦmd+]y WD\ɛYbY,n+]]JekeAVr$v]څmS;RaW;v+U5'"׸Zh/NA;pR3(5DZ9rFP LFt8z@vI)̎y5]D(Y!7(OlRgs(.ơ Ei- 4;^nRY״)g-THŤVA/=?:J~>Ӻ~nhKR&-Lr{1j[PFokxNO LqmsU;J{U5a(/,cǤ4=DƦ7p)c*-(&ĢѶpTHjҞdnw(3Y-yq\Nڍ{v|ݝ\$ZQ}? aM?df \4yD*T1(+f89 6jQҜ !D?!C<4Ft+|K$|B=t!4q9FPՒ92ϯQxx,"[*Doeie+dmL 5XR/{x_ӏ^꘣9Yu^ԓ-rYzg#7/ҾEt/*z4^*Hx`܌W6 1ȡO p 2*4@fWOeV!b,@"/~ZIp݈3Imq"dټc H3_IمAjOBl<ؘ(^G7NA(!Bc3 +WEq\ޒ$K:tŅ!9y#lJZ.e I$$ȍcYHH?݆ҧ{*wU(!_Rx+&.뮕I*ɳGb[8Ƽӵd*ƆN~~āO_+d' Ýdt֊5hdMʂ}/TE[^|d1Z"i;֦habU,>H䖺QBet>8z_P'hWvXWyi *kG QIB+^kz*&yxeVM%V3TE3A\!oE#D$t ,0UbH&B<123ſu%uACqwP%npDc/"Zg5I+e)[HmV8.ulMխ^ae,E=eؖ HbVA挗,1%@y2v.kjqF3pbGs^71]JEi+gyiԈ/m{VT#%UfdB2eQ^76$fݠ *P6߃ef{iH9O dKL쾲T @8T` =M Bv[, hVb6&BP@@yt*y&#YP36Xhcr ڑӒ'sLW9x8# ,"ep78]M Ƀzd;>W.eQ61H(!mn)zYW cYv >=0ӿ9Rא>0l.QH̒O$2OrI3ə$3d2g2fI''y&L2dI$2|g$2Irr2d̞|$dgfI$''9&d<&I?NI2I$$ɓ$d2drdə2fNgI$ٙ9?<$ dO9N$&ydI3&NL>I2Ld'&rI'ɇ2dɒI$9'?I2g>sI'əNI2I32rI99O9I&IOg$I&NI&d2I$$zs$9$9$y9&O?'<3'$dI2|O2I99gds&d$$$9'NLsI39'3Oyɜ2g$&I?L̓$3$IyINy<$9&LI&I>I$Lә>sILO3y9&dLI'&də$9̓d&s&L2d&II'$$|rLɓ'y'2IϞLɓ$̓$$NINfy&LNfI$I$3$2fId$2s<'rO9Lϙ3$$<$i'fLd2I''&ONLrdII'2d&g2y2L9I3I&L2NfL䜓I?9fIO&I'9\2I<ϒIɜdI'$NII$I92L'̒dd3ϒI̒I2frI2d$I?Ig&L$$dɒI>LI7&y&I0Y N%|Y~M !;m,漿 ë-:-YfdMu_l?uG>ވV ^&[N7J(wޡΛ>'ecf\}ۈ&+LyJᙶq qJR>ҏQؗLR-K$CB(dJ~ HեqY0C@Yh{:D>XtE#1h)xt>@O*R|8>80.1"qpٽ=Bj*yV8#\dt˘'c-Z: c+e2vre˥drFH`)'Y|EԱ޹i']. T'Q1w -޲tۊ܁WF7+Ƀ!R(f %{e=j0*皹h5t<\V*Lp28@()tv90낫璤u98`yѳ5PoNz`;5|% /?ٕ,:?]R3V-ahz %TKM_0NѵT3iktLhI <1ڟ!$-`Vzj%\xT0j)1Y(5jZᖋ+m?@/8C:pơ+zԟozSo\󞳵i#bZ(uJ@ŔxIG(1Ab[h}9%Nk  NPKJACP< ܰT?:=js [e+њBMo,^`UMĥE@O¢1ZmpI>[5A]-Sf&]ip6\٤?JPF/ZͥM?^>\c\Fh3@0S@&i*pL9csDDg3!2$:m =b;9AgwyܝaCW;E1!y<ˁr&7I'X HꛎlkL4cdGq7ঢ2&{Ё͹D(0%TC3a &?Ǻ:79%7M_=!ETLUlqhdm(b`"Y-gmRs9!x1w}w3#/%|LQ2|+ G6{ <UjIf~}S.}3~@Ku&zY 8j0 pZYY¶X:53ɕpէԹ,6Kye(8Vel~\"Cf^mBTYwi_7GElЭ(}@Rn+YpZ,eYvӊti G>Fl {ٰI¢QBD?ڇUBDu!b6a0_~j2kQ P=#~Mya3w")H^t܂2*I-JWP@dI< 7y{";>>uv"6< 'ȗ E=Z4:zBKj”=XueLk*J4KW= ˇD!$lN x*[Qh,wnLd8߹Rv≯wЇݪ頃JĆ4^">^+!7dALi_~/6 <= "KI!I"QkֵKӖT} wP YGt.% { FLֶv.!U5 mc}skWMCg6 8)0dnC]d8mdo؛ZCED噖,D2?'>G1y^٩(H2)pBnuziqsc)أ0+2^Q[eq`E'&<i c #'C̲|/ 2Δ=`gЛYk} Wbc1Ap娥CA VxT #o7pJ"`cTA5!DRR,(x'\75>r=Ub숣O?d'>E{4b!13 lyس剌$SvȈhsEdTfL讗f#DQ6J5QJZ"{4q)L·"KU0yu1۔CF0U TOqd/T/.kVHҙACId *F ٙ$04vTl'o(5X˛3}wtu3sY P8}JxSVS/cj֫^zu RLu믋#A 4`_RƷ5zhJSo<?Knr##xqJh*mKbUWE8l 4"Z}iF΁nK|є LᓿgVR(dX PyDѡ#,K}1l9949`IՌ;mL|&:,րr0d_笠3Ę_򹑂֜0 gޠ:|.6șD/B&%@V-YH( Yߍ1H aĎB>I)C߶nZ.[pӚ!-<%b- +3@J5HDoD#j;Q(EC\)qD[uĊvɂ"ws 򳠫$૩X #'!{zn gaMD'TS|6 3*H&>A'( ܵ( 2ֹҵ_9*B(NQI?jBIg8hn@:3$jcǨ̯y03jSsF9t:Df9ߋb/4Ǚ#aWJ g_ń-d$;ؓmI@D_G~8w!B3nQ^fkɑtﮟeS^1oc`;t&Ubwavf=1 )V`fݿ*8=Ķ3قG"-Z8 ^O|$ Ϡ˚kT!0^LY35=TM!luJdM:-ȟAqBgDۚ%oWKuJS6& 2IBW>L;9MAˢg."Pw=~@[|pU,H+2;e%8w=zhں G&`p lts,l:OMv{e hVe^ϊ9;\WB˸Z6v< 㵹SUI/֍P쾛O&+Y#>Ko$4MVSŹ T냤{@p \ъ;mrI=%(H`y7:U~&IregY *:J#{MCC_Pm3=22Wzau(43Q)qjEu@U+dۗoH|g t@,fT j%=IY'R  8ܐpA;@u3.So][A.esE*[cdB=zT W5XYhgyQ<d~щWIp_QN)ύ}1/2 F`*FSuE]_H+ j.Q턗!&48w-X3G [$&:fĒx+v]Mk#)fa齍—j$LN64wg}HUYmH cpicy۠;U &oUxC{ o!B\p!dpPIȕ%+N*Ķ%'3 g&F?pΊ%'zH|EDoq" ͵p ̺wثڙyi/ph~ ^D7žT Fcwސ'LMuWw`rF>[Gzz3aK%k+OPVOHaiU{/ME u11aT4<a*-i2|C7;2fpcj9ZQ"ՋF$/A~KeP (n.Zk>#FOVfKPkԦ"(w@k5Ф+ X|`cPV65E%]PHϹM0}D vsP9&6*<ڤMx~Z6>q}eSI`1u5R~O*;;}$9Æl4"LÓ JiMKaUqƮ%8f{B XbYsi3E1}Y<1rN"5*SW(', !Dٕ/iKljT‘2U➭XSƗʦZ@==$j1]-áaS6 ,E$FwkJK3bK S(ZfXG([ӿ B\yis#bJ;b\y{UědvDl xuLB% @& "f4T{*IvaQu&~X68"+WU4zp%"E҈NMhҮ3uPg?ә0+I UgbIRnxGv@t`Xe>V>,~+PQ 76g(\ MRIU)'wL 0L`!de# ?^o!5CK, H>Is -ͧ!Y3 wn+~-\P%K)ݞsD`Tdf:wh[(GJ藝ZwlO˞@1ܞ>G:bByi1 m7I3Q1 'T~8w7y/].'H>IE@Zxu?I\P\£+0cOd!)>/LJlUBT9!b1VWf*TiRBb 3c9-/z5X 19A'Y6o--zV #ҷ\7|%OYέbk.Eςm6JcR`9縭'Kjcr6TSrk1i:l1݂&:>Ot)M,Ca OsUg Qޅ Yh+h҄XwakJ'NS*)YRc2buV-VE/Zyɂ0 ɺЃԑ"T 2l88 !]=>%oX/=^jzŗ|G1IL$Μ?yAT8&BeÒa[rpֆs/khJlH׃荭eM^ }ԏ~LupPmA.<ɒ\+I&Q>Kz,$]~A %㱝(`@0NXLu/v>x%{=!EH/w IzmiNYM6[$1eaQZ+)u+ֺk+%ϤYyxر% wbB'0^>هK`Cy^+[(4y ~'%LT}lMfAP]1[m N` aG TX{20 q}H/-Ă:ɽr3a9Za$j="e ,!j $<;ل)ExJ:1 j:1(mEtJ2n+g>dm1M͍W1~I">S;~"T<WƦWUٱ?`nma~":R+X.xwj!lp7A A@*JIo]HFe(W}ruSWU-٨tOI0$}iRGɕNkc\&$fy?:>\ID{l/@i" -J\r΍ ?yiХйNx>56,Ǐ}j$pBIP|_\hK8BB$b 5^FZeQsic -20Lu} 2eexCt8UƧá2w@u%2 7KůL^VM Wc8gjJNLLEVS |76csXzns 5*|Z4Nvֿ4A ޭ*LOwܽTGnޔz Z6߈##[ o Cvb=L]V3OqeGۄztC'"Of[.ëYd_„zw_ L0x{MkT]Sxf˦kyu|bo)ղP?>x2-b :Be\ 2ufˑ`Xqt0q>~JPVh&EzO$@PJ{-8. lcKɑ{xHZnj^7cZ@J$]2em*Sur&tj\:4tد0 e/=AtKPױ"Cr'U H}Iq>{IbK5sQC2:Q,!R:W2ZgM0rdʗ}:B FՐ?`L2Mz@3u9 ,fjLMm\"J9El!>1t8ܞbȘ8_P#" ^=EFO&7.jI GͬYG)ɐʊ>D?e˳#.v'ϓNI9&dd'92rI9<'$$'3$9>NL<$I9$ϓ3$d$9&I'$OٙIL|ɜ<$̜3̜dL$y2s2ds$fNrL̙'$NLLI2gL$2I3<&I$$N$NNI$<̙g9$ɓsNdI'$33s$ &də2LI$d&fI$I3$$̒Oyd2I&rs3d<$r939&I3$2fd$9$$I$>rI3992I9$I99|ɜ&I&də2d$$$<̙LddO$̙2dL̒I)NrI$$$I&I2fy$$sI3$K&L&I?drI?2I$&I&O$$L$'9&sI%$&L?2d&fy3&I2LIrNI'$y3'<&L2sL2dfdg3$rN|I9O <'2I3!|II93d$'fI2I$&d'd$rrd$$$$|$̙$9$_$9$$Y2y32I&I3ϒI%̙3$ Y Nf ei !|#ˤ$x溕<. }D<P,A/y?]Q. +_xn @Dh+$&F?" |t?&d9qxKˉ9QW >ŰK7r?b}OW Ös1 QMJ2xIZ fp PrWbQOUF ]$J2 ik+ qܶ$3<Зw\F/Lb 9-@|H&Ӗ}Y%|+)r|X+w"F`oY &''vSp/B.`T"gm%qM ?q.w.fro2\nvl=8rĜ ~]d5{ȴ*:؁K2xh5#\뻽G"qx.@c+$XX(w??'Dު!Y+$9˚7!{XtSfA=a=ƍ+~^uC5}g\y"jzQDQk,:cks'o8ǑPmJw&`%7y)$II4i&Ā_j4m=b.aJA9E0u~Л3̸JG9I`Y8dfi\,c*uB%ruqVqU\NM! ~9JS֥μW-ݪt"qymhdQACeFh kRd% h*YJH ,#Y0N7iyO WRm lGysYb%;O(4Hm&ܫX3t2@υY)cAK|7L]%肚J)-ݐSTѨr]?ʆFik,J/IRX1KVxՈnay:)N<Qܷ476m((uD7!fTY7lٻI7߰QvZ|+*v44f@|_ĮRDC {0lŕb %1o[Dvd Abڜ *tмQ$IGpF>v)6T@P9,!O=t%03UDCjK/$ONAg{-G-g'4 VMWZ ?!GԙWv'*DsEgdVtⷺ`q7vVl A)6ZaػT_RYPՂӊZ$X~z)5KkMaI6fuwc¿jI^-фgi"3x2x<I:NhY˜Vlm>sYD$k-V˧٥7ė2pɔ^R#]a.)@/Ϗ&~""ZyQNB낸U'ʹ&!F,kH:H[!녋3цJ=S*>Pڧ@.pBJ0aH@@m}B[MF+J82%A; CqXGzb6 L7"fxΖڒMZ#e&T+lQ:4SV*TBuvۢJn ͘Sc$}=+.̵ ؇*$9XCIafU`㒂{حD~^+3G\0Pd)cKJ/1q#br/_>'B#O2>BaL2tz]2qJ$^r . %$t7 B.A^ǣI|RS ؓM^;RaEKq[#'0׼ӧNIr*a8r`«O6U.SHj.Ab[PJ 9ƥ#Vdaq[΍2;{ RC&6B]!:g"eci&U:9xoixqV+h'hXJa7q"ȖKQvBp5BS_qUT*qpHɣE uPXAIF$\2:B1VJ6Z:Da1PS]XF/ɶZ%[]IZDq%m(V?ҘC"FNc˄AB fJs%GEү 2-*b;ZTPKOѴB4ٵucLwt3ђ0C"ujAOi6(oYTZ 复nmWx o1ߧ- 53XĪW$UB7O7"#I^E`B+cDѺOR&rD@u=M [Et QE$hqH2 IlIb9uЧ P!EvZV_aDn0(m03ƹ8wy-'%$KL"=pjFCtS轶xvA8&Ls!6,غ){I>bYmIZ"<UeP<3 1\0^LZ,PNTR8iM?j]bG悡q/ҥґb \ډ>X|r0,@T/.d)3"#VQ"$U@h[2d*ھ(apꌫ+0X&"B ֯Iml6Q!*w(bzp*čq)FsI_г5q#Ob$SuWN?! Ps7ۭӲY҄D5[j?- *w^9aCi`-6v{bJ N-xto53"xB5q`r58\ŭz7ݤ֣;*d2הnP# *jJ!M(DcT]_+ɑ\N a%&\/cH))$\mʧP"PW*mOɢ3U߲j2De9lHʄZH¾n`fX$tAZ*t.㨩Ѡ!'HK;N@M>gС䊰ݢ9~_"C݄?Üο%pR̭~5VkvkM[1N1YwLv&WܝR8ORBDDR#,{IBEsϹ7sQ2Y2 s8noO"?"LTQG l$;/:h<>yhmFˆR#1cc BTџ4?4čL hsQ)8N8Ϡ9="$PJ1 Z UE3 ùpۧL)xLSߨ BxE67_7bgoJ^&Tb5Oelo=2f~ں\l\cOkmro^^z醲 tpmM Iw/U[Xe˙^^ݯHwtt`_LYɮֶڙ,Jy9~ZfC2{JO< 0< V/߇p\b S)@&ۛ 41PLQ?:wiG6 $rC`ݔrPCIt/{a3z ZehOMQʉZC)3".g )U&U@@ބzT1@R0Q̧F h܋NJ(CteھirnJey5*?P?_&.'[Yp-5wHa/#̺f_ˉb7]KEV@i#H "-`JL[B"#_Qu̖ӵ?כzG?OISV7䝨'&UxZ,(xV%܈U?s$n JH=%m:"brnJYVf=YFjsN{[jޒ,gce35XȪ͍0/Saz,wȃ%CZI|T`QDU!"5+3JhbQuϐYPf; Sm*>@y^&.Rpe A?1uRt" } `̠Bu,TbDP%k褠O:fXLu~];#3\02ˆ+mG4BEC,N#h*e=;[i.i.\K_1AW1>j*Vɜ^'p:WmlމZY/ f̉a:@k"2S/?F{at jUugEsD zYZ n@hbK>8cuIG[OӍCtfDUFCbcr``yI#/^(e"nO E=C5c!'9i̸=aOe5"ś/]l*n~d‹! #[7,H{Q%N \i: zD`zG#ʘrYҞ&LuIEkal< ROЦ(5bɀC+X&o0ŏjzRb+K',vҍۿ?gļs.`mǹ3eu;hޒsL`ft@U >g k2RhGθfBX 3Hn=!$?0@WdGX3I GPWJaۓ5WzS-.dYy\R~PE2;!9+ӝRwKY$9~ .[~lYΑ.]PAd4hi%{KP&ӿN?+ lYONdr==iϻ$)ʅpGt'˘II }wDAjDJWzUԙіjx aS"=kz/$z^rh &< yE;Ǒ-p}d,G@Zs|u.lPL\gd] I18Wpe]/"5F`v]NS}dLDeTV~AF׳']䩜8DP3 y+&Zc:UHdV$<RHpFX*eIIyH%"ij *W`@$h)Z9G)p31yBFwqp6{nU!w!R"xTXfa|b:l@ pUn(K}0I v6`?d٣yjxx<"\߅ 9[IO?U}DEdℷV\&ѯa~-b8olM--FQ~X1/x,T#U88X=LCo v$:O7%~(%sE},Ƭ5,, r9g!RV;/\ Uo+q`dy Qڂ%<8$ɒyIə$$3'2L$y$&L$$NI<$$d9$9$$I&y$2Ng&ry'I'<'2L$'$$I93!93$&Od9$II9'$Lɓ$$fNrL&I<&IdL$&I'2dI%I39I$ɓI$2Nd$'d2rI$K<>s$'&I99'L9&O?$fI$'3$?9&y9I2Lg$$d&yrI$̓'&~g9$癜|dII92NIəN~I&frffrrI$dϒNrLɟ3NLs999LIIrNNrI$'2s$s$L̓9y$̙$NddOL>s2LLI3$939I'I$ɓ39$9$'I&I9L$ϙ$sL̟2NfL&dO&I3&fNI̙ɓ2I&I$Lɜ$2I>fg2fL>g'2O&'O3O|'2L&I92L>O$Idə>L3&I&ffg̙$̙LI$&ILɒrfIL'2d2dffd2I<99$ɓ3$LfI's'L$ɒ~d̙&rd$s'$2fLLJL̙$Nuo`y'?t7OVjV$POs-$E d‚B³%m6t9K?Zgu@P)܄ɩT[/K2%LśgwvlfJEP2ox;k7AWXA8s0܂/ k_JȉB%)T(=F XTN$Qk`hURwlu&k6nk lkSӐ1v.o?T6I&XC,$ª3B*/VTVIH3Y¥N$G "cirɰ? C;뛁]~ΒVPOG"pne ˔;K` HlUEZ5^ Qm|e\w'κ8 溕)'!PD~0%Zqށ]\N yJ4BE^,f費"yuCpXsP,Mv._]GwKcVDQYLY\2;烋P9!eĥ Rpx y3] Dfpx!?uJ+JeA36:wc F 5*"lqXYvwU MQKqg )aON??`[X&W )Eoe0vHsjcu [~'?n\.r5ȕХ&MZ:%_H\,Y/v-@B۪7*y[/MTM"\3Qn{BXӔlj/p8' :ڣu͐1i=vIXC[DB:?U{rn²,cQL`sARd0=wh/$f5aj\X ;W틛xo;c A%Ǧ] mT`𖋢arp*lc A*EngD⹹ܟs*>TJܑӵ{9eUճB"ioѩy׊hB8(,zå ,E:?{S\ڂEM=ik-dIRI;&\e!~ Flq--D-kP M|F ]b?pQZ X2i-; ]low2[4&MpEig=rfAHgoQd e覰O+-  4G6ڶDZ Eε] i"k_q-{FBTd?'_G?RunW킪tj 9?UuGv?-$`+"(J]u "Xݝq{=F,pOnf%o> ZNGNyI19+hYb pm L.슃Q:Ҏj>Rܕf$O754V7@6Ϯ:wm\.& I٢#߈_ mu8 >u*F+-\ѕC"l4GQ%M.kv⢀e! h5FxXBH7aW7s2K{m0$/H+&_͢$sH?p[W.,yWbNBZhwлmv`^XHTQoHS3V$OH?`(Gzw5! dT"-Q1 YX4J8awYrJ _$E;[ 175.GS-l"8<ё!BC,k@dȔe|;v% &S6WCN'$=~wB5fz{ք4IJ|fWAN GT>g`I5>qA$WF[yFuna0%ѝ?jZOyGA;;F HM=GGӯYpQq[ʉVs],DrԕE-ljЂ}fYv^ȇ:$Hnq4=賞eG8uyD hO !=/ pJ9q_4Lԝ@- 0=(p7=zf7 "(aE35PVV+3_BTvQeUoU+ 8% RrOHpՖ̡GB;<VҫOQ-2eݫ<[}=h,ԙ9u)%R ͑k[Β]4'?zj%:ʮZy<D_ WHg 5S:TDYu~Ӎ6c& **?sV7-+NX' Yovao$+<}hyՕΛKՉEHUrUC}`JYŰe_6\ r1.9&fumczOk ~FGrrvIgxN_wb@o!njf]i=[KkkH@+; bkjІ*v eK?wgp5 }qLZOcn FI aV[o HXc0R.bOR%U|ܣ!zf"̄SH9Ĉ @žS{9خ p4Ô1zmdu} +s @oCZ č ͮ8FΩ9Q^ 9tcgC@d j[ mJ1ׯQ[~{߉>M+(ز[q5 w ܿl-Kn˲D9M>nPSeB.Y*`FdcoLFج dЧ~9ntRŸ(6I*k@UŒD*pT)9:Nr*h^H-قLя)3Poظ+G8),V#Q1cNqtDr5eѮXJY;B8xAƒj)5brVp"D᜔m,AT4Ah[pPTGL\\a aV/?rGP{Lk>PV($xIۚbO1F/KW38;m弓u<+k4\@Z/bMu0 X YM`ʕji!ep{cD' 1D*Wat[bRakhF5A:ƬS"WPш?4_ovȯZ~<9 X *b 'M|^/7?$5#"f!m{CBvTʬ7k: p<O,ɼ0a;G]{W h͚sK{l(6J(腜@G lci{@VwJ*71%9_K6Bǩ'Y/$H1NNl>JS+TS}b+aw͠Sz{)rBuc*8ƍb "%Q9Y815颷-b(KJYOrR:sͶs _y6N[= ދ{nG6n˫Ro(w2-)S`cn 5J,&ڏ 6&yvE9f4??!ySVQk&EtW7T4!6wmB  kxŊ*zC*%Gf t0K4kf(!sTЎ$} Հ]L$$qoO? :U$YCZB6h[NlEt[9YT2T(z/SZ )vli[fPK+Xو6I]O,>K[<#+˖`?|7Fݞ} u|9%ӳJ(XGmd_2g4!2?:cHW !S i(`[͔5]~R 6R >XHJ!ٔ ETBn* 0^sC`Cp$bOUn?0PZY4N5iJ6Z@ʃrdSR{tg=h4:,lUg7qUHK( ʴhԵ4Jqݿҳn "7vkp0]{9p;XdKQ$MjQڇa XN!<䟵Eu@1NCHQtDrHJhPt6x|-O37D]nWCWn%Jy):K.=7pr|Ih6L_#kS9 B Fl{Gbv8 gH`QU?lܗ8?[Dǜ3~$Iɓ'Irg&rd'2rNO̙&d2|I9ɞI2O&s&INNLI̙$2NI$33$ɓ3ɒI$9gNgI<$$2fg3&Is2I$̜&I3''fO3y'&d̓3$II2d3$NdyO$&rI&LfId̓99'g$dI̙2g2I$&L2ry'I̓I93ɒI|ɒd<$y'&Nd$>g2dL2fI>rs2|ə&s92LL'$?>LI2rO'I$rrI&I&LO9sI&y&C$&rdINI2g$$I$9d'L&I'&f|$93y9g2s$Iy$&dɜ9$̒d39̙9̓3$Ϟy'$̙$$&I$$'$$&yrs2NL䓒I?2g'Iɒs|II2I2II>NrI$fo2rffNs$$NL32fNI&fy2Ld$Nd$O&y<̓&IO'<9NI$s&I2IL2I9'''d3$$ɒI$9d'I2ry2d̓2IɜI'ɒdg$rg$$9$NNI&NI$drdId2|'$dI%?2I$I$ds92NL2fI̙I2I2sN|frI&dOI$d$LfrId䓜2IL̙2rL2fII9g̓9ddd$&fI̙<3du ٓ&3P% =Z)v`~c 8 \nr,E4wpbf;NXف!*l[ M3Q79WL!`0Y88{EQGͲtJ)doYYɴ^hd% ]tXfdiOp+{.T GiW_TU dTDc)pImwS@* Ũ &0q;u`),F:zt@b=gXOzSh+U34GLmCbaU!qE`AR] LvJ!WcIiG{osW[:wcuaDڙ:UHD! TaW|0p±#ikŗ1SE*v#zrCfLaX#2#js ݒ.0iPl2A7Bqmmzom=db꤫stdUK:) |M t %j@BZQu\oⲺ&óZw^eC,k1#GM-_ɫ c M9 bs11ZHC9ʐ]_$wl@\o,aoy؆-&q~&g8:W3cx$)?n18Ԗxɂ'k GIt1h'D6 ).уi/Q aBL|<6)Eds.K 4\wlkk`z;_ɷH"3g$2Bōz !G&x- USD|T uT=7Zov <֔@(R@*CI!bV_ߩ:zwMdqҫxKgߢVo!- ؜_7;8NP{L,)41m]){o]iw)c =B!1EЯt[ ᶻod_c>zɚc%IN)G512\n'BVܢ 4nu#(Ap7ǩ*S!S=8Rqyp"_N@6獉'K=&U|nhgǠaTKQ?4rjWD'6&