Замена программы для создания изображения спектра

Список разделов foobar2000 Есть вопрос!

Описание: У вас проблемы с foobar2000 и вам необходима помощь? Спрашивайте здесь!
Правила раздела: Одна тема — один вопрос или группа связанных вопросов. Обязательно формируйте внятный заголовок, максимально отражающий суть. Подробно описывайте проблему. Не забывайте указать версию плеера, название сборки, по возможности добавить скриншоты проблемы.

Сообщение #1 iPhoneSasuke » 28.07.2018, 21:09

Здравствуйте, форумчане.

В очередной раз хочу допилить то, что раздражает в любимой сборке fb2k. Недавно получил доступ к китайским сайтам с закачкой через baidu, теперь у меня жесткий диск кишит сотнями гигов файлов с иероглифами. Мой встроенный sox не умеет создавать файл спектра, если присутствуют иероглифы в названии файла и/или тегах.

В моем случае запуск спектра реализован следующим образом:
Спойлер
    var empty_rating = gdi.Image(fb.FoobarPath + "images/similar_wmp_12/STAR1.png");
    var empty_rating2 = gdi.Image(fb.FoobarPath + "images/similar_wmp_12/STAR2.png");
    var checked_rating = gdi.Image(fb.FoobarPath + "images/similar_wmp_12/STAR3.png");
    var g_font = gdi.Font("Segoe UI", 11, 0);
    var focus_playing = false;
    var in_library = false;
    var g_move = null;
    var xpos;
    var ypos;
    var g_timer = window.CreateTimerInterval(500);

    var property_spectrum = window.GetProperty("Spectrum.Enabled", true);
    var property_tmp_auto = window.GetProperty("Spectrum.TmpDir.Auto", true);
    var property_tmp_dir = window.GetProperty("Spectrum.TmpDir.String", "G:\\TEMP\\spectrum\\");

    // Flags, used with GdiDrawText()
    DT_LEFT = 0x00000000;
    DT_RIGHT = 0x00000002;
    DT_END_ELLIPSIS = 0x00008000;

    function RGB(r,g,b){
    return (0xff000000|(r<<16)|(g<<8)|(b));
    }
    function on_paint(gr){
    var title_back_color = RGB(200,225,250);
    var title_fore_color = RGB(0,41,99);
    var back_color = window.GetColorCUI(3);
    var text1_color = window.GetColorCUI(0);
    var text2_color = RGB(0,100,200);

    var ww = window.Width;
    var wh = window.Height;

    gr.FillSolidRect(0, 0, ww, wh, back_color);
    gr.SetTextRenderingHint(5);

    gr.FillGradRect(4, 1, ww-4, 14, 0, title_back_color, back_color);
    gr.FillRoundRect(1, 0, 4, 15, 2, 2, title_back_color);
    gr.GdiDrawText("›› General Information", g_font, title_fore_color, 4, 1, ww, 14, DT_LEFT);
    gr.FillGradRect(122, 8, ww-124, 1, 0, text1_color, back_color);
    gr.FillSolidRect(122, 8, 1, 1, text1_color);

    gr.GdiDrawText("Codec:", g_font, text1_color, 5, 14, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Codec Profile:", g_font, text1_color, 5, 28, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Bitrate:", g_font, text1_color, 5, 42, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Channels:", g_font, text1_color, 5, 56, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Sample Rate:", g_font, text1_color, 5, 70, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Tool:", g_font, text1_color, 5, 84, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Tag Type:", g_font, text1_color, 5, 98, 70, 14, DT_RIGHT);
    gr.GdiDrawText("File Size:", g_font, text1_color, 5, 112, 70, 14, DT_RIGHT);

    gr.FillGradRect(4, 134, ww-4, 14, 0, title_back_color, back_color);
    gr.FillRoundRect(1, 133, 4, 15, 2, 2, title_back_color);
    gr.GdiDrawText("›› Playback Statistics", g_font, title_fore_color, 4, 134, ww, 14, DT_LEFT);
    gr.FillGradRect(110, 141, ww-112, 1, 0, text1_color, back_color);
    gr.FillSolidRect(110, 141, 1, 1, text1_color);

    gr.GdiDrawText("First Played:", g_font, text1_color, 5, 147, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Last Played:", g_font, text1_color, 5, 161, 70, 14, DT_RIGHT);
    gr.GdiDrawText("Played:", g_font, text1_color, 5, 175, 70, 14, DT_RIGHT);

    gr.FillGradRect(4, 197, ww-4, 14, 0, title_back_color, back_color);
    gr.FillRoundRect(1, 196, 4, 15, 2, 2, title_back_color);
    gr.GdiDrawText("›› Rating", g_font, title_fore_color, 4, 197, ww, 14, DT_LEFT);
    gr.FillGradRect(52, 204, ww-54, 1, 0, text1_color, back_color);
    gr.FillSolidRect(52, 204, 1, 1, text1_color);

    var key_playing = fb.TitleFormat("%path%\\%subsong%").Eval();
    focus_playing = false;
    in_library = false;

    var g_focus_metadb = fb.GetFocusItem();
    if(g_focus_metadb!=null){
    var key_focus = fb.TitleFormat("%path%\\%subsong%").EvalWithMetadb(g_focus_metadb);
    if(key_focus==key_playing) focus_playing = true;

    var string1 = fb.TitleFormat("[%codec%][ '('$info(encoding)')']").EvalWithMetadb(g_focus_metadb);
    var string2 = fb.TitleFormat("[%codec_profile%]").EvalWithMetadb(g_focus_metadb);
    if(focus_playing) string3 = fb.TitleFormat("$if($strcmp(%codec_profile%,CBR),,~)%bitrate% kbit").Eval();
    else var string3 = fb.TitleFormat("$if($strcmp(%codec_profile%,CBR),,~)$info(bitrate) kbit").EvalWithMetadb(g_focus_metadb);
    var string4 = fb.TitleFormat("$caps($if3(%__mp3_stereo_mode%,[%channels%]))").EvalWithMetadb(g_focus_metadb);
    var string5 = fb.TitleFormat("[%samplerate%' Hz']").EvalWithMetadb(g_focus_metadb);
    var string6 = fb.TitleFormat("$if(%__tool%,$caps($if($strcmp($left(%__tool%,4),LAME),$insert(%__tool%,' ',4),%__tool%)),n/a)").EvalWithMetadb(g_focus_metadb);
    var string7 = fb.TitleFormat("$if3($replace($info(tagtype),|,+),n/a)").EvalWithMetadb(g_focus_metadb);
    var string8 = fb.TitleFormat("$insert(%filesize_natural%,' ',$sub($len(%filesize_natural%),2))").EvalWithMetadb(g_focus_metadb);
    gr.GdiDrawText(string1, g_font, text2_color, 78, 14, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string2, g_font, text2_color, 78, 28, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string3, g_font, text2_color, 78, 42, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string4, g_font, text2_color, 78, 56, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string5, g_font, text2_color, 78, 70, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string6, g_font, text2_color, 78, 84, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string7, g_font, text2_color, 78, 98, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string8, g_font, text2_color, 78, 112, ww-80, 14, DT_END_ELLIPSIS);
    if(fb.IsMetadbInMediaLibrary(g_focus_metadb)){
    in_library = true;

    var string9 = fb.TitleFormat("$if(%first_played%,$day_of_month(%first_played%)'.'$month(%first_played%)'.'$year(%first_played%)' '$substr(%first_played%,12,16),Never Played)").EvalWithMetadb(g_focus_metadb);
    var string10 = fb.TitleFormat("$if(%last_played%,$day_of_month(%last_played%)'.'$month(%last_played%)'.'$year(%last_played%)' '$substr(%last_played%,12,16),Never Played)").EvalWithMetadb(g_focus_metadb);
    var string11 = fb.TitleFormat("'x '$if3(%play_count%,0)").EvalWithMetadb(g_focus_metadb);
    }
    else{
    var string9 = "[NOT IN MEDIA LIBRARY]";
    var string10 = "";
    var string11 = "";
    }
    gr.GdiDrawText(string9, g_font, text2_color, 78, 147, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string10, g_font, text2_color, 78, 161, ww-80, 14, DT_END_ELLIPSIS);
    gr.GdiDrawText(string11, g_font, text2_color, 78, 175, ww-80, 14, DT_END_ELLIPSIS);

    xpos = Math.floor((ww-65)/2);
    ypos = 212;
    if(g_move) var now_check = g_move;
    else var now_check = fb.TitleFormat("$if2(%rating%,0)").EvalWithMetadb(g_focus_metadb);
    for (i = 0; i < now_check; i++){
    gr.DrawImage(checked_rating, xpos+i*13, ypos, 13, 13, 0, 0, 13, 13);
    }
    for (j = now_check; j < 5; j++){
    if(g_move) gr.DrawImage(empty_rating2, xpos+j*13, ypos, 13, 13, 0, 0, 13, 13);
    else gr.DrawImage(empty_rating, xpos+j*13, ypos, 13, 13, 0, 0, 13, 13);
    }
    }
    }
    function on_item_focus_change(){
    window.Repaint();
    }
    function on_mouse_move(x,y){
    if(focus_playing && in_library){
    if(xpos<=x && x<xpos+65 && ypos<=y && y<ypos+13){
    g_move = Math.floor((x-xpos)/13)+1;
    window.Repaint();
    }
    else{
    if(g_move){
    g_move = null;
    window.Repaint();
    }
    }
    }
    }
    function on_mouse_leave(){
    g_move = null;
    window.Repaint();
    }
    function on_mouse_lbtn_down(x,y){
    if(focus_playing && in_library){
    if(xpos<=x && x<xpos+65 && ypos<=y && y<ypos+13){
    g_move = Math.floor((x-xpos)/13)+1;
    fb.RunContextCommand("Playback Statistics/Rating/" + g_move);
    }
    }
    }
    function on_mouse_rbtn_down(x,y){
    var _menu = window.CreatePopupMenu();
    if(focus_playing && in_library){
    var current_rating = fb.TitleFormat("$if2(%rating%,0)").Eval();
    if(current_rating>0){
    _menu.AppendMenuItem(0x00000000, 1, "Delete rating");
    _menu.AppendMenuItem(0x00000800, 0, 0);
    }
    }
    if(property_spectrum && fb.GetFocusItem()!=null){
    _menu.AppendMenuItem(0x00000000, 2, "Spectrum");
    _menu.AppendMenuItem(0x00000800, 0, 0);
    }
    _menu.AppendMenuItem(0x00000000, 3, "Properties");
    _menu.AppendMenuItem(0x00000000, 4, "Configure...");
    ret = _menu.TrackPopupMenu(x, y);
    switch (ret){
    case 1:
    fb.RunContextCommand("Playback Statistics/Rating/<not set>");
    break;
    case 2:
    spectrum();
    break
    case 3:
    window.ShowProperties();
    break;
    case 4:
    window.ShowConfigure();
    }
    _menu.Dispose();
    }
    function on_timer(id){
    window.Repaint();
    }
    function spectrum(){
    var g_focus_metadb = fb.GetFocusItem();
    if(g_focus_metadb!=null){
    var subsong=fb.TitleFormat("%subsong%").EvalWithMetadb(g_focus_metadb);
    if(subsong==0){
    var workdir = fb.FoobarPath+"\spectrum\\";
    if(property_tmp_auto) var tmpdir = workdir;
    else var tmpdir = property_tmp_dir;
    var batname = tmpdir+"spectrum.bat";
    var filename = fb.TitleFormat("%path%").EvalWithMetadb(g_focus_metadb);
    var lenght = " ("+fb.TitleFormat("%length%=%length_seconds%").EvalWithMetadb(g_focus_metadb)+")";

    var fso = new ActiveXObject("Scripting.FileSystemObject");
    if (fso.fileexists(batname)==true) var ts = fso.OpenTextFile(batname, 2);
    else var ts = fso.CreateTextFile(batname, 1);
    ts.WriteLine('@echo off');
    ts.WriteLine('"'+workdir+'ffmpeg.exe" -y -i '+shortname(filename)+' "'+tmpdir+'spectrum.wav"');
    ts.WriteLine('"'+workdir+'sox\\sox.exe" "'+tmpdir+'spectrum.wav" -n spectrogram -x 739 -Y 568 -w Hann -S 0 -t "'+filename+lenght+'" -c "Generated with SOX in foobar2000 by Alex Realist" -o "'+tmpdir+'spectrum_tmp.png"');
    ts.WriteLine('copy "'+tmpdir+'spectrum_tmp.png" "'+tmpdir+'spectrum.png"');
    ts.WriteLine('"'+tmpdir+'spectrum.png"');
    ts.WriteLine('exit');
    ts.Close();

    var WshShell = new ActiveXObject("WScript.Shell");
    WshShell.run('cmd /c start "spectrum.bat" /min cmd /c "'+batname+'"');
    }
    else fb.ShowPopupMessage("Spectrum for multiple tracks within single physical file is unreleased in this version.","Information");
    }
    }
    function shortname(filespec){
    var fso2 = new ActiveXObject("Scripting.FileSystemObject");
    return(fso2.GetFile(filespec).ShortPath);
    }
    //EOF

Коль sox не может работать с файлами, содержащими иероглифами, я согласен переключиться на Spek. Но вот вопрос — как реализовать его запуск привычным мне способом — не через добавление службы, а вызовом программы по клику (у меня это в поле над обложкой альбома ПКМ->Spectrum)?
Вложения
Скриншот 28-07-2018 200505.png
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #2 Azaza » 28.07.2018, 22:38

ffmpeg умеет генерировать спектрограммы и, в отличие от SoX'а, поддерживает юникод. Т.е. можно, наверное, просто в скрипте этом заменить SoX на ffmpeg и заменить соответствующие команды.
Команда для ffmpeg такая:
ffmpeg.exe входнойфайл -lavfi showspectrumpic=s=4096x2048:mode=separate выходнойфайл.png
Ширина и высота должны быть степенью двух.
Результат выглядит примерно так:
Изображение
https://ffmpeg.org/ffmpeg-filters.html#showspectrumpic
Последний раз редактировалось Azaza 29.07.2018, 00:30, всего редактировалось 3 раз(а).
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев

Сообщение #3 iPhoneSasuke » 28.07.2018, 22:50

А можно под спойлер то, что получилось? Я в программировании деревянный совсем. И например меня ставит в тупик, какой прописывать «входнойфайл», если это может быть рандомный трек из фонотеки :eh:

Вот на всякий случай сборка целиком:
https://yadi.sk/d/WZ-msMLH3Zfkrj

Спектр делается через ПКМ в правом поле (кроме обложки альбома).
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #4 Azaza » 28.07.2018, 22:55

iPhoneSasuke:Я в программировании деревянный совсем.
Я тоже.
За пути к файлам и команды отвечает, видимо, эта часть скрипта
Код: Выделить всё
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (fso.fileexists(batname)==true) var ts = fso.OpenTextFile(batname, 2);
else var ts = fso.CreateTextFile(batname, 1);
ts.WriteLine('@echo off');
ts.WriteLine('"'+workdir+'ffmpeg.exe" -y -i '+shortname(filename)+' "'+tmpdir+'spectrum.wav"');
ts.WriteLine('"'+workdir+'sox\\sox.exe" "'+tmpdir+'spectrum.wav" -n spectrogram -x 739 -Y 568 -w Hann -S 0 -t "'+filename+lenght+'" -c "Generated with SOX in foobar2000 by Alex Realist" -o "'+tmpdir+'spectrum_tmp.png"');
ts.WriteLine('copy "'+tmpdir+'spectrum_tmp.png" "'+tmpdir+'spectrum.png"');
ts.WriteLine('"'+tmpdir+'spectrum.png"');
ts.WriteLine('exit');
ts.Close();
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев

Сообщение #5 iPhoneSasuke » 28.07.2018, 23:01

Это я догадался, но без ошибки в скрипте не получается засунуть вашу подсказку.
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #6 Azaza » 28.07.2018, 23:18

Получилося.
Код: Выделить всё
         var fso = new ActiveXObject("Scripting.FileSystemObject");
         if (fso.fileexists(batname)==true) var ts = fso.OpenTextFile(batname, 2);
            else var ts = fso.CreateTextFile(batname, 1);
         ts.WriteLine('@echo off');
         ts.WriteLine('"'+workdir+'ffmpeg.exe" -y -i '+shortname(filename)+' -lavfi showspectrumpic=s=4098x2050:mode=separate "'+tmpdir+'spectrum_tmp.png"');
         ts.WriteLine('copy "'+tmpdir+'spectrum_tmp.png" "'+tmpdir+'spectrum.png"');
         ts.WriteLine('"'+tmpdir+'spectrum.png"');
         ts.WriteLine('exit');
         ts.Close();
И ffmpeg.exe в папке spectrum нужно заменить на современную версию - https://ffmpeg.zeranoe.com/builds/
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев

Сообщение #7 iPhoneSasuke » 28.07.2018, 23:30

Отлично!!! Теперь работает! УРА! Спасибо!

Теперь вопросы:
а) обязательно ли придерживаться правила «должно быть степенью 2». Т.к. для общего развития огромное разрешение не нужно, а 1024х1024 неудобно смотреть на 24.1" экране с 1920х1200 (не влезает без масштабирования).
б) можно ли добавить надписи, как было с sox? Ну там, путь к файлу, с которого бралось...

в) ffmpeg не заставить читать спектр у одного из трека в образе? К примеру, трека #4 в образе .ape.
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #8 Azaza » 28.07.2018, 23:33

iPhoneSasuke:обязательно ли придерживаться правила «должно быть степенью 2»
Для высоты точно. Иначе спектр будет обрубаться. Я, кстати, в примере неправильно написал ) 4098x2050, а надо 4096x2048

iPhoneSasuke:можно ли добавить надписи, как было с sox? Ну там, путь к файлу, с которого бралось
Нет.

iPhoneSasuke:ffmpeg не заставить читать спектр у одного из трека в образе?
Просто так - нет. Но, если найти способ извратиться со скриптом и заставить его предать ffmpeg'у время начала и окончания трека в образе, то можно.
Последний раз редактировалось Azaza 28.07.2018, 23:43, всего редактировалось 2 раз(а).
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев

Сообщение #9 iPhoneSasuke » 28.07.2018, 23:41

Azaza:Просто так - нет. Но, если найти способ извратиться со скриптом и заставить его предать ffmpeg'у время начала и окончания трека в образе, то можно.
А этот метод здесь неприменим?
https://cdpos.biz/showthread.php?465-Spek-(Анализатор-спектра)&p=5914&viewfull=1#post5914
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #10 Azaza » 28.07.2018, 23:46

iPhoneSasuke:А этот метод здесь неприменим?
https://cdpos.biz/showthread.php?465-Spek-(Анализатор-спектра)&p=5914&viewfull=1#post5914
Вы про это?
"$if(%__referenced_file%,$directory_path(%path%)\%__referenced_file%,%path%)"
Так мы увидим спектрограмму ВСЕГО образа, а не отдельного трека.

Добавлено спустя 5 минут 3 секунды:
Для cue в свойствах фубар показывает такой параметр как REFERENCED_OFFSET. Если заставить скрипт передать имя самого образа, а не cue, а также значение REFERENCED_OFFSET и продолжительность трека в ffmpeg, то с командами -ss и -t можно добиться того, чего вы хотите. Но как передать значения - это уже вопрос к спецам по скриптам.

Проверил: ширина не обязательно должна быть степенью двух. Только высота.
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев

Сообщение #11 Azaza » 29.07.2018, 15:13

На самом деле, чтобы работал SoX, достаточно убрать -t "'+filename+lenght+'"
Код: Выделить всё
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (fso.fileexists(batname)==true) var ts = fso.OpenTextFile(batname, 2);
else var ts = fso.CreateTextFile(batname, 1);
ts.WriteLine('@echo off');
ts.WriteLine('"'+workdir+'ffmpeg.exe" -y -i '+shortname(filename)+' "'+tmpdir+'spectrum.wav"');
ts.WriteLine('"'+workdir+'sox\\sox.exe" "'+tmpdir+'spectrum.wav" -n spectrogram -x 739 -Y 568 -w Hann -S 0 -c "Generated with SOX in foobar2000 by Alex Realist" -o "'+tmpdir+'spectrum_tmp.png"');
ts.WriteLine('copy "'+tmpdir+'spectrum_tmp.png" "'+tmpdir+'spectrum.png"');
ts.WriteLine('"'+tmpdir+'spectrum.png"');
ts.WriteLine('exit');
ts.Close();
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев

Сообщение #12 iPhoneSasuke » 29.07.2018, 23:02

В итоге разница сводится к тому, что SoX рисует в свободном формате (размер изображения), красивой разгерцовкой, без сохранения временной wav'ки и слоупочно, а ffmpeg делает быстрее, информативнее по громкости, но с неудобными размерами и не шибко казистой разметкой частот. Эх... Вот бы всё сразу, идеально, и с возможностью приписать имена исходных файлов (удобно, чтобы не запутаться). Сохраню себе в блокнот 2 альтернативных варианта...
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #13 iPhoneSasuke » 11.08.2018, 11:26

Вот о чем еще подумал: можно ли SoX научить удалять wav файл после создания рисунка спектра?
iPhoneSasuke M
Автор темы
Аватара
Репутация: 3
С нами: 9 лет 2 месяца

Сообщение #14 duremar2000 » 17.08.2018, 23:13

Скрипт, это как я понимаю vbs файл? Запускать через foo_runcmd?
duremar2000
Репутация: 0
С нами: 11 лет 10 месяцев

Сообщение #15 Azaza » 19.08.2018, 17:26

duremar2000:Скрипт, это как я понимаю vbs файл? Запускать через foo_runcmd?
В данном случае - нет. Это скрипт для JScript панели.
Azaza M
Аватара
Репутация: 393
С нами: 7 лет 6 месяцев


Вернуться в Есть вопрос!