Введение
Время перерыва ограничено. В музыкальной библиотеке есть полные записи с оценкой предпочтения, длительностью в секундах и постоянным ID трека. Вы создадите переиспользуемую программу на C++, которая рассматривает треки в объявленном порядке, сохраняет каждую запись, помещающуюся в оставшееся время, и создаёт настоящий воспроизводимый плейлист.
Нужны базовая навигация в терминале и знание строк, классов, vector, файловых потоков и исключений C++. Используемые здесь ссылки const, функцию сравнения и относительные пути мы объясним. Анализатор входных данных, оригинальные синтетические композиции и браузерный проигрыватель подготовлены. Вы напишете функции выбора и экспорта, а не конвертер аудио или алгоритм оптимальной упаковки.
Для команд используйте вкладку Terminal, для прослушивания — Player. Они работают в одной среде. Изменяйте только selection.cpp и export.cpp, создавая плейлисты непосредственно в outputs/. Сохраните без изменений каталог записей, предоставленный каркас и оригиналы в outputs/audio/. Эти оригинальные синтетические композиции не используют сторонние аудиосемплы.
Уместить полные треки во время перерыва
На этом шаге вы сначала прослушаете нормальный пример, затем выберете полные записи с помощью счётчика оставшегося времени. Начните в Player. Выберите example-120.m3u8, нажмите Load playlist, затем Play 1. Список содержит Morning Light (50 секунд) и Window Seat (70 секунд). Используйте элементы управления аудио для паузы или перемещения по записи. Затем выберите example-90.m3u8, загрузите его и воспроизведите единственный трек. Более короткий перерыв сохраняет первую запись целиком, а не обрезает вторую.
Переключитесь в Terminal. Если в приглашении появились посторонние символы или незавершённая команда, нажмите Ctrl+C, чтобы отменить текущий ввод. cd меняет рабочий каталог. Этот абсолютный путь также помогает исправить обычную ошибку выбора каталога:
cd /home/labex/project/short-break
cat показывает текстовый файл. sample.tsv — каталог из двух треков; TSV означает значения, разделённые табуляцией. Столбцы содержат ID, название, целое число секунд, оценку предпочтения и путь к аудио. Более высокие оценки рассматриваются первыми. ID обозначает запись; названия могут повторяться.
cat sample.tsv
playlist.hpp объявляет объект Track и интерфейсы двух функций. Каждый Track объединяет пять полей. vector<Track> хранит несколько объектов в определённом порядке. Прочитайте короткое определение:
cat playlist.hpp
Соберите начальную программу. g++ компилирует C++; -std=c++17 выбирает C++17, -Wall -Wextra включает полезные предупреждения, а -o playlist задаёт имя исполняемого файла. Три файла .cpp компилируются вместе. Предупреждение о неиспользуемом бюджете в начальном коде ожидаемо.
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist запускает исполняемый файл из текущего каталога. Аргументы — файл каталога записей, бюджет в целых секундах от 1 до 86400 включительно и путь вывода. Подготовленный анализатор применяет те же границы к длительности и оценкам предпочтения в каталоге; реализовывать его не нужно. Запустите незавершённую программу для примера с 90 секундами:
./playlist sample.tsv 90 outputs/sample-90.m3u8
Она выводит обе записи и Total: 120s / 90s. Это ошибка, которую вы исправите. Пока выходной файл содержит только названия, поэтому его загрузка в Player сообщает об отсутствии заголовка #EXTM3U. Это ожидаемая ошибка формата, отличная от отсутствия аудиофайла. Формат экспорта будет изучен на шаге 3.
Теперь используйте в chooseTracks оставшийся бюджет. Передача tracks по значению создаёт локальную копию: последующая сортировка не меняет каталог записей. const auto& track читает существующий объект без копирования и изменения. push_back добавляет выбранный объект в конец результата. <= включает трек, который помещается ровно. После добавления -= вычитает его длительность. Не поместившийся трек пропускается, а цикл продолжается.
Замените короткий файл полной функцией ниже. > заменяет этот исходный файл. Here-document <<'CPP' передаёт в cat все строки до завершающего CPP; кавычки вокруг разделителя сохраняют символы C++ буквально. Вставьте блок целиком, включая завершающий разделитель на отдельной строке.
cat > selection.cpp <<'CPP'
#include "playlist.hpp"
#include <algorithm>
std::vector<Track> chooseTracks(std::vector<Track> tracks, int budget) {
std::vector<Track> selected;
for (const auto& track : tracks) {
if (track.seconds <= budget) {
selected.push_back(track);
budget -= track.seconds;
}
}
return selected;
}
CPP
После изменения исходника соберите программу снова и повторите тот же пример:
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist sample.tsv 90 outputs/sample-90.m3u8
Теперь появляются только t01 | Morning Light | 50s и Total: 50s / 90s. Осталось сорок секунд, поэтому запись на 70 секунд не помещается. Эта версия соблюдает порядок входных строк. Далее вы сделаете порядок независимым от расположения строк в каталоге.
Если ошибка компиляции указывает строку, откройте selection.cpp в редакторе или повторите полный блок замены, затем пересоберите программу. Запуск старого исполняемого файла после неудачной сборки не проверяет ваше изменение.
Применить однозначный порядок предпочтений
На этом шаге вы отсортируете всю библиотеку до выбора треков. Начните в Terminal, в /home/labex/project/short-break. Просмотрите полный каталог записей:
cat catalog.tsv
Строки специально перемешаны. Рассматривайте оценки предпочтения от большей к меньшей, а при равенстве — ID треков в возрастающем алфавитном порядке. Поэтому t02 идёт перед t03, хотя длится дольше. Это простые решения о предпочтениях, а не поиск максимально возможной суммарной длительности.
При бюджете 90 секунд t01 использует 50. Ни t02 (95), ни t03 (70) не помещаются в оставшиеся 40. Продолжите с t04 (30), после которого останется 10. t05 (20) не помещается. Результат — t01, t04, всего 80 секунд. Остановка на первом слишком длинном треке потеряла бы подходящий более поздний вариант.
std::sort использует полуинтервал от begin() до end(). Сравнение возвращает true, если a должен предшествовать b. [] вводит небольшую анонимную функцию, лямбду; два параметра const Track& читают сравниваемые объекты. Проверка != определяет основное сравнение, а последний return разрешает равенство по ID. Замените selection.cpp полной версией:
cat > selection.cpp <<'CPP'
#include "playlist.hpp"
#include <algorithm>
std::vector<Track> chooseTracks(std::vector<Track> tracks, int budget) {
std::sort(tracks.begin(), tracks.end(),
[](const Track& a, const Track& b) {
if (a.preference != b.preference)
return a.preference > b.preference;
return a.id < b.id;
});
std::vector<Track> selected;
for (const auto& track : tracks) {
if (track.seconds <= budget) {
selected.push_back(track);
budget -= track.seconds;
}
}
return selected;
}
CPP
Пересоберите программу и выполните оба бюджета:
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist catalog.tsv 90 outputs/break-90.m3u8
./playlist catalog.tsv 120 outputs/break-120.m3u8
Для 90 секунд результат — t01, t04, всего 80. Для 120 секунд — t01, t03, ровно 120: t02 пропускается, затем 70-секундный трек точно занимает оставшиеся 70. Обе выборки используют записи целиком. До следующего шага вывод по-прежнему содержит лишь названия.
Экспортировать воспроизводимый плейлист
На этом шаге вы сохраните выбранные объекты в расширенном M3U с кодировкой UTF-8. Начните в Terminal. Файл .m3u8 — текстовый плейлист, а не само аудио. Первая строка — #EXTM3U. Для каждого выбранного трека затем идут строка метаданных #EXTINF:seconds,title и строка пути к аудио.
Относительный путь отсчитывается от папки плейлиста, а не от расположения каталога записей или текущей папки процесса. Сохраняйте созданные списки прямо в outputs/; оригиналы находятся в outputs/audio/. Поэтому запись вроде audio/Morning Light.wav достигает аудиофайла рядом с плейлистом. При скачивании или передаче сохраняйте эту структуру папок. Пробелы и Unicode поддерживаются; используйте предоставленные пути без перевода строки, начального #, абсолютных путей, перехода к родительской папке или дополнительных директив плейлиста.
std::ofstream открывает выходной файл. << записывает текст или числа; \n записывает перевод строки. Функция получает выбранный vector и имя выхода через ссылки const, не копируя и не изменяя их. Ошибки файлов вызывают исключение, о котором сообщает подготовленная основная программа. Анализатор проверяет положительный целочисленный бюджет, каталог записей и все указанные аудиофайлы до того, как эта функция откроет вывод, поэтому при таких ошибках входа существующий результат сохраняется.
Замените export.cpp:
cat > export.cpp <<'CPP'
#include "playlist.hpp"
#include <fstream>
#include <stdexcept>
void writePlaylist(const std::vector<Track>& tracks, const std::string& output) {
std::ofstream file(output);
if (!file) throw std::runtime_error("Cannot write playlist.");
file << "#EXTM3U\n";
for (const auto& track : tracks) {
file << "#EXTINF:" << track.seconds << ',' << track.title << '\n';
file << track.path << '\n';
}
if (!file) throw std::runtime_error("Playlist write failed.");
}
CPP
Пересоберите программу и заново создайте оба списка обновлённым экспортом:
g++ -std=c++17 -Wall -Wextra main.cpp selection.cpp export.cpp -o playlist
./playlist catalog.tsv 90 outputs/break-90.m3u8
./playlist catalog.tsv 120 outputs/break-120.m3u8
Проверьте сохранённый файл для 90 секунд:
cat outputs/break-90.m3u8
Сохранённый UTF-8-файл содержит:
#EXTM3U
#EXTINF:50,Morning Light
audio/Morning Light.wav
#EXTINF:30,夜の散歩
audio/夜の散歩.wav
Японское название и имя файла остаются текстом UTF-8. В некоторых сеансах терминал показывает символы-заполнители, хотя сохранённые байты не повреждены. Не переименовывайте оригиналы ради исправления отображения: Player показывает исходные названия и пути. Строки длительности описывают полные исходные файлы; изменение числа здесь не сокращает звук.
Переключитесь в Player. Нажмите Refresh files, выберите break-90.m3u8 и нажмите Load playlist. Должны появиться две записи и заявленная сумма 80 секунд. Нажмите Play 1, переместите позицию и поставьте воспроизведение на паузу средствами аудиоплеера, затем нажмите Play 2 для второй записи. Браузер читает ваш сохранённый M3U8 и именно указанные в нём пути. Отсутствующая запись приводит к Cannot play, а не к подстановке другого трека. Download playlist и Download current track скачивают настоящие файлы; один плейлист не содержит самого аудио.

Обратите внимание на две строки, их длительности и аудиоконтролы ниже. Проверив воспроизведение, вернитесь в Terminal.
Повторно использовать программу и понять отказ
На этом шаге вы отличите корректный короткий результат от неверного ввода. Начните в Terminal. Положительный бюджет меньше длительности любого трека допустим: целая запись не помещается. Создайте список для одной секунды:
./playlist catalog.tsv 1 outputs/tiny.m3u8
Программа сообщает Total: 0s / 1s. Файл содержит только #EXTM3U. При загрузке в Player должно быть ноль треков. Это правильный пустой результат, а не ошибка соединения или анализа.
Бюджет вроде 90s неверен, поскольку CLI ожидает цифры, задающие целые секунды. Следующая команда намеренно завершается со статусом 1 и не должна заменить уже имеющийся список для 90 секунд. echo $? немедленно показывает код завершения предыдущей команды (0 означает успех). Выполните эти две команды подряд:
./playlist catalog.tsv 90s outputs/break-90.m3u8
echo $?
Ошибка объясняет, что бюджет и длительности должны быть положительными целыми секундами; затем выводится статус 1. Не сбрасывайте среду: удалите суффикс и повторите с 90. Если ошибиться в пути входного файла, Cannot open catalog указывает на аргумент каталога записей. Исправьте ситуацию абсолютным cd из шага 1 и верным именем catalog.tsv. Missing audio означает, что путь из каталога не достигает файла; сохраните подготовленные оригиналы и проверьте путь перед повтором.
Убедитесь, что правильный существующий файл по-прежнему содержит обе корректные пары метаданных и путей:
cat outputs/break-90.m3u8
Теперь программа пригодна для разных допустимых положительных бюджетов, с записью всегда непосредственно в outputs/. Она сортирует по предпочтению и ID, пропускает слишком длинные варианты и никогда не обрезает запись. Звук на физических колонках и работа всех сторонних проигрывателей не входят в упражнение; предоставленный браузер декодирует эти настоящие WAV-файлы.
Резюме
Вы использовали объекты C++ и vector для упорядочивания записей, выбора полных треков в пределах оставшегося бюджета и записи UTF-8-файлов M3U8. Вы воспроизвели реальный результат, разобрались с относительными путями и отличили допустимый пустой плейлист от неверного ввода. Следующее задание меняет библиотеку, сохраняя эти правила.



