foobar2000 0.8.3 custom by Draikin

Discussion in 'Nguồn phát từ máy tính' started by tml3nr, 3/8/17.

  1. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    chào 4rum,
    chào năm mới.

    bắt trend theo vintage audio, mình sẽ kiếm cái skin hoàn chỉnh về băng cối hay dĩa nhựa rồi gắn vô bản dựng fb2k-vnav cho nhanh.

    link các bác đưa năm trước (mình đã đề cập ở các post trên) hình như có 1 bản skin của Dmitri/ Iprad (người Nga) nhưng thấy không hợp skin Tech nên đã bỏ qua. nay có nhu cầu nhiều hơn nên mình sẽ tìm hiểu và báo lại anh em.
    về nguyên tắc, skin đã từng dựng thành công với các foobar version cũ thì không lý gì không 'chạy' được với bản mới, trừ khi plugin không tương thích. skin của iprad chủ yếu dùng script nên xem như không có vấn đề lớn.

    mình cũng sẽ trả lời 1 số ý liên quan bài trước.

    về phần nghe: do gu nghe mỗi người khác và cũng có nhiều VST giả lập analog audio nên mình sẽ không đề cập. hơn nữa, mình không có kiến thức đủ về VST, không hiểu bản chất nên không thể nói cho người khác hiểu.

    1 số link tham khảo VST analog theo các trao đổi và dẫn ra ở 4rum về foobar2000: https://hydrogenaud.io/index.php/topic,124701.0.html
    (vui lòng kiểm tra và check virus kỹ nếu dùng. bản thân mình chưa sử dụng ) :
    https://www.voxengo.com/product/tubeamp/

    https://bedroomproducersblog.com/2021/01/20/free-saturation-vst-plugins/

    https://blog.landr.com/8-free-vst-plugins-warm-sound/
     
    Last edited: 20/2/24
    tml3nr likes this.
  2. VNA

    VNA Advanced Member

    Joined:
    2/3/16
    Messages:
    174
    Likes Received:
    189
    Có bác nào cài được foobar2000 đọc đc file DSD 1024 không ?
     
  3. Scorpio

    Scorpio Moderator

    Joined:
    2/12/05
    Messages:
    7.233
    Likes Received:
    3.297
    Location:
    VNAV
    Lâu lắm rồi mới quay lại foobar ... bác @viking mod ác thật

    upload_2024-3-1_14-25-20.png

    P/S: Mình cài foobar sau đó chép đè file vào thư mục cái đặt hoặc chạy theo cách portable ( giải nén và thư mục rồi chạy file foobar2000.exe) đều bị mất dàn nút điển khiển ở control bar

    DSD 1024 file hiếm lắm bác ơi, file size, hao dung lượng ổ cứng
     
  4. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    cảm ơn anh có lời động viên.
    hổm rày lu bu đột xuất và cũng đợi Peter ra version 2.2 tối ưu thêm phân luồng CPU, 28/2 vừa có bản beta review 2.2 khá ổn nên mình sẽ sớm quay lại với chủ đề đã hứa : đồ họa với foobar2000.

    bản mod VNAV này dựa trên skin TECH của br3tt ạ (https://www.deviantart.com/br3tt/art/TECH-v1-5-2-162724968 ).

    bản mod fb2k-vnav 2.1 dựng trên Windows 11, màn hình FullHD 1080, CPU AMD Ryzen 7 đời đầu.

    Phần control symbol dùng font symbol nên anh cài giúp bộ font kèm theo thư mục skin nhé.
     
    Last edited: 2/3/24
    Scorpio likes this.
  5. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Thân gửi anh em 4rum vnav,
    Bản foobar2000 vnav V2.2 mới nhất đã có tại đây: (727 MB)
    link fshare: không duy trì do file dung lượng lớn và các bác có vẻ ưu tiên mediafire.
    link MediaFire :
    https://www.mediafire.com/file/nvv4...0_2.2_08Mar24_32bit__tech_vnav_2.2.0.rar/file
    link google: xin cảm ơn nhã ý của bác gần xa, do a/c cá nhân nên mỗi khi upload lại phiền các bác.

    Bữa nay xong rồi Diem nên mình lại lên bài, đem niềm vui đến cho mọi người cũng là niềm vui của chính mình.
    Sau 1 thời gian giữ mình thì đến 1 ngày skin TECH cũng toang, chắc Br3tt sẽ không vui vì xem như skin banh chành luôn rồi dù hồn cốt thì vẫn vương vãi đâu đó.
    Sao vậy ? vì ham vui sắm thêm đồ mà nhà cửa thì chật chội nên đành cơi nới, cụ thể là đám cassette player, turntable và băng cối reel-to-reel của Dmitri/iprad.
    (source: https://foobar2000.ru/forum/viewtopic.php?t=5081&start=580. riêng skin turntables thì được 1 bác giới thiệu qua Jul2323 lưu giữ https://hydrogenaud.io/index.php/topic,124489.0.html . cảm ơn các bác)

    và nữa, bản này vẫn được xây dựng trên ý kiến của nhiều người gần xa trong ngoài 4rum, tích hợp các ví dụ ở loạt bài trước, và nhất là phần vẽ vời được quan tâm từ đầu, nên sẽ có vài điểm cần nói rõ thêm ở các bài tiếp.

    Có điểm gì mới không ? Tất nhiên vẫn hoa lá cành như tiêu chí dựng và:
    1. về hình thức/tính năng:
    - như đã nêu, thêm mấy cái skin (skin trong skin) của Dmitri/iprad liên quan đến cassette player, turntables, reel-to-reel.
    - thêm shortcut trên control bar truy cập nhanh reel-to-reel - bản desktop.
    - hiện ngày âm-dương lịch trên skin.
    - nhắc trước, trong và sau sự kiện liên quan âm/ dương lịch.
    - tìm và hiện các image lưu ở các thư mục khác nhau trên HDD hay download từ Internet (last.fm).
    - hiệu ứng đồ họa slide show với hình album/cover (gọi là cover art).

    2. kỹ thuật:
    - cập nhật core lên foobar2000 2.2 beta ngày 8/3/2024.
    - cập nhật JSPanel 3 lên 3.4.16 ngày 10/3/2024.
    - cập nhật cuefixer (khắc phục lỗi với vài ký tự đặc biệt).
    - thêm MilkDrop visualization plugin. bản này được tối ưu cho Windows 11. DirectX 11.
    - thêm set milkdrop CreamOftheCrop (do 1 bác gửi link). rút kinh nghiệm, lần này mình để nguyên vì gu thẩm mỹ mỗi người khác nhau. (sorry các bác gửi các set lần trước vì bị mình lọc lựa từa lưa).
    - mod scripts của Dmitri/iprad cho hợp với skin FB2k-VNAV 2.2
    - tinh chỉnh vài bugs.
    - vân vân và mây mây. . .

    Bản này được test trên màn hình HD, với các màn hình độ phân giải cao hơn, các bác tham khảo thông tin sau giúp (1 bác đưa, mình không có điều kiện test):
    https://www.quadraphonicquad.com/forums/threads/foobar2000-on-4k-display.29342/.

    Đã test trên máy cấu hình tương đương:
    Windows 11,
    CPU: AMD 7, RAM 12 GB
    Direct X: 12

    Để hiển thị đúng các biểu tượng trong control bar (phía dưới), thông tin thời gian audio track (phía trên), các bác cân nhắc cài các font trong thư mục 'foobar. . . /skin/tech fonts'

    chúc các bác sức khỏe và luôn yêu đời.

    lưu ý: Foobar2000 và các plugin, chương trình đình kèm thuộc bản quyền của các nhà phát triển tương ứng. mình đã cẩn thận quét virus và xem xét nguồn nhưng không chịu trách nhiệm với các rủi ro liên quan (nếu có).
     

    Attached Files:

    Last edited: 13/3/24
  6. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    trước khi đi tiếp, mình xin làm rõ 2 ý nhỏ trước, liên quan bài trước:

    1. Quản lý tab:
    nhắc lại: như chúng ta đã đề cập, PSS (Panel Stack Splitter) do Ssenna (hình như người Nhật - https://foo2k.chottu.net/) phát triển đã lâu và chưa có kế thừa, quản lý việc sắp đặt các panel, và do đó, các tab. PSS và Columns UI/CUI (@ musicmusic) đã tạo ra cuộc cách mạng về giao diện cho foobar2000, đến nỗi sau này, plugin nào cũng - hầu như - xem việc cài đặt được trên CUI (bên cạnh Default UI/DUI) là tiêu chuẩn. tài liệu về PSS : https://wiki.hydrogenaud.io/index.p...Panel_Stack_Splitter_(foo_uie_panel_splitter).
    (DUI cũng có tab nhưng ít tùy biến hơn.)

    nhưng sao lại nói Br3tt cũng quản lý được tab trong skin TECH nhưng chỉ dùng WSH script? thực ra, Br3tt cũng dựa vào PSS để quản lý tab, cụ thể là ẩn hiện tab.
    cái hay ở đây là Br3tt vừa tận dụng được thế mạnh của WSH (Microsoft® Windows® Script Host) trong việc vẽ hình và PSS ẩn/hiện tab. vậy 2 plugin này trao đổi thông tin ra sao ?

    hiện giờ, foobar2000 vẫn chưa có cơ chế trao đổi thông tin chính thức giữa các plugin, nói cách khác, foobar2000 quản lý và ưu tiên tiến trình rất nghiêm ngặt (kiểu như ưu tiên băng thông cho từng dịch vụ trên LAN/WAN, hay Windows 1 thời (Vista) từng cho can thiệp), điều này đảm bảo core play audio luôn là số 1 - đúng nhận sai cãi, và để tránh đổ vỡ dây chuyền do lỗi của 1 plugin nào đó tạo ra (tưởng tượng, cho 1 vòng lặp vô tận trong plugin WSH nào đó thì toang).

    Haiz, ba má hổng cho gặp mặt thì chui rào mật thư vậy - nhà em có ấy nhưng anh cứ vào (xin lỗi anh Đen V) : qua file - tên file. vâng, các bác không đọc lầm đâu.

    khi có cơ chế thì mọi sự đơn giản lắm:
    1. bên A (WSH panel mod - script quản lý vẽ vời tab): khi chọn tab nào thì WSH script tạo 1 file với tên (không có đuôi file ext) - thực ra là số thứ tự tab, cho dễ nhớ, để trong thư mục (hộp thư mật) qui ước.
    với hệ thống tab center (ở giữa màn hình) thì thư mục là mainpanel_id (trong thư mục skins\tech\settings), tab nhỏ dưới màn hình là thư mục botpanel_id.
    câu lệnh trong file common.js như sau:
    Code:
     var conf = "tech";
     var ppth = fb.ProfilePath;
     var sdir = ppth + "skins\\" + conf + "\\settings\\";
     var ForReading = 1;
     var ForWriting = 2;
     var fso = new ActiveXObject("Scripting.FileSystemObject");
     settings_exists(conf);
    
     function read_option(opt, val) {
         var odir = sdir + opt + "\\";
         if (!fso.FolderExists(odir)) {
             fso.CreateFolder(odir)
         } else {
        //vnav Mar24
        // take the 1st file, cause this folder has 1 file only. that is the number sequence of Tab
        // PSS will read this value to switch to that tab.
    
        var fileSetting = utils.Glob(odir+"*.*").toArray();
        if  (fileSetting.length > 0) return  fileSetting[0].split('\\').pop(); // get filename
     
        /* vnav. remove
         var e = new Enumerator(fso.GetFolder(odir).Files);
             var s;
             for (; !e.atEnd();
             e.moveNext()) {
                 s = e.item();
                 if (utils.PathWildcardMatch("*", s.Name)) {
                     return s.Name
                 }
    
             }
        */
         }
        try {
             var fileHandle= fso.CreateTextFile(odir + val, ForWriting);
        fileHandle.Close();
             return val
        } catch (e) {fb.trace("WSH Common error, File read_option :"+e); return val;}
     }
    
     function update_option(opt, val) {
         var odir = sdir + opt + "\\";
         if (!fso.FolderExists(odir)) {
             fso.CreateFolder(odir);
             fso.CreateTextFile(odir + val, ForWriting)
         } else {
        // vnav Mar24
            var fileSetting = utils.Glob(odir+"*.*").toArray();
        if  (fileSetting.length > 0)
        try {
            var file1 = fileSetting[0];
            file1=file1.substr((file1.lastIndexOf('\\')+1)); // old school type to get filename. fileSetting[0].split('\\').pop() works also.
            fso.MoveFile(odir + file1, odir + val); //https://learn.microsoft.com/vi-vn/office/vba/language/reference/user-interface-help/movefile-method
     
        } catch (e) { fb.trace("WSH Common error, File update_option :"+e); return val;}
    
        /*
        var e = new Enumerator(fso.GetFolder(odir).Files);
             var s;
             for (; !e.atEnd();
             e.moveNext()) {
                 s = e.item();
                 if (utils.PathWildcardMatch("*", s.Name)) {
                     fso.MoveFile(odir + s.Name, odir + val);
                     break
                 }
    
             }
        */
    
         }
         return val;
     }
    
    lệnh ActiveXObject("Scripting.FileSystemObject").MoveFile(source_file_full_path, dest_file_full_path ) dùng 1 FileSystemObject activeX (https://learn.microsoft.com/en-us/o...e/user-interface-help/filesystemobject-object), là 1 thư viện các 'hàm/functions' của OS Windows (giao tiếp với hắn qua cơ chế COM/OLE) để chép file.
    file mới được tạo ra chính là số thứ tự tab (như nêu ở trên). sau khi tạo file này thì bên A ngồi đợi bên B lấy mật thư.

    lưu ý thêm: nguyên bản script, Br3tt dùng đối tượng Enumerator, 1 dạng object (như Array) nhưng rất mạnh và nhanh vì quản lý trực tiếp con trỏ (pointer) bộ nhớ (ngôn ngữ cấp thấp như C thì thuận món này). tuy nhiên, các plugin script, tuy cùng họ script như JavaScript nhưng không phải lúc nào cũng hỗ trợ. Ngoài ra, vì bạn ấy quá nhanh nên đôi khi quá nguy hiểm - cầm đèn chạy trước xe hơi, đặc biệt khi foobar2000 'chạy' trên removable HDD/SSD mà bản thân OS đôi khi không kịp active HDD thì bị báo lỗi phải liền. (khi gắn HDD rời vô máy tính và được mounted, ta luôn 'thấy' HDD này, nhưng không phải lúc nào OS cũng duy trì kết nối - có thể trong 1 sát na xoay tua mà kết nối không được duy trì).

    vì vậy, nhân dịp có thắc mắc về tab của skin TECH nên mình chuyển qua Array truyền thống luôn - lành nhưng cũng mạnh.
    Code:
     var fileSetting = utils.Glob(odir+"*.*").toArray();
        if  (fileSetting.length > 0) return  fileSetting[0].split('\\').pop(); // get filename 
    2. bên B (PSS ẩn/hiện tab):
    phần này thì các bác quá rõ rồi. tìm trong script của PSS gốc (nền của skin TECH) là ra.

    - bước 1: lấy tên file, lưu vào biến mainpanel_id (biến tên gì cũng được, miễn là tuân thủ quy tắc đặt tên, không nhất thiết trùng tên thư mục chứa file)
    Code:
    // TECH config for foobar2000 1.0+
    // by Br3tt - may 2010
    //______________________________________________
    $puts(cf_name,'tech')
    $puts(z3,%ps_user_profile_path%'/skins/'$get(cf_name))
    . . .
    $set_ps_global(mainpanel_id,$right($findfile($get(z3)/settings/mainpanel_id/*),2))
    
    
    - bước 2: úm ba la, ẩn/hiện tab :
    Code:
    // selected panel display
    $ifequal(%mainpanel_id%,01,,$if($isvisible_c(P01),$showpanel_c(P01,0),))
    $ifequal(%mainpanel_id%,02,,$if($isvisible_c(P02),$showpanel_c(P02,0),))
    . . .
    
    $ifequal(%mainpanel_id%,99,,$if($isvisible_c(P99),$showpanel_c(P99,0),))
    $movepanel_c(P%mainpanel_id%,$add($get(elp.x),8),$add($get(elp.y),8),$sub($get(elp.w),14),$sub($get(elp.h),14))
    $if($isvisible_c(P%mainpanel_id%),,$showpanel_c(P%mainpanel_id%,1))
    
    các bác chép PSS script ra notepad++ để dễ edit.
    tương tự cho tab ở dưới màn hình.
    vậy thôi, đơn giản mà hiệu quả.

    điểm cuối - vậy còn cơ chế trao đổi thông tin chính thức giữa các plugin là gì ? Ờ, thì tùy dạng thông tin và tùy plugin.
    foobar2000 là trình play audio, nên các biến hệ thống và 'hàm' được xây dựng hoàn toàn xung quanh các thông tin liên quan 'bài hát', tức là album, artist, composer, track length time, path track, cover art (album/artist art - image), bitdepth, sample, channel . . . thông tin chủ yếu là số và chữ (string). foobar2000 gọi chúng bằng 1 thuật ngữ TitleFormatting (1?) - thà rằng không biết còn hơn, vì biết cũng không hiểu nghĩa thực sự. thông tin trên tàng kinh các wiki https://wiki.hydrogenaud.io/index.php?title=Foobar2000:Title_Formatting_Reference. khi có thay đổi thông tin dạng này thì Fb2k tự động truyền đi cho bá tánh biết, ví dụ: thay đổi trạng thái play track, thay đổi metadata (metadb), thay đổi playlist- album - track . . . và đôi khi các sự kiện này chồng chéo nên ta phải rất cẩn thận khi quản lý chúng, kẻo bị tối tăm mặt mũi (sẽ đề cập chi tiết ở post tiếp).
    thông tin chính chủ dạng này được trao đổi theo đường chính thức, thường xuyên, liên tục.

    ngoài ra, 1 số script plugin (như WSH, JSPanel Script, SMP) có 2 method dùng trao đổi thông tin - bất kể chuyện gì - cho nhau , và cho các plugin khác (nếu chúng cũng muốn nghe).
    - Bên A: bên gửi mật thư, qua đối tượng window (đối tượng này thường do OS bảo kê, chuyên để mắt đến phím bấm, chuột và co giãn panel, timer)
    Code:
    window.NotifyOthers(name,infor);
    name : string
    info : string, number, array, object
    
    
    - Bên B: bên nhận, là 1 callback của plugin, thân phận nhỏ bé chuyên ngồi hóng xem ai gọi mình rồi action. khi có anh NotifyOthers gọi thì lật đật xem mật khẩu name có đúng với quy ước, nếu đúng thì tới công chuyện rồi - xem chỉ thị info có gì hay muốn làm gì với hắn.
    Code:
    on_notify_data(name, info)
    name : string
    info : string, number, array, object
    
    vậy thôi,

    2. quản lý sự kiện âm - dương lịch:
    (vui lòng xem bài tiếp, do bị giới hạn dung lượng/post).
     
    Last edited: 14/3/24
    quangng likes this.
  7. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    2. quản lý sự kiện âm - dương lịch: script minh họa Lunar_calendar_HoNgocDuc_WSH.txt theo bản dựng Fb2k-vnav 2.2.
    lưu ý: khi có sự kiện, fb2k-vnav 2.2 sẽ nhấp nháy ký hiệu ♪ trong khu vực hiện giờ ngày tháng âm dương lịch. right click sẽ có thêm thông tin.

    post trước Tết có để ngỏ 1 loại sự kiện: lặp lại mỗi cuối tháng.
    trà dư tửu hậu cũng có vài điều hay, xin mạn phép trao đổi cho vui:
    - 1 tháng 30 ngày, 1 năm 360 ngày: quản lý sự kiện thì dựa vào thời gian thực tế của máy tính. quy ước 30 hay 360 ngày rất đặc thù. nếu - nếu thôi nhé- liên quan đến lĩnh vực gì đó có phát sinh quyền/nghĩa vụ theo thời gian, có thể cân nhắc dựa trên cơ sở đơn vị nhỏ nhất rồi tính lên và tính luôn từ ban đầu (ví dụ: 1 ngày là M quyền lợi, thì 1 tháng 30 hay 31 ngày theo đó nhân M). nếu ngày chốt hưởng/thi hành quyền/nghĩa vụ rơi vào những thời điểm nghỉ theo luật định như lễ/Tết thì cần quy định trước (vì có khi thiếu/dư tháng 30 hay năm 360 ngày).
    - so sánh ngày tháng: JavaScript có đối tượng rất mạnh là Date() theo hệ dương lịch, tính thời gian theo địa phương và chi tiết đến hàng 1/1000 giây. so sánh, cộng trừ thời gian cũng thầu tuốt. vì vậy, dương lịch thì cứ Date() mà tẩn, âm lịch thì đổi ra Date() rồi cũng tẩn. khỏe re . . .
    rồi, công cụ đã có.

    so sánh thì sao ?
    + so sánh hơn, bằng, thua: Date() làm được hết.
    + cộng trừ ngày tháng năm : Date() cũng làm được và làm rất tốt. cứ quy ra đơn vị nhỏ nhất là milliseconds rồi tính.

    'hàm' sau cho phép tới/lui n ngày kể từ ngày dd/mm/yyyy.
    Code:
    function navi_sDate(n,dd,mm,yyyy){
        return new Date(new Date(yyyy,mm-1,dd).getTime() + ((n * 24) * 60 * 60 * 1000));
    // vnav Jan24
    }
    
    nhưng làm quá tốt cũng phải xem lại: khi muốn chuyển 30/02/2024 cho Date() (để tính toán) - nguyên nhân tại sao lại chuyển 1 'ngày' 30/02/2024 vô nghĩa như vậy ? vì đôi khi input 'ẩu', hay đang ở ngày 31 cuối tháng này và chuyển qua ngày cuối tháng sau có 28/29/30 ngày - Date() rất nhanh, bấm đốt ngón tay biết tháng 02 có 28 ngày, và cũng biết năm 2024 nhuận nên bonus thêm 1 ngày = 29 ngày. vậy còn dư 30-29 = 1 ngày để đâu ? cũng rất lẹ, hắn 'đá' ngay cho tháng kế (tháng 3/2024) nên ta có Date() là 01/03/2024. chớt quớt!!!.

    giải pháp: dùng RegEx (regular expression - https://en.wikipedia.org/wiki/Regular_expression), 1 cách dùng thuật toán kiểm tra data người dùng nhập vào phải thỏa 1 điều kiện chi đó, ví dụ: chỉ có số. cái món này thì dùng như từ điển, khi nào cần thì tra.
    RegEx = /^(0?[1-9]|[12][0-9]|3[01])[\/\-\:\,\ ](0?[1-9]|1[012])[\/\-\:\,\ ]\d{4}$/
    cụ thể, RegEx này chỉ chấp nhận nhập theo format dd[/-:, ]MM[/-:, ]yyyy (dấu cách có thể là / hay - hay : hay , hay khoảng trắng.

    xong,
    chưa ? vì nhiều trường hợp đặc biệt như hơn 2 khoảng trắng hay - như nói ở trên - ngày 30/02/2024 có hợp lệ không ?

    giờ sao ta ? giải pháp thực ra đã được ông bà ta giải quyết từ lâu: trước sau như 1.

    >> bước 1 : đổi 'ngày' d1, tháng m1, năm yyyy1 do người dùng input (hay sinh ra từ thuật toán) sang Date(), được ngày 'thiệt/thực tế tồn tại theo lịch' là d2/m2/yyyy2.
    >> bước 2: yêu cầu Date() phân rã d2/m2/yyyy2 để lấy d2, m2, yyyy2
    >> bước 3: so sánh d2 với d1, m2 với m1, yyyy2 với yyyy1. nếu trùng nhau hết thì d1/m1/yyyy1 là ngày tháng năm 'thiệt'.

    tương tự cho âm lịch.
    Code:
    // kiểm tra ngày tháng năm do người dùng input
    
    bước 1. dùng RegEx
            var dtCV=window.GetProperty("vnav.Convert date to lunar date dd/MM/yyyy ?",ddSolar+"/"+mmSolar+"/"+yyyySolar);
            var dateformat = /^(0?[1-9]|[12][0-9]|3[01])[\/\-\:\,\ ](0?[1-9]|1[012])[\/\-\:\,\ ]\d{4}$/; // date format regular expression dd[/-:, ]MM[/-:, ]yyyy
            if (!dtCV.match(dateformat)) return false;
    
    bước 2: dùng thuật toán kiểm tra có dùng đủ 3 dấu phân cách (phòng khi khoảng trắng nhiều hay nhập cả chữ và số thì cũng xem xét xử lý tiếp).
    
            var slDt=dtCV.split("/");
            if (slDt.length <2) slDt=dtCV.split("-");
            if (slDt.length <2) slDt=dtCV.split(":");
            if (slDt.length <2) slDt=dtCV.split(",");
            if (slDt.length <2) slDt=dtCV.split(" ");
            if (slDt.length <2) return false;
     
    bước 3: dùng Date() kiểm tra
    
            /* Date("MM/dd/yyyy"), Date(dd,MM,yyyy) . MM is as normal calendar, 1-indexed
             * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
             * construct Date
            */
     
           var _dd=slDt[0]*1, _md=slDt[1]*1, _yd=slDt[2]*1;
            var _ed=new Date(_md+"/"+_dd+"/"+ _yd);
            if (_ed == "NaN" ) return false;
    
    bước 4: cuối cùng, dùng phương pháp 'trước sau như 1'  kiểm tra
    
    if (!chekSDate(_dd,_md,_yd)) return false;
    function chekSDate(dd,mm,yyyy) {
     
        /* check solar date
          mm : 1 indexed as normal calendar month
          partial algorithm source: https://www.w3resource.com/javascript/form/javascript-date-validation.php
        */
        dd    = parseInt(dd,10);
        mm    = parseInt(mm,10);
        yyyy    = parseInt(yyyy,10);
        var res    = false;
        if ( isNaN(dd) || isNaN(mm) || isNaN(yyyy)) {
            return res;
        }
        if (dd<1 || dd >31 || mm <1 || mm > 12) {
            return res;
        }
        //1. overall check by JSengine uitilised Date() constructor.
     
            var _ed = new Date(mm+"/"+dd+"/"+ yyyy); // Date("MM/dd/yyyy");
        if ( _ed.getDate() == dd && ( _ed.getMonth() + 1) == mm && _ed.getFullYear() == yyyy) return true;
        // 2. furthur check, as JSengine is subject to local time or hidden algorithm
        var ListofDays = [31,28,31,30,31,30,31,31,30,31,30,31];
          if (mm == 1 || mm > 2) {
              if (dd > ListofDays[mm-1]) {  return res; }
          }
          if (mm == 2)  {
              var lyear = false;
              if ( (!(yyyy % 4) && yyyy % 100) || !(yyyy % 400)) { lyear = true; }
     
            if ((lyear == false) && (dd>=29)) { return res; }
              if ((lyear == true) && (dd>29)) { return res; }
          }
        return true;
    // vnav Feb24
    }
    
    
    giải quyết xong chuyện này thì tìm sự kiện các ngày mỗi cuối tháng âm/dương lịch xem như được giải quyết.

    kiểm tra sự kiện dương lịch:
    Code:
    function lastDayMonthS(yyyy,mm) {
        return  new Date(yyyy, mm+1, 0).getDate(); // next month end, then go back 1 day
        // mm: the month is 0-indexed
        // parameter day = 0 : 1 day less than first day of the month mm+1, which is last day of the previous month (mm+1-1 = mm).
    }
    
    function getLastDayMonthS(yyyy,mm, ddR) {
        var ddRet=lastDayMonthS(yyyy,mm);
        ddRet = (ddRet > ddR) ? ddR : ddRet;
        return ddRet;
    }
    
    
    function checkSDate(df,mf,pd,nd,dd,mm){
        var ret="";
        var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
        var today= new Date();
        var tmonth=today.getMonth()+1, tyear = today.getFullYear();
    
        var df1=parseInt(df,10), mf1=parseInt(mf,10), dd1 =parseInt(dd,10), mm1 =parseInt(mm,10),pd1 =parseInt(pd,10),nd1 =parseInt(nd,10);
        var diffday1=diffday2=diffday3=diffDays=0;
        var theDate,festDate1,festDate2,festDate3;
        var thisYear=tyear,mf2,mf3,ty2,ty3;
     
        fb.trace("Solar festival date to check.Noted that fb2k-vnav adjusts end-month day if applicable : ["+ df +"/"+ mf + "]" + thisYear);
    
        if (( !isNaN(df1) && !isNaN(mf1) && !isNaN(dd1) && !isNaN(mm1) && !isNaN(pd1) && !isNaN(nd1)) && !( df1>31 || df1<1 || mf1>12 || mf1<0 || dd1>31 || dd1<1 || mm1>12 || mm1<1 || pd1<0 || nd1<0)) {
            // check solar date
            if (!chekSDate(df1, (mf1 ==0 ? mm1 : mf1) ,thisYear)) {
                fb.trace("========= check solar festival date ================  ");
                fb.trace("Festival day/month : " + df1 +"/"+ (mf1 ==0 ? mm1 : mf1));
                fb.trace("Festival date is not valid in this year : " + thisYear);
                fb.trace("Fb2k will try to attemp the date as last day month: " + getLastDayMonthS(thisYear,(mf1 ==0 ? mm1 : mf1)-1,df1) +"/"+ (mf1 ==0 ? mm1 : mf1));
            }
    
            theDate = (new Date(thisYear,mm1-1,dd1));
            if (mf1==0) { // monthly event -> check event prv, this and next month to get the 'real' gap
                tmonth=mm1;
                mf1 = tmonth;
     
                if (tmonth==1) { // prev 1 month
                    mf2 =12;
                    ty2= tyear-1;
                } else {
                    mf2 = tmonth-1;
                    ty2=tyear;
                }
    
                if (tmonth==12) { // next 1 month
                    mf3 =1;
                    ty3= tyear+1;
                } else {
                    mf3 = tmonth+1;
                    ty3=tyear;
                }
     
                // check end-day month
                // if the day is beyond last day month (i.e 30 > 29 day of Feb 2024, then get 29 instead of)
                festDate1 = new Date(thisYear,mf1-1,getLastDayMonthS(thisYear,mf1-1,df1));// same month
                festDate2 = new Date(ty2,mf2-1,getLastDayMonthS(ty2,mf2-1,df1));  // prv 1 month
                festDate3 = new Date(ty3,mf3-1,getLastDayMonthS(ty3,mf3-1,df1)); // next 1 month
     
            } else { // edge year
                 festDate1 = new Date(thisYear,mf1-1,getLastDayMonthS(thisYear,mf1-1,df1)) ; // same year
                 festDate2 = new Date(thisYear-1,mf1-1,getLastDayMonthS(thisYear-1,mf1-1,df1)) ; // prv 1 year
                 festDate3 = new Date(thisYear+1,mf1-1,getLastDayMonthS(thisYear+1,mf1-1,df1)) ; // next 1 year
            }
     
            diffday1 =  Math.round((festDate1 - theDate) / oneDay);
            diffday2 =  Math.round((festDate2 - theDate) / oneDay);
            diffday3 =  Math.round((festDate3 - theDate) / oneDay);
    
            if (diffday1 ==0 ) diffDays=diffday1;
            if (diffday1 < 0 ) { diffDays = (diffday3 > Math.abs(diffday1)) ? diffday1 : diffday3; }
            if (diffday1 > 0 ) { diffDays= (diffday1 > Math.abs(diffday2)) ? diffday2 : diffday1; }
    
            ret="";
    
            if (diffDays < 0 && nd1>= Math.abs(diffDays)) {ret=" (đã qua khoảng "+  Math.abs(diffDays) +" ngày)";}
            if (diffDays == 0) {ret= " (hôm nay)";}
            if (diffDays > 0 && pd1 >= diffDays) {ret= " (còn khoảng "+  diffDays +" ngày)";}
     
        } else {
            ret = "\nSolar festival day/month :" + df +"/"+ mf ;
            ret += "\nDays reminded before :" + pd;
            ret += "\nDays reminded after :" + nd;
            fb.trace("========= check solar festival date ================  ");
            fb.trace("Festival date is not valid, please check festival.txt file");
            fb.trace(ret);
            ret="";
        }
        return ret;
    // vnav Jan24, mod Feb24
    }
    
    kiểm tra sự kiện âm lịch:
    Code:
    function getLastDayMonthL(_ld, _lm, _lyyyy) {
     
        _ld    = parseInt(_ld,10);
        _lm    = parseInt(_lm,10);
        _lyyyy    = parseInt(_lyyyy,10);
        var res    = 0;
    
        if ( isNaN(_ld) || isNaN(_lm) || isNaN(_lyyyy)) {
            return res;
        }
    
        if (_lyyyy < 1200 || 2199 < _lyyyy) { //lunar year constraint as it is whithin this script
            return res;
        }
    
        if (_ld<1 || _ld >30 || _lm <1 || _lm > 12) {
            return res;
        }
    
        res=_ld;
        while (true) {
            if (chekLDate(res, _lm, _lyyyy))
            {
                break;
            } else {
                res=res-1;
            }
        }
        //if (res < _ld) return res;
    
        var sdate = getSolarDate(res, _lm, _lyyyy);  //convert the lunar date to solar date
        if (sdate[3] == "isNaL") return 0;
        var ret=getLunarMonthLength(sdate[0], sdate[1], sdate[2])
    
        return (ret < _ld ? ret : _ld);
    // vnav Feb24
    }
    
    function chekLDate(dd,mm,yyyy) {
    
        /* vnav Feb24
           as Date() constructor solar date of JS engine, it can treat a wrong-human input date as 32-12-2024 and convert to a real solar date.
           as such, this constructor can not be utilised to validate input date.
           regexp can not cover all cases, or to some extent - too complicated.
     
           Solution: straighforwards - 'trước sau như 1'
            step 1: let JSengine/lunar engine to convert input date to a solra Date
            step 2: reverse-engineering the output from step 1
            step 3: compare input date with/to output from step 2.
        */
    
        //dd, mm, yyy : lunar date
        var sDate = getSolarDate(dd, mm, yyyy);//step1: convert lunar to solar date
        if (sDate[3] == "isNaL") return false;
    
        var lDate= getLunarDate(sDate[0], sDate[1], sDate[2]); //step 2: convert solar back to lunar date
     
        if (!(lDate.day==dd && lDate.month==mm && lDate.year == yyyy)) return false; // step 3: compare
        return true;
    // vnav Feb24
    }
    
    
    function checkLDate(df,mf,pd,nd,dd,mm){
        var ret="";
        var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
        var today= new Date();
        var tmonth=today.getMonth()+1, tyear = today.getFullYear();
    
        var df1=parseInt(df,10), mf1=parseInt(mf,10), dd1 =parseInt(dd,10), mm1 =parseInt(mm,10),pd1 =parseInt(pd,10),nd1 =parseInt(nd,10);
        var diffday1=diffday2=diffday3=diffDays=0;
        var sd_theDate,sd_festDate1, sd_festDate2, sd_festDate3;
        var theDate,festDate1,festDate2,festDate3;
        var thisYear=tyear,mf2,mf3,ty2,ty3;
     
        //determine current lunar year
        var lDate=getLunarDate(today.getDate(), tmonth, tyear);
        tyear = thisYear = lDate.year; //convert these variables to lunar year
        tmonth = lDate.month;
    
        fb.trace("Lunar festival date to check. Noted that fb2k-vnav adjusts end-month day if applicable : ["+ df +"/"+ mf + "]" + thisYear);
    
        // we adjust lunar date end-month day as we do with solar date to investigate the exact date
        if (( !isNaN(df1) && !isNaN(mf1) && !isNaN(dd1) && !isNaN(mm1) && !isNaN(pd1) && !isNaN(nd1)) && !( df1>31 || df1<1 || mf1>12 || mf1<0 || dd1>30 || dd1<1 || mm1>12 || mm1<1 || pd1<0 || nd1<0)) {
            // check lunar date
            if (!chekLDate(df1, (mf1 ==0 ? mm1 : mf1) ,thisYear)) {
                fb.trace("========= check lunar festival date ================  ");
                fb.trace("Festival day/month : " + df1 +"/"+ (mf1 ==0 ? mm1 : mf1));
                fb.trace("Festival date is not valid in this lunar year : " + thisYear);
                fb.trace("Fb2k will try to attemp the date as last day month: " + getLastDayMonthL(df1,(mf1==0 ? mm1 : mf1), thisYear)+"/"+ (mf1 ==0 ? mm1 : mf1));
            }
    
            sd_theDate = getSolarDate(dd1, mm1, thisYear);  //convert the lunar date to solar date
            theDate = (new Date(sd_theDate[2],sd_theDate[1]-1,sd_theDate[0]));
    
            if (mf1==0) { // monthly event -> check event prv, this and next month to get the 'real' gap
                tmonth=mm1;
                mf1 = tmonth;
     
                if (tmonth==1) { // prev 1 month
                    mf2 =12;
                    ty2= tyear-1;
                } else {
                    mf2 = tmonth-1;
                    ty2=tyear;
                }
    
                if (tmonth==12) { // next 1 month
                    mf3 =1;
                    ty3= tyear+1;
                } else {
                    mf3 = tmonth+1;
                    ty3=tyear;
                }
    
                 sd_festDate1 = getSolarDate(getLastDayMonthL(df1,mf1, thisYear), mf1, thisYear); //convert event-festival lunar date to solar date - same month
                 sd_festDate2 = getSolarDate(getLastDayMonthL(df1,mf2, thisYear), mf2, ty2); //convert event-festival lunar date to solar date - prv 1 month
                 sd_festDate3 = getSolarDate(getLastDayMonthL(df1,mf3, thisYear), mf3, ty3); //convert event-festival lunar date to solar date - next 1 month
            } else {
                 sd_festDate1 = getSolarDate(getLastDayMonthL(df1,mf1, thisYear), mf1, thisYear); //convert event-festival lunar date to solar date - same year
                 sd_festDate2 = getSolarDate(getLastDayMonthL(df1,mf1, (thisYear-1)), mf1, thisYear-1); //convert event-festival lunar date to solar date - prv year
                 sd_festDate3 = getSolarDate(getLastDayMonthL(df1,mf1, (thisYear+1)), mf1, thisYear+1); //convert event-festival lunar date to solar date - next year
            }
    
            festDate1 = (new Date(sd_festDate1[2],sd_festDate1[1]-1,sd_festDate1[0]));
            festDate2 = (new Date(sd_festDate2[2],sd_festDate2[1]-1,sd_festDate2[0]));
            festDate3 = (new Date(sd_festDate3[2],sd_festDate3[1]-1,sd_festDate3[0]));
     
            diffday1 = Math.round((festDate1 - theDate) / oneDay);
            diffday2 = Math.round((festDate2 - theDate) / oneDay);
            diffday3 = Math.round((festDate3 - theDate) / oneDay);
    
            if (diffday1 ==0 ) diffDays=diffday1;
            if (diffday1 < 0 ) { diffDays = (diffday3 > Math.abs(diffday1)) ? diffday1 : diffday3; }
            if (diffday1 > 0 ) { diffDays= (diffday1 > Math.abs(diffday2)) ? diffday2 : diffday1; }
    
            ret="";
    
            if (diffDays < 0 && nd1>= Math.abs(diffDays)) {ret=" (đã qua khoảng "+  Math.abs(diffDays) +" ngày)";}
            if (diffDays == 0) {ret= " (hôm nay)";}
            if (diffDays > 0 && pd1 >= diffDays) {ret= " (còn khoảng "+  diffDays +" ngày)";}
     
     
        } else {
            ret = "\nLunar festival day/month :" + df +"/"+ mf ;
            ret += "\nDays reminded before :" + pd;
            ret += "\nDays reminded after :" + nd;
            fb.trace("========= check lunar festival date ================  ");
            fb.trace("Lunar festival date is not valid, please check festival.txt file");
            fb.trace(ret);
            ret="";
        }
    
        return ret;
    // vnav Jan24, mod Feb24
    }
    
    script đầy đủ: (mình để luôn ở đây để các bác tiện tham khảo, khỏi mất công tìm trong bản dựng Fb2k-VNAV 2.2)

    Vậy thôi.

    Lưu ý: thuật toán âm lịch thuộc bản quyền của tác giả tương ứng, mình và mạng nghe nhìn Việt nam vnav.vn không chịu trách nhiệm về tính chính xác thuật toán liên quan tính âm lịch hay kiểm tra sự kiện, hay thông tin do thuận toán sinh ra.
     

    Attached Files:

    Last edited: 14/3/24
    dangbn likes this.
  8. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Thân gửi anh em 4rum vnav,
    bữa nay mình quay lại chủ đề được quan tâm từ lâu : đồ họa với foobar2000, cụ thể minh họa là phần album/artist art (gọi chung là cover arts).
    cover arts thường là hình ảnh đẹp, chỉn chu và mang nhiều thông tin hữu ích. đôi khi, tác phẩm còn kèm theo rất nhiều hình giới thiệu về lời bài hát, ban nhạc hay tự sự của ca sỹ.

    đây là phần thú vị khi nghe nhạc trên máy tính.

    1. giới thiệu:
    //-----------------//

    đồ họa nói chung trong fb2k đi liền với các plugin script (như WSH, SMP, JScript Panel, JScript Pnael 3), cụ thể là các method vẽ chữ, hình (tròn, vuông, . . .), tô màu hình, image.
    chính các method này đã tạo ra làn sóng mod skin cho fb2k, tiêu biểu là Tedgo với bản DarkOne đầu tiên quãng 2008 với WSH Script và liên tục đến khoảng 2019 với JSPanel, bên cạnh Br3tt với vô số bản skin đẹp mắt do đồ họa đẹp + hiệu ứng customised window từ chính OS, . . . và nhiều đoạn script liên quan đến cover arts, playlist . . . đến nay vẫn là nguyên mẫu được kế thừa. nếu nhìn vào sample script của các plugin script, ta dễ dàng bắt gặp mạch ADN đó được phát triển, hoàn thiện ra sao. Ví dụ: với sample của JScript Panel 3 mà Marc2k3 liên tục cập nhật những ngày qua (https://jscript-panel.github.io/gallery/), ta đã có đủ bộ công cụ cho 1 chương trình audio play, từ quản lý playlist, phát nhạc đến trình diễn cover arts, download artist arts từ last.fm, xem các review, bio artist. các ví dụ vẽ chữ, hình cũng dễ hiểu và đầy đủ.

    tóm lại, muốn vẽ chữ, hình thì dùng plugin script là chính, và nguyên tắc chung - plugin nào muốn vẽ thì phải hỗ trợ script (ví dụ: PSS, ELP), ngoại trừ 1 số tính năng hay plugin khá đặc thù (như album art, reFacets ngày xưa) khá tù túng.

    về cơ bản, có 2 đối tượng liên quan vẽ vời:
    - Graphic : trực tiếp vẽ hình, tô màu hình, image và chữ ra screen (chỉ screen, không ra printer).
    - Image : quản lý và khai thác image, như áp lọc màu, thay đổi kích cỡ, load image từ HDD, save image xuống HDD.

    trong khi image có thể được xử lý ở bất kỳ đoạn script nào thì Graphic chỉ được 1 method duy 1 của đối tượng window quản lý: window.on_paint(Graphic); muốn vẽ gì thì phải thực hiện từ đây- trực tiếp hay gián tiếp.

    Plugin script có sẵn sample script thì cứ lấy mà dùng, còn cơ hội nào cho ta !?
    tất nhiên, khi nói đến đồ họa thì chỉ có trí tưởng tượng là giới hạn.

    2. minh họa:
    //--------------------//
    các ví dụ minh họa về cover arts có sẵn theo bản dựng Fb2k-VNAV 2.2 nêu trên.

    2.1 cover arts, fb2k quản lý cover arts như thế nào:

    fb2k chia cover arts thành:
    - 5 loại : front, back, disk, icon, artist, và
    - lưu trữ 2 nơi : embedded/nhúng theo file (metatag) hay lưu trên HDD theo audio file, cách đặt tên - có vẻ - là 'front cover.xxx', ' back cover.xxx', 'disk/cd.xxx','icon.xxx','artist.xxx'.

    plugin script cũng có method tương ứng để lấy các cover arts này.
    tuy vậy, ta thường xuyên gặp những album/track không có 'tý' hình ảnh nào cả, hay có nhưng không như ý - lấy front cover lại được back cover, hay back cover có artist thì không . . .

    ở đây, chúng ta sẽ tiếp cận theo hướng :
    - tìm kiếm tất cả cover arts, bao gồm nhúng và lưu trên HDD.
    - tìm thêm image với những định dạng khác nhau ở những folder khác : thông tin các bác cung cấp thì có vẻ folder có tên cover,art, artwork, artworks, scan, image trong thư mục cùng tên track nhạc là nơi chứa image. ngoài ra, có trường hợp (mình xem 1 hồi mới hiểu ý tác giả) image để folder ngang cấp với folder chứa file audio, do tác giả có nhiều album của cùng 1 collection nên lưu chung thư mục (collection1), mỗi album lưu riêng file nhạc chủ đề (collection1/album1; collection1/album2 . . ), 1 folder lưu image chung (collection1/cover).
    chúng ta sẽ để users tùy biến các folder này.

    2.2 tìm kiếm cover arts, hình ảnh:

    công thức chung tìm kiếm cover arts như sau:
    - bước 1: tìm tất cả 5 loại cover arts nhúng.
    - bước 2 : tìm tất cả 5 loại cover arts lưu theo file.
    - bước 3: gom tiếp các hình chứa trong các folder ngóc ngách do users chỉ định.

    tới đây, có 2 vấn đề nhỏ phải xử lý:
    a: như trên nói, không phải lúc nào 5 loại cover (thực ra có 1 icon) cũng đủ, nếu dùng vòng lặp để quét qua cover arts, cẩn thận xét lại cover nào không có để bổ sung hình.

    b. khi số lượng image nhiều, dung lượng image lớn (có bác có bộ sưu tập thật đáng nể, 1 album có thể hơn 20 hình, mỗi hình tif cỡ 30 MB) : nếu load hết vào memory thì rất chậm, load mỗi khi cần dùng thì chậm đều đều.
    giải pháp:
    (i) như chương trình play phim - load từ từ và load ở hậu trường.
    (ii) ngoài ra, chúng ta sẽ tận dụng cơ chế 'bóp'/crop/resize image nhỏ đi và lưu xuống HDD - mỗi khi cần view lớn thì ta load image thực sự. tuy vậy, khi load hình từ HDD cũng mất khá nhiều thời gian.

    nhưng đôi khi phải load hết 1 lần để trong RAM đặng xử lý cho nhanh (như các skin reel-to-reel, cassette player, turntable của Dmitri/iprad) thì resize, nhưng vì thời gian load từ HDD khá lâu nên cần hiển thị tiến trình cho người dùng biết.

    xong ?
    chưa. như đã nói, bản dựng Fb2k-vnav 2.2 tận dụng Internet để 'giữ chân' người dùng, nên ngoài việc xem youtube, xem review, tiểu sử ca sỹ, còn có thể load image từ last.fm, google/spotify search hình ảnh album artist.

    khoảng hơn 7/8 năm trước, khi plugin script còn loanh quanh với WSH, kgena_ua đã tận dụng các ActiveX để load hình artist từ last.fm nhưng mà rất cực, cơ bản là thủ công lần mò qua từng tag HTML để tìm thông tin liên quan hình ảnh từ last.fm - mà lúc đó các web site này cũng nâng cấp thay đổi thường xuyên nên xem như mèo vờn chuột.

    hiện giờ, mọi thứ đã đơn giản rất nhiều với plugin script mới như JScript Panel 3: tác giả Marc2k3 đã cung cấp sẵn tính năng search Web và download hình ảnh (ví dụ sample script: https://jscript-panel.github.io/gallery/thumbs/).

    2.3 ví dụ minh họa:
    2.3.1 lấy tất cả cover arts, image trong các folder phụ, không phân biệt loại cover.
    script này của Grimes (admin của 4rum foobar2000 https://foobar-users.de), cho phép slide show cover arts. mình đã mod để thêm hình ảnh và tự động slide show, cài đặt trong phần [Library Filter] tab.
    trong script, mình đã rem đoạn code lấy cover arts từ HDD do WSH có vẻ lấy đại 1 hình nào đấy từ HDD nếu không tìm được đúng loại cover art. việc này đã do đoạn code load image từ các folder phụ đảm nhiệm và làm tốt.
    Code:
    // ==PREPROCESSOR==
    // @name "Cover Art Animation Panel 1.0 (CUI/DUI)"
    // @author "Grimes, mod by vnav Mar24"
    // ==/PREPROCESSOR==
    
    /* feature:
    +original functions by Grimes:
        - animation cover arts. (core feature)
        - animation style select via context menu.
        limitation:
        - no automatical cycle.
        - only embedded + local cover arts (by filename).
        - no caching.
        - no default cover art.
    
    + mod by vnav Mar24:
     cache, animate and automatic cycle
        - all 4 EMBEDDED/Local cover arts if any.
        - image arts are added up to a pre-defined max number/or ALL if nay.
        - several subfolders are seached for images.
        - resize arts to thumb size to improve performance.
        - default art.
        - option for cycle and cycle time.
        - animation style applied randomly.
       
     limitattion:
        - no ONLINE sources (last.fm, musicall, brainz . . ) coverred. (see JSP3 Cover Art modul).
        - no CD shape applied to CD cover. (see WSH Cover Art modul).  
    */
    
    
    // vnav
    var img_path = fb.ProfilePath + "skins\\tech\\images\\vnav_fb2k\\"
    var noArt = fb.ProfilePath + window.GetProperty('VNAV.TRACK.NOART',"skins\\tech\\images\\vnav_fb2k\\noThumbbottomleft.png"); // default art image
    var g_imgs =[];
    var g_img_mask = gdi.Image(img_path+"foony_Mask.png");
    var metadb = fb.IsPlaying ? fb.GetNowPlaying() : fb.GetFocusItem();
    
    var imgExt =["jpg","jpeg","png","tif"];
    var subFolder =["","\\scan","\\cover","\\art","\\artwork","\\img"];
    var maxCover=window.GetProperty("VNAV.Automatic cycle arts.MaxNumber",305) -300;
    var coverOnShow=5; // minmum number as font+back+cd+icon+artist cover.
    
    function cache_covers() {
     
        metadb = fb.IsPlaying ? fb.GetNowPlaying() : fb.GetFocusItem();
       
        for (var i=0; i < g_imgs.length; i++) {
            if (g_imgs[i]) g_imgs[i].Dispose();  
        }
        g_imgs=[];
     
        if (metadb) {
               
            // embedded covers ==>
                for (var i=0; i < 5; i++) {
                    g_imgs[i] = utils.GetAlbumArtEmbedded(metadb.RawPath, i);
                }      
            // local covers
            /*
                var j=0;
                for (var i=0; i < 5; i++) {
                    if (j> 4) break;
                    if (!g_imgs[i]) {
                    for (j=j; j < 5;) {
                         if (utils.GetAlbumArtV2 (metadb, j)) {
                             g_imgs[i] = utils.GetAlbumArtV2 (metadb, j);
                             j++;
                             break
                         }
                         j++;
                    }
                }
               }
            */
            // search for 5 images
    
                /* as we do not know g_imgs[i] contains cover at which position via the above loop (get embedded covers). ie. maybe i=0 (front) or i=4 (artist).
                   that is why we do not add up images (push). */
                var list_length=5;
               
                var pathTrack = fb.TitleFormat("$directory_path(%path%)").EvalWithMetadb(metadb);
                var maxImg,mMaxImg,ije;
                var g_image_list = new Array;
               
                for (var i=0; i<imgExt.length; i++) {
                    for (var j=0; j<subFolder.length; j++) {
                                           
                        g_image_list = utils.Glob(pathTrack+(subFolder[j]).replace(/^\s+|\s+$/gm,'')+"\\*." + (imgExt[i]).replace(/^\s+|\s+$/gm,'')).toArray();
                        list_length =  g_image_list.length > 0 ? list_length + g_image_list.length  : list_length;
                        maxImg = maxCover == 0 ? g_image_list.length : Math.min(maxCover,g_image_list.length);
                        mMaxImg = maxCover == 0 ? list_length : maxCover;
                        ije=0;
                       
                        for (var ij = 0; ij < mMaxImg ; ij++ ) {
                             if (ije>= maxImg) break;
                             if (!g_imgs[ij]) {
                                 for (ije=ije; ije < maxImg ; ){
                                    if (g_image_list[ije]) { // always true the 1st time.
                                        try {
                                            g_imgs[ij] = gdi.Image(g_image_list[ije]);
                                            ije++;
                                            break;
                                        } catch (e) {}
                                    }
                                    ije++; // just in case file g_image_list[ije] can not be accssed as deleted/locked by its attribute.
                                }
                             }
                        }
                         if (maxCover >0){  
                             var exit=false;
                             for (k=0; k < maxCover; k++){
                                 if ((g_imgs[k]) && k == (maxCover -1)) exit=true;
                             }
                             if (exit) break;
                        }                    
                    } //j
                         if (maxCover >0){  
                            var exit=false;
                            for (k=0; k < maxCover; k++){
                                 if ((g_imgs[k]) && k == (maxCover -1)) exit=true;
                            }
                            if (exit) break;
                        }                    
                } //i
               
             coverOnShow = maxCover == 0 ? g_imgs.length : Math.min(g_imgs.length, maxCover);
           
             for (var i=0; i < coverOnShow; i++) {
                    if (g_imgs[i]) {
                        var g_img_tmp = g_imgs[i];
                        break;
                    }
                }
             
             for (var i=0; i < coverOnShow; i++) {
                    if (!g_imgs[i]) {
                        if (g_img_tmp) g_imgs[i] = g_img_tmp;
                    }                  
                }
           
    
            try {
                 for (var i=0; i < coverOnShow; i++) {
                    if (!g_imgs[i]) g_imgs[i]= gdi.Image(noArt);
                    if (g_imgs[i])  {
                       
                        if (g_imgs[i].Width> g_imgs[i].Height) {
                            var n_w = 512;
                            var n_h = (g_imgs[i].Height/g_imgs[i].Width)* 512;                        
                        } else {
                            var n_h = 512;
                            var n_w = (g_imgs[i].Width/g_imgs[i].Height)* 512;
                        }
                        g_imgs[i]=g_imgs[i].Resize(n_w, n_h);
                        var mask1 = g_img_mask.Resize(n_w, n_h);
                        g_imgs[i].ApplyMask(mask1);
                        mask1.Dispose();
                    }
                }
            } catch (e) {}
        }
       
     }
    2.3.2 lấy tất cả cover arts, image trong các folder phụ, phân biệt loại cover để áp vài bộ lọc tương ứng (ví dụ: hình CD thì sẽ vẽ tròn).
    (xin xem post tiếp theo)
     
    Last edited: 15/3/24
  9. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    2.3.2 lấy tất cả cover arts, image trong các folder phụ, phân biệt loại cover để áp vài bộ lọc tương ứng (ví dụ: hình CD thì sẽ vẽ tròn).
    script này của Hunter (cao thủ 1 thời https://www.deviantart.com/extremehunter1972), cho phép cycle cover arts. mình đã mod để thêm hình ảnh, cho phép users thêm bớt folder phụ chứa image và loại format image, cài đặt trong phần [Now Playing] tab.


    Code:
    // ==PREPROCESSOR==
    // @name "Cover Art Panel 1.0 (CUI) - deployed with Foony theme"
    // @name "Cover Art Panel 1.1 (CUI) - deployed with Fb2K-VNAV"
    // @author "Hunter, mod by vnav Feb24"
    // ==/PREPROCESSOR==
    
    /* vnav :
     can cycle ALL EMBEDDED images and LOCAL ones in the same 1 folder as playing track's or 1 user-defined folder a time.
     limitattion: no ONLINE sources (last.fm, musicall, brainz . . ) coverred as JSP3 cover modul.
     CD shape applied to CD cover (if any).
    
     option to only show 5 cached images (which stand for front/back/cd/icon/artist in theory) to speed up fb2k, not ALL cover arts.
    */
    
    var img_path = fb.ProfilePath + "skins\\tech\\images\\vnav_fb2k\\"
    var g_img, image, g_rotate_img, g_artreader, img_bg;
    var id = 0; // default 1st image cover art.  0: icon, 1: front, 2: back, 3: cd, 4: artist cover.
    var maxDefaultAlbumArt = 5; // reseved 5 slot for icon, front, back, cd, artist art images.
    var idMax=maxDefaultAlbumArt;
    
    var album_tf = fb.TitleFormat("$directory(%path%)"); //%album% may be empty as the others
    var album = null;
    var g_img_mask = gdi.Image(img_path+"foony_Mask.png");
    var g_img_cdmask = gdi.Image(img_path+"foony_CDMask.png");
    var cr = gdi.Image(img_path+"foony_COVER_REFLECT.png");
    var g_ext = window.GetProperty("vnav.Album art file extension", "  jpg  |  jpeg  |  png  |  tif  ");
    var imgExt = g_ext.split("|");
    var s_ext = window.GetProperty("vnav.Album art file subfolder", "  | \\scan | \\cover | \\art |  \\artwork | \\img ");
    var subFolder =s_ext.split("|");
    var g_imgList = new Array(); //list fill path file image
    var subImgFolder=""; // sub folder/dir contained images. If any, declare as "\\subdir"
    var g_imgListQtt=0; // total Images
    var ij=0; // total local images assigned for default arts
    var open_img="";
    var showAllAlbumArt = window.GetProperty("Show all cover arts", false);
    var embedded, front, back, cd, icon, artist = null;
    
    // =============================================== //
    
    function cache_covers() {
        //vnav: cache only 4 images, the others (if any) will be fetched on the fly to increas loading-time and reduce on-hold memory.
        // OTW, cache all images while put them on show, but may consume memory.
    
        metadb = fb.IsPlaying ? fb.GetNowPlaying() : fb.GetFocusItem();
        //if (metadb.RawPath.indexOf("http") > -1) return; // stream online, then there is nothing to show. except for online artist downloaded, which is not applicable by this JSengine
        idMax=maxDefaultAlbumArt;
        g_imgList=[]; // clear list images, otherwise it will be added up
        // vnav - check and get all album arts/covers, no matter embedded (as artist) or enclosed as cover (may be another artist)
        fb.trace("\nWSH: cache_covers() requested.");
         if (metadb) {
            // 1.Fisrtly, grab ALL images in the same folder as current playing or focusing/selected track's
                var pathTrack = fb.TitleFormat("$directory_path(%path%)").EvalWithMetadb(metadb);
               
                for (var j = 0; j < imgExt.length;j++ ) {
                    for (var i = 0; i < subFolder.length;i++ ) {
                    var g_image_list = utils.Glob(pathTrack+(subFolder[i]).replace(/^\s+|\s+$/gm,'')+"\\*." + (imgExt[j]).replace(/^\s+|\s+$/gm,'')).toArray();
                    for (var ij = 0; ij < g_image_list.length;ij++ ) {
                        fb.trace("cover file loading : " + g_image_list[ij]);
                        g_imgList.push(g_image_list[ij])
                    }
                    }
                }
               
                //fb.trace("\nmetadb "+ metadb.RawPath);
                //fb.trace("$directory_path(%path%) eVal: " + fb.TitleFormat("$directory_path(%path%)").eVal());
                //fb.trace("$directory_path(%path%) EvalWithMetadb: " + fb.TitleFormat("$directory_path(%path%)").EvalWithMetadb(metadb));
           
           
            //2. check the embedded images. para 0: front 1: back 2: disc 3: icon 4: artist
                front = utils.GetAlbumArtEmbedded(metadb.RawPath, 0);
                back = utils.GetAlbumArtEmbedded(metadb.RawPath, 1);
                cd = utils.GetAlbumArtEmbedded(metadb.RawPath, 2);
                icon = utils.GetAlbumArtEmbedded(metadb.RawPath, 3);
                artist = utils.GetAlbumArtEmbedded(metadb.RawPath, 4);
               
            // 3. if not, chek the images enclosed in the same folder as track's
                /* in fact, WSH engine will grab only the one  1st  image format-supported (ie. png, jpg . . .) regardless para 0.1.2..4.
             
                   If no file-name specified - we do not know the rule, 4 covers are the same 1 image
                   and, the following method/function can be used instead of: this is just to grab ANY 1st image in the folder.
                   gdi.Image(fb.TitleFormat("$directory_path(%path%)").eVal()+"\\*." + g_arr[i])
               
                   we BYPASS these method cause we can not depend on somethig we really do NOT know or understand.
               
                   if (! front) {front = utils.GetAlbumArtV2 (metadb, 0); fb.trace("WSH cover embedded : NO front");} else {fb.trace("WSH cover embedded : YES front");}
                   if (! back) {back = utils.GetAlbumArtV2(metadb, 1); fb.trace("WSH cover embedded : NO back");} else {fb.trace("WSH cover embedded : YES back");}
                   if (! cd) {cd = utils.GetAlbumArtV2(metadb, 2); fb.trace("WSH cover embedded : NO cd");} else {fb.trace("WSH cover embedded : YES cd");}
                   if (! icon) {icon= utils.GetAlbumArtV2(metadb, 3); fb.trace("WSH cover embedded : NO icon");} else {fb.trace("WSH cover embedded : YES icon");}
                    if (! artist) {artist = utils.GetAlbumArtV2(metadb, 4); fb.trace("WSH cover embedded : NO artist");} else {fb.trace("WSH cover embedded : YES artist");}
                */
                fb.trace(" ============== COVER ART wsh ===================");
                fb.trace("WSH local cover  : counting . . . " + g_imgList.length);
                fb.trace("WSH cover embedded : check in . . . ");
    
                if (! front) {fb.trace(" WSH cover embedded : NO front");} else {fb.trace("WSH cover embedded : YES front");}
                if (! back) {fb.trace("WSH cover embedded : NO back");} else {fb.trace("WSH cover embedded : YES back");}
                if (! cd) {fb.trace("WSH cover embedded : NO cd");} else {fb.trace("WSH cover embedded : YES cd");}
                if (! icon) {fb.trace("WSH cover embedded : NO icon");} else {fb.trace("WSH cover embedded : YES icon");}
                if (! artist) {fb.trace("WSH cover embedded : NO artist");} else {fb.trace("WSH cover embedded : YES artist");}
    
            /* 4. if not, take some images from store done in step 1.
             vnav Feb24
           
             in fact, 1 to 4 images in the store are already maybe taken : we DO NOT know the way WSH selected these 4 images
             thus, we surely double 4 images on showing.
             Moreover, same images may be look the same but differrent file-names.
           
             AS we cycle the pool on show, then this is not big deal.
            */
                g_imgListQtt=0;
                ij=0;
                for (g_imgListQtt = 0; g_imgListQtt < g_imgList.length;g_imgListQtt++ ) {
                     fb.trace("Caching local images to cover g_imgListQtt, ij : " + g_imgListQtt+"/"+ij);
                    // sould we have another way to compare, such as file name.
                    if (g_imgListQtt==0 && !icon) {
                            icon = gdi.Image(g_imgList[ij]);
                            ij=ij+1;
                        }
                   
                    if (g_imgListQtt==1 && !front) {
                            front = gdi.Image(g_imgList[ij]);
                            ij=ij+1;
                        }
                    if (g_imgListQtt==2 && !back) {
                            back = gdi.Image(g_imgList[ij]);
                            ij=ij+1;
                        }
                       
                    if (g_imgListQtt==3 && !cd) {
                            cd = gdi.Image(g_imgList[ij]);
                              ij=ij+1;
                        }
    
                    if (g_imgListQtt==4 && !artist) {
                            artist = gdi.Image(g_imgList[ij]);
                              ij=ij+1;
                        }
                       
                    if (g_imgListQtt == 5) {
                        break;}
                }
               
                //5. check out : if there are less than 5 covers, then share among  default cover arts.
                // balance makes things nice
                if (front || back || cd || icon || artist ) {
                    if (front){
                        back =  !back   ?   front : back ;
                        cd =    !cd     ?   front : cd;
                        icon =   !icon     ?   front : icon;
                        artist = !artist ?  front : artist;
                    }
    
                    else if (back){
                        front = !front  ?   back : front ;
                        cd =    !cd     ?   back : cd;
                        icon =   !icon  ?   back : icon;                   
                        artist = !artist ?  back : artist;
                    }
                    else if (cd){
                        front = !front  ? cd : front ;
                        back =  !back   ? cd : back;
                        icon =  !icon   ? cd : icon;
                        artist = !artist ? cd : artist;
                    }
                    else if (artist){
                        front = !front  ? artist : front ;
                        back =  !back   ? artist : back;
                        icon =  !icon  ?  artist : icon;
                        cd =    !artist ? artist : cd;
                    }
                    else if (icon){
                        front = !front  ? icon : front ;
                        back =  !back   ? icon : back;
                        artist = !artist ? icon : artist;
                        cd =    !artist ? icont : cd;
                    }
                }
                fb.trace("\nWSH cover embedded : check out . . . ");           
                if (! front) {fb.trace(" WSH cover : NO front");} else {fb.trace("WSH cover : YES front");}
                if (! back) {fb.trace("WSH cover : NO back");} else {fb.trace("WSH cover : YES back");}
                if (! cd) {fb.trace("WSH cover : NO cd");} else {fb.trace("WSH cover : YES cd");}
                if (! icon) {fb.trace("WSH cover : NO icon");} else {fb.trace("WSH cover : YES icon");}
                if (! artist) {fb.trace("WSH cover : NO artist");} else {fb.trace("WSH cover : YES artist");}
    
                // take note
                g_imgListQtt=g_imgList.length-Math.min(g_imgListQtt,ij);
                idMax=Math.max(maxDefaultAlbumArt,(maxDefaultAlbumArt+g_imgListQtt));
                // view list
                fb.trace(" -------------------------------\ntotal (ie) embedded cover arts : "+(idMax- g_imgListQtt -ij) );
                fb.trace(" total (ij) local images assigned for default cover arts : "+ij);           
                fb.trace(" total (g_imgListQtt) remainning local images :"+g_imgListQtt );
                fb.trace(" total (idMax) image cover arts on show (some maybe replicated to fill up max "+maxDefaultAlbumArt+ " default cover arts) :"+idMax );
                if (g_imgList.length > 0) {
                    fb.trace ("\ndetail local image arts :" );
                    fb.trace ("   > local images CACHED as assigned for cover arts:" );
                    for (var pi = 0; pi < ij; pi++ ) { fb.trace ("       id="+pi+".> " + g_imgList[pi]);}
                    fb.trace ("   > local images files NOT cached :" );
                    for (var pii = ij; pi < g_imgList.length; pi++ ) { fb.trace ("      id="+pi+".> " + g_imgList[pi]);}
                    fb.trace(" ============== COVER ART =================wsh\n");
                }
           
        }
        try {
        if (front && metadb && window.GetProperty("Cover Mask") == true) {
            front =resize(font);
            var mask1 = g_img_mask.Resize(front.Width, front.Height);
            front.ApplyMask(mask1);
            mask1.Dispose();
        }
        if (back && metadb && window.GetProperty("Cover Mask") == true) {
            back =resize(back);
            var mask2 = g_img_mask.Resize(back.Width, back.Height);
            back.ApplyMask(mask2);
            mask2.Dispose();
        }
        if (cd && metadb && window.GetProperty("CD Mask") == true) {
            cd =resize(cd);
            var mask3 = g_img_cdmask.Resize(cd.Width, cd.Height);
            cd.ApplyMask(mask3);
            mask3.Dispose();
        }
        if (artist && metadb && window.GetProperty("Cover Mask") == true) {
            artist =resize(artist);
            var mask4 = g_img_mask.Resize(artist.Width, artist.Height);
            artist.ApplyMask(mask4);
            mask4.Dispose();
        }
        } catch (e) {}
        if (!metadb) { // if not track is selected or playing. metadb = fb.GetNowPlaying() or fb.GetFocusItem()
    
            if (front) front.Dispose();
            if (back) back.Dispose();
            if (cd) cd.Dispose();
            if (artist) artist.Dispose();
        }
    // this function grab all 4 album art images and booklist
    // cache max 4 images to memory . vnav Feb24
    }
     
  10. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    2.3.3 lấy tất cả cover arts, image trong các folder phụ + online last.fm, không phân biệt loại cover arts.
    script gốc này của Mar2k3 (tác giả JScrip Panel 3, https://jscript-panel.github.io/gallery/thumbs/), cho phép lấy artist arts từ last.fm hay image từ HDD, hiển thị image rất nhỏ (thumb) và view (zoom in) lớn.
    tuy nhiên, Marc2k3 giới hạn số lượng và dung lượng image.

    mình có ý nhờ tác giả mod giúp để hiển thị cả online+local image nhưng chắc tới mùa quít nên tự làm luôn: xem như bung hết code để xây lại ( rất tiếc vì code của Marc2k3 quá đẹp và gọn), đã xây lại thì tới luôn:
    - xem 1 lúc online+offline/local image.
    - không giới hạn dung lượng file local (vẫn giới hạn số image down từ last.fm, nhưng users có thể chỉnh số lượng)
    - thêm cover arts.

    đổi lại, người dùng sẽ phải cho phép tạo 1 thư mục chứa image thu nhỏ (cỡ 512x512 px) trong cùng thư mục nhạc.
    fb2k sẽ save image thu nhỏ này theo từng album (mỗi khi thay đổi path), đồng thời cache vào RAM để tăng tốc load image. chỉ lưu ý, khi copy image từ RAM cache, dùng deep copy (Clone) để không ảnh hưởng đến image gốc đã cache.

    ngoài ra, để hạn chế fb2k load lại image liên tục, chương trình yêu cầu tất cả callbacks muốn load image phải đi qua on_playback_new_track() của core foobar2000.

    do mỗi track/file nhạc có thể trình bày bởi ca sỹ khác nhau nên load image (từ RAM cache) + cover arts theo từng track nhạc.

    cuối cùng, chúng ta không biết khi nào download image từ last.fm xong nên cứ để hắn âm thầm làm việc ở hậu trường (DownloadImageAsync(window_id, url)), lâu lâu ghé mắt để lấy image . (ý tưởng của Marc2k3).

    script cài đặt trong phần [Now Playing] tab.

    mình để đoạn script thumb nguyên bản của Marc2k3 kèm theo JScript Panel 3 - 3.4.14 ở đây để các bác tham khảo, đoạn script này liên quan đến load image:
    Code:
    
        this.create_mask = function() {
            this.circular_mask = utils.CreateImage(512, 512);
            var temp_gr = this.circular_mask.GetGraphics();
            temp_gr.FillEllipse(256, 256, 256, 256, RGB(0, 0, 0));
            this.circular_mask.ReleaseGraphics();
            temp_gr = null;
        }
    
      
        this.create_thumb = function (img) {
            var size = this.properties.px.value;
    
            if (img.Width < img.Height) {
                var src_x = 0;
                var src_w = img.Width;
                var src_h = img.Width;
                var src_y = Math.round((img.Height - src_h) / 4);
            } else {
                var src_y = 0;
                var src_w = img.Height;
                var src_h = img.Height;
                var src_x = Math.round((img.Width - src_w) / 2);
            }
    
            var square = utils.CreateImage(size, size);
            var temp_gr = square.GetGraphics();
            temp_gr.DrawImage(img, 0, 0, size, size, src_x, src_y, src_w, src_h);
            square.ReleaseGraphics();
            return square;
        }
    
        this.create_thumbs = function () {
            _dispose.apply(null, this.thumbs);
            this.thumbs = [];
    
            _.forEach(this.images, function (image) {
                this.thumbs.push(this.create_thumb(image));
            }, this);
        }
    
        this.download = function () {
            if (!_tagged(this.artist)) {
                return;
            }
            var url = 'https://www.last.fm/music/' + encodeURIComponent(this.artist) + '/+images';
            var task_id = utils.HTTPRequestAsync(window.ID, 0, url, this.headers);
            this.artists[task_id] = this.artist;
        }
    
      
    
        this.get_files = function () {
            var files = [];
    
            if (this.properties.source.value == 0 && _.includes(this.properties.tf.value, '\r\n')) {
                var folders = _stringToArray(this.properties.tf.value, '\r\n').map(function (item) {
                    return panel.tf(item);
                });
                files = _getFiles(folders, this.exts);
            } else {
                files = _getFiles(this.folder, this.exts);
            }
    
            if (this.properties.source.value == 1 && files.length > 1) {
                this.default_file = this.folder + this.defaults[this.artist];
                var tmp = _.indexOf(files, this.default_file);
                if (tmp > -1) {
                    files.splice(tmp, 1);
                    files.unshift(this.default_file);
                }
            }
    
            var count_limit = Math.floor(8192 / this.properties.px.value)
            var size_limit = this.properties.size_limit.value;
            var total_file_size = 0;
            var filtered = files.filter(function (item, i) {
                total_file_size += utils.GetFileSize(item);
                return total_file_size < size_limit && i <= count_limit;
            });
            if (filtered.length < files.length) {
                console.log(N, "Not all images have been loaded. This is due to excessive total size and/or count.");
            }
            return filtered;
        }
    
        this.http_request_done = function (id, success, response_text) {
            var artist = this.artists[id];
            if (!artist) return; // we didn't request this id
            if (!success) return console.log(N, response_text);
    
            var filename_base = _artistFolder(artist) + utils.ReplaceIllegalChars(artist) + '_'
    
            _(_getElementsByTagName(response_text, 'li'))
                .filter({ className : 'image-list-item-wrapper' })
                .map(function (item) {
                    var img = _firstElement(item, 'img');
                    var url = img.src.replace('avatar170s/', '');
                    return {
                        url : url,
                        filename : filename_base + url.substring(url.lastIndexOf('/') + 1) + '.jpg',
                    };
                })
                .filter(function (item) {
                    return !utils.IsFile(item.filename);
                })
                .take(this.properties.limit.value)
                .forEach(function (item) {
                    utils.DownloadFileAsync(window.ID, item.url, item.filename);
                })
                .value();
        }
    
      
    
        this.playback_time = function () {
            this.counter++;
            if (panel.selection.value == 0 && this.properties.source.value == 1 && this.properties.auto_download.enabled && this.counter == 2 && this.get_count() == 0 && !this.history[this.artist]) {
                this.history[this.artist] = true;
                this.download();
            }
        }
    
      
        this.update = function () {
            this.reset();
            this.using_stub = false;
    
            _.forEach(this.get_files(), function (item) {
                var image = utils.LoadImage(item, this.properties.max_size.value);
                if (image) {
                    this.images.push(image);
                }
            }, this);
    
            if (this.images.length == 0 && this.properties.source.value == 1) {
                var stub_img = fb.GetAlbumArtStub(4);
                if (stub_img) {
                    this.using_stub = true;
                    this.images.push(stub_img);
                }
            }
    
            if (this.images.length) {
                this.blur_img = this.images[0].Clone();
                this.blur_img.StackBlur(120);
            }
    
            if (this.properties.mode.value != 5) {
                this.create_thumbs();
            }
            this.size(true);
            window.Repaint();
        }
    
        
    và 1 phần script đoạn load image đã mod:

    Code:
     
     
            //1. reset stack memory
                this.jsp3_metadb = null;
                _dispose.apply(null, this.images);
                this.images = [];
                this.image = 0;
                this.files = [];
                this.folder='';
                _dispose.apply(null, this.thumbs);
                this.thumbs = [];
          
            //2. checking whether the album/playlist is changed
                if (panel.selection.value == 0) {
                    this.jsp3_metadb = fb.GetNowPlaying();
                 } else {
                    this.jsp3_metadb= fb.GetFocusItem();
                 }
          
                if (!this.jsp3_metadb) {
    
                    var image = utils.LoadImage(this.properties.topCoverArt.value,sizeArt);
                    if (image) {
                            this.images.push(image.Clone());
                            this.files.push(this.properties.topCoverArt.value);
                            image.Dispose();
                    }
                    window.Repaint();
                    return; //nothing ahead, back to routine
                }
              
                try {
                    this.jsp3_album_current = this.jsp3_album_tf1.EvalWithMetadb(this.jsp3_metadb);
                } catch (e) {
                        window.Repaint();
                        return;
                    }
                  
    
                //let try evaluate local art folder(s)
                // reset memory
                this.artist_prv = this.artist; // remember the prv artist
                this.artist = fb.TitleFormat(DEFAULT_ARTIST).EvalWithMetadb(this.jsp3_metadb);
                var pathTrack,pathTrack1,pathTrack2;
                    pathTrack = pathTrack1 = pathTrack2 = this.jsp3_album_tf1.EvalWithMetadb(this.jsp3_metadb); // playing track path
                var spcPathCover = fb.TitleFormat(this.properties.tf_specialCoverPath.value).EvalWithMetadb(this.jsp3_metadb);  // special cover art path
                var sizeArt = this.properties.pxArt.value; //art size capacity in px.
              
              
                if (!_tagged(pathTrack)) {
                    window.Reload(false);
                }
              
                this.folderOnline = _artistFolder(this.artist);
                this.folderLocal = pathTrack +" | "+  spcPathCover ;
              
                if (_.includes(this.properties.tf_subFolder.value, '|')) {
                    var arr = _stringToArray(this.properties.tf_subFolder.value, '|');
                    this.folderLocal = this.folderLocal + '|'+pathTrack+'\\'+arr.join('|'+ pathTrack+'\\');
                    pathTrack = pathTrack.substr(0,pathTrack.lastIndexOf('\\'));
                    this.folderLocal = this.folderLocal + '|'+pathTrack+'\\'+'|'+pathTrack+'\\'+arr.join('|'+pathTrack+'\\');
                }
      
                this.playListOrAlbumIsChanged = false;
              
                if(this.jsp3_album_current == this.jsp3_album) {
                        console.log("JSP3_update cover arts: album path remained as [" + this.jsp3_album+"]" );
                    } else {
                        this.jsp3_album = this.jsp3_album_current;
                        this.playListOrAlbumIsChanged = true;
                    }
              
                pathTrack = this.jsp3_metadb.RawPath;
                pathTrack1 = (pathTrack1.indexOf('youtube') >= 0 ? 'youtube' : (pathTrack1.indexOf('http') >= 0 ? 'radio' : (pathTrack1.indexOf('cdda') >= 0 ? 'audio track' :'unknown')));
             
    
                if (pathTrack.indexOf('http') >= 0 )  {
                    _dispose.apply(null, this.imagesLocal);
                    this.imagesLocal = [];
                    this.filesLocal = [];
              
    
                    var image = utils.LoadImage(this.properties.topCoverArt.value,sizeArt);
                    if (image) {
                            this.topCoverArt_prv = this.properties.topCoverArt.value;
                            this.imagesLocal.push(image.Clone());
                            image.Dispose();
                    }
                }
          
              if (pathTrack.indexOf('http') <0  && this.playListOrAlbumIsChanged) { // local arts are ignored if online streaming.
                console.log ("JSP3_update local cover arts: caching HDD . . . . .");
                var time1 = (new Date()).getTime();
    
                _dispose.apply(null, this.imagesLocal);
                this.imagesLocal = [];
                this.filesLocal = _getFiles(this.folderLocal, this.exts);
          
            
                var image = utils.LoadImage(this.properties.topCoverArt.value,sizeArt);
                if (image) {
                        this.topCoverArt_prv = this.properties.topCoverArt.value;
                        this.imagesLocal.push(image.Clone());
                        image.Dispose();
                }
                  
                // caching, resize local cover arts
              
                if (this.filesLocal.length > 0){
                              
                 
                    var image = null;
                    var letGo = false;
                    var extPosition,
                        file_size, accum_file_size,
                        file,extFile,fileThumb;
                      
                    var accum_file_size = 0;
                    var accum_accum_file_size = 0 ;
                    var size_file_limit_total = this.properties.size_limit.value;
                    var size_file_limit = this.properties.size_limitPerFile.value;
    
                    var CACHE_FOLDER = pathTrack2.substr(0,pathTrack.lastIndexOf('\\')) + "\\{♪_jsvnav_smooth_cache_♪}";
                    if (!utils.IsFolder(CACHE_FOLDER)) utils.CreateFolder(CACHE_FOLDER);
          
                    for (var i = 0; i< this.filesLocal.length; i++) { // welcome to a loop
                        file = this.filesLocal[i];
                        file_size=utils.GetFileSize(file);                 
    
    
                        if (this.properties.resizeArt.enabled) {
                         
                                extPosition=file.lastIndexOf('.');
                                extFile = file.substr((extPosition+1));
                                fileThumb = CACHE_FOLDER +file.substr(file.lastIndexOf('\\')) + "_[thumb_jsp3_cover_art]_"+file.hashCode()+"."+extFile;
                              
                    
                                if (file_size < size_file_limit) {
                                    image = utils.LoadImage(file,sizeArt);
                                } else {
                                    image = utils.LoadImage(fileThumb,sizeArt);
                                    if (!image) {
                                        // checking total file size arts limitation, not including those been cached.
                                        accum_accum_file_size = accum_accum_file_size + file_size; 
                                        if (accum_accum_file_size > size_file_limit_total) {
                                            if (!letGo){
                                                if (IDYES == utils.MessageBox(    "Total files size is "+ Math.round(accum_accum_file_size/(1024*1024)) +" MB."
                                                                +"\nover limitation as user-defined as " + Math.round(size_file_limit_total/(1024*1024)) +" MB."
                                                              
                                                                + "\n(if we go NO, this message will up next time album played"
                                                                + "\nbut the arts will be cached from last abandon, not from beginning)"
                                                              
                                                                + "\n\nDo you want continue ? "
                                                                , // message
                                                                pathTrack2.substr(0,pathTrack.lastIndexOf('\\')), // tittle
                                                                MB_ICONQUESTION + MB_YESNO  // icon question+ buttons Yes+ No
                                                                ))
                                                {
                                               
                                                        letGo = true;
                                                } else {
                                                    
                                                    this.filesLocal= _.take(this.filesLocal, i);
                                                    break; // exit loop
                                                }
                                            }     
                                        } 
    
                                        image = utils.LoadImage(file,sizeArt);
                                        image.SaveAs(fileThumb); // save thumb to HDD
                                        console.log("  - thumb is cached to HDD, save file image thumb as " + fileThumb);
                                    } else     console.log("  - thumb was already saved to HDD, just cache this " + fileThumb);
                                    console.log("\n");
                                }
                                                  
                                if (image) {
                                    this.imagesLocal.push(image.Clone());
                                    image.Dispose();
                                } 
                              
                        } else {
                       
                            accum_file_size = accum_file_size+file_size; // +=
                            if (accum_file_size > size_file_limit_total) {
                                if (!letGo){
                                    if (IDYES == utils.MessageBox(    "Total files size is "+ Math.round(accum_file_size/(1024*1024)) +" MB."
                                                    +"\nover limitation as user-defined as " + Math.round(size_file_limit_total/(1024*1024)) +" MB."
                                                    + "\n\nDo you want continue ? ", // message
                                                    pathTrack2.substr(0,pathTrack.lastIndexOf('\\')), // tittle
                                                    MB_ICONQUESTION + MB_YESNO
                                                    ))
                                    {
                                    
                                            letGo = true;
                                    } else {
                                       
                                        this.filesLocal= _.take(this.filesLocal, i);
                                        break; // exit loop
                                    }
                                }     
                            } 
    
                       
                            this.imagesLocal.push(utils.LoadImage(file));
                        }
                      
                    } 
                }
              
         
            }                 
          
       
          
          
            if (this.properties.source.value == 1 || this.properties.source.value == 4){ // online or both
                    if (this.artist !=  this.artist_prv) console.log ("\nJSP3_update online cover arts: artist changed, from ["+this.artist_prv+"] to ["+this.artist+"]");
                    else console.log (" >> JSP3_update cover arts: artist remained as ["+this.artist+"]");
    
                  
                    if ((this.artist !=  this.artist_prv)  || this.imgMoreDownloded ) { // downloading, caching
                  
                        if (this.artist !=  this.artist_prv)  {
                            this.artist_prv = this.artist;
                         
                            this.playback_time();
                        } else console.log (">>JSP3_update online cover arts: downloading and caching still on progress . . . ");
      
                    
                      
                        _dispose.apply(null, this.imagesOnline);
                        this.imagesOnline = [];
                        this.filesOnline = _getFiles(this.folderOnline, this.exts);
                      
                        if (this.filesOnline.length > 1) {
                            this.default_file = this.folderOnline + this.defaults[this.artist];
                            var tmp = _.indexOf(this.filesOnline, this.default_file);
                            if (tmp > -1) {
                                this.filesOnline.splice(tmp, 1);
                                this.filesOnline.unshift(this.default_file);
                            }
                        }
    
                        if (this.filesOnline.length > 0){
                            for (var i = 0; i< this.filesOnline.length; i++) {
                                console.log(" loading new artist online: " + this.filesOnline[i]);
                                var image = utils.LoadImage(this.filesOnline[i]);
                                if (image) {this.imagesOnline.push(image);}
                            } 
                        }
                    
                       }
                    }
          
    
            //4. clone arts from stack memory [LIFO].
      
            var time1 = (new Date()).getTime();
          
          
            // 4.0 assign 1st/default art's path
             if (utils.IsFile(this.properties.topCoverArt.value)) {
                this.files.push(this.properties.topCoverArt.value);
             } else if (_tagged(this.topCoverArt_prv)){
                this.files.push(this.jsp3_album);
             }
            // 4.1 local
            if (this.imagesLocal.length >0 && this.properties.source.value == 0 ){
               
                    for (var i = 0; i< this.imagesLocal.length; i++) {
                            console.log(i+"> clone cover art : " + this.filesLocal[i]);
                            this.images.push((this.imagesLocal[i]).Clone());
                    
    
                    }
                 
                    this.files = this.files.concat(this.filesLocal);
                }
          
            // 4.2 online
                if (this.imagesOnline.length >0 && this.properties.source.value == 1){
                        for (var i = 0; i< this.imagesOnline.length; i++) {
                            console.log(i+"> clone cover online artist : " + this.filesOnline[i]);
                            this.images.push((this.imagesOnline[i]).Clone());
                        } 
                    
                        this.files = this.files.concat(this.filesOnline);                 
                    }
                  
            // 4.3 combine 2 sources
                if (this.properties.source.value == 4){ //Online source : //local source: update only album/playlist changed or switched AND artist changed (to-do)
                    // local arts
                    for (var i = 0; i< this.imagesLocal.length; i++) {
                        console.log(i+"> clone cover art : " + this.filesLocal[i]);
                        this.images.push((this.imagesLocal[i]).Clone());
                    }
                    //this.files = this.filesLocal.slice();
                    this.files = this.files.concat(this.filesLocal);
                    // online artist arts
              
                    var imagesLenghtIdx = this.images.length;
                    for (var i = 0; i< this.imagesOnline.length; i++) {
                    
                        this.images.push((this.imagesOnline[i]).Clone());
                    } 
                    this.files = this.files.concat(this.filesOnline);
                }
           
            // 4.4 finally, grab EMBEDDED arts as this is per track
                for (var i=0; i < 5; i++){
                    var image = this.jsp3_metadb.GetAlbumArtEmbedded(i);
                    if (image)  {
                        this.images.push((this.create_thumb(image,sizeArt)).Clone());
                        image.Dispose();     
                        this.files.push(this.jsp3_album);
                    } 
                }
              
            // 4.5 and Blur the 1st cover art to set it background
            if ((this.images.length > 0 || imgBgrThumbCover) && this.properties.blur_image.enabled ){
                _dispose.apply(null,this.bckg_img);
                this.bckg_img = null;
                if (this.properties.default_image.enabled && imgBgrThumbCover) this.bckg_img = imgBgrThumbCover.Clone();
                else if (this.images.length > 0) this.bckg_img = this.images[0].Clone();
                if (this.bckg_img) this.bckg_img.StackBlur(40); // 2 (min, clear image)-254 (max, just color)
            }
          
          
            if (this.properties.mode.value != 0) { // 0: no thumb, 1: thumbs
                this.create_thumbs();
            }
            this.size(true); // create 1 overlay image from thumbs, this.image variable
            window.Repaint(); // on show
       
         }
     
    Last edited: 15/3/24
  11. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    2.3.4 chỉ lấy 1 cover art/image để hiển thị, bất kể là image nào:
    cài đặt trong phần [Top control] và [Control Bar], hiển thị cover art.

    Code:
     // vnav Mar24 : take any cover/image, regardless cover type (as indicated by id but failed).
                            var g_img=null;
                            //1. grab embedded arts. if any, this is standard and a 'truly' art
                                for (var i=0; i < 5; i++){
                                    g_img = utils.GetAlbumArtEmbedded(g_metadb.rawpath, i);
                                    if (g_img) break;
                                }
       
                            //2.grab local arts, image filename must be specific (not as WSH ?)
                            //0: Front cover.ext, 1: Back Cover.ext, 3: icon.ext, 2: Disk.ext, 4: Artist.ext
                                if (!g_img ) {
                                    for (var i=0; i < 5; i++){
                                        g_img = utils.GetAlbumArtV2(g_metadb, i);
                                            if (g_img) break;
                                    }
                                }
           
                            /* vnav: if no specfic arts took. search for it, just take any 1st image.
                                this should not cost any time.*/
                             
                                if (!g_img ) {
                                    var pathTrack = fb.TitleFormat("$directory_path(%path%)").EvalWithMetadb(g_metadb);
                                    var imgExt =["jpg","jpeg","png","tif"];
                                    var subFolder =["","\\scan","\\cover","\\art","\\artwork"];
                                    var g_image_list =[];    
                                   
                                    for (var i=0; i<imgExt.length; i++) {
                                        for (var j=0; j<subFolder.length; j++) {
                                            g_image_list = utils.Glob(pathTrack+(subFolder[j]).replace(/^\s+|\s+$/gm,'')+"\\*." + (imgExt[i]).replace(/^\s+|\s+$/gm,'')).toArray();
                                            if (g_image_list.length) {
                                                try {
                                                    g_img= gdi.Image(g_image_list[0]); // any, just 1 image
                                                } catch (e) {g_img=null;}
                                                if (g_img) break; // exit 1 (2nd) loop
                                            }
                                        } //j
                                        if (g_img) break; // exit 1st/primary loop
                                    } //i
                                }  
                           
                            if (!g_img )  {
                                covers[1] = FormatImage(gdi.Image(img_dir+nocover_img), ww, wh, 7, 0, 0);
                            } else {
                               covers[1] = FormatImage(g_img, ww, wh, 7, 0, 0);
                            }
                        }

    tới đây, chúng ta cũng khép lại phần đồ họa vẽ vời của foobar2000.
    hy vọng sẽ sớm có những chủ đề thú vị để chia sẻ tiếp với các bác.

    thân chúc các bác sức khỏe và an lành.
     
    Last edited: 15/3/24
  12. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    load các loại VU Meter khác nhau trong cùng 1 bản dựng/skin:

    còn lưu ý nho nhỏ, trả lời 1 thắc mắc hồi hôm: các skin reel-to-reel, cassette player của Dmitri/iprad cài đặt cùng lúc vào Fb2k-VNAV 2.2 thì sao load được các Analog VU Meter plugin khác nhau (các kiểu đồng hồ, đèn LED).

    để load được các kiểu VU Meter trong cùng 1 bản dựng bằng script, chúng ta cần 2 bước:
    - bước 1 : tạo folder chứa file VU meter tương ứng, trong folder VU Meter gốc (của plugin), chép/move các file vào folder này. ví dụ r2r.
    - bước 2: load VU Meter plugin bằng script code (như Dmitri/iprad) đã làm, nhưng thêm đoạn script chỉ ra folder chứa loại VU Meter VU_Window.GroupName="r2r";
    sau đó load VU Meter plugin VU_Window.LoadSkin(VU_Window.GroupName, val_VUMeterSkin);

    Code:
    //---------- VU METER ----------
    var VU_Meter = new ActiveXObject("VUMeter")
    var VU_Window = VU_Meter.CreateWindow(window.ID);
    var val_VUMeterSkin = window.GetProperty("val_VUMeterSkin","RT909 Blue Small");
    var loadVuMeterRightAway=true; // load skin while skin loading
    VU_Window.GroupName="r2r"; // vnav Feb24 : VU_Window.GroupName is set to default - root folder of VU Analog meter component. otherwise, specify folder contained VU meters
    
    VU_Window.LoadSkin(VU_Window.GroupName, val_VUMeterSkin);
    VU_Window.Layout = 4;
    VU_Window.LockAspectRatio = true;
    vậy thôi,
     
    Last edited: 15/3/24
    Rndce likes this.
  13. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Thân gửi anh em 4rum vnav,
    chào tháng 4 rực rỡ.
    hổm rày Peter chịu khó cập nhật foobar2000 và 1 số cải thiện gần đây tối ưu hóa phân luồng CPU cho core audio play rất đáng giá. bản gần nhất 28/3/2024.
    Fb2k_VNAV_2.2 cũng có vài cập nhật theo, vì giờ đây việc quản lý bộ nhớ và tiến trình không phải là thứ quá . . . đáng quan tâm : cứ thử cho 1 tiến trình ngốn RAM và CPU/chậm chạp (ví dụ: nghe radio FM từ đài xa), fb2k sẽ vẫn điềm nhiên tập trung ngay và luôn, nghiêm túc cho audio play, những việc khác cứ từ từ. cũng vì điều này, các bác nên chuyển qua 1 tác vụ ít chiếm tài nguyên/nhanh (phát nhạc từ HDD) trước khi 'tắt' fb2k nếu không muốn lần khởi động sau chờ mút mùa lệ thủy.

    do bản dựng fb2k_vnav_2.2 đang lưu hành nội bộ hội bạn già xem có lỗi phải gì không nên sẽ báo lại sau, mình giới thiệu vài điểm chú ý để các bác DIY gần xa trong ngoài được rõ trước. đây cũng là mấy điểm được quan tâm nhất:
    - cover arts (phần này đã nêu ở bài trên).
    - lời bái hát (xin phép gọi là lyrics cho nhanh).

    1. cover arts:
    ở loạt bài trên, nổ quá cỡ thợ mộc album nào hầu như cũng có cover lung linh. có bác xem tới lui, lật lên xuống mà tối thui vì album của mấy bác tự sưu tầm và sắp xếp theo chủ đề nên thất lạc luôn cover art. chỉ có 1 cách và 1 nơi luôn có thể có cover arts: Internet.

    như post trước, trong modul JSP3 cover arts (trong tab [now playing]) có mục cho dowload artist arts (1 dạng 'arts' ) từ last.fm. vậy chỉ cần 'nói' các modul khác vô lấy artist arts từ folder định trước (chứa artist arts) sau khi download xong. khỏe re!

    ở đây có vài vấn đề :
    1.1; thông báo cho modul (liên quan cover arts) qua đường nào.
    1.2; có hơn 1 ca sỹ trình diễn 1 bài hát (xem như song ca là tối đa, đồng ca thì . . . thôi).

    1.1 thông báo cho modul khác khi download xong artist arts:
    1.1.1 với modul cùng chung cha mẹ với modul chuyên trách download (là JScript Panel 3):
    - gửi qua đường window.NotifyOthers("JSP3_cover_art_check_artist_art","download done")
    Code:
    this.interval_func = _.bind(function () {
     
         . . .
    
            // checking downloading images
     
            if ((this.properties.source.value == 1 || this.properties.source.value == 4) && this.time % this.intervalTimer == 0) { // check downloaded files every this.intervalTimer sec.
          
                // artist 2
                if (_tagged(this.folderOnline1)) {
                    if ((_getFiles(this.folderOnline1, this.exts).length + _getFiles(this.folderOnline, this.exts).length) != this.imagesOnline.length){
                        this.imgMoreDownloded = true;
                        this.update("Timer interval_func");
                        console.log("Downloading files 2 artists arts: checking . . . in sec : " + this.time);
                        window.NotifyOthers("JSP3_cover_art_check_artist_art","download done"); //notify other panels to update cover arts         
                    } else {
               
                        this.imgMoreDownloded = false;
                    }
                } else {
                    if (_getFiles(this.folderOnline, this.exts).length != this.imagesOnline.length){
                        this.imgMoreDownloded = true;
                        this.update("Timer interval_func");
                        console.log("Downloading files 1 artist art : checking . . . in sec : " + this.time);
                        window.NotifyOthers("JSP3_cover_art_check_artist_art","download done"); //notify other panels to update cover arts         
                    } else {
                
                        this.imgMoreDownloded = false;
                    }
                }
            }
        }, this);
    bên nhận nghe ngóng tín hiệu on_notify_data(name,infor) và cập nhật lại artist arts là xong.
    Code:
    function on_notify_data(name,infor){
        if (name == "JSP3_cover_art_check_artist_art" ) {
            update_album_art();
        } else if (fb.TitleFormat('$if2(%length%,stream)').Eval() == "stream") update_album_art();
    }
    
    1.1.2 với modul không cùng chung cha mẹ với modul chuyên trách download (là JScript Panel 3):
    modul này như 'con ghẻ' khác hệ nên thân phận hẩm hiu, không được thông báo chi cả (không nghe được qua on_notify_data(), hay có nghe mà không hiểu !?), cứ phải canh me lâu lâu ghé mắt nhìn vào folder định trước xem có artist arts nào không để hành sự.
    vậy lâu lâu là nhiu ? cái này tùy vào tốc độ Internet và máy tính nhanh chậm. theo đó, ta sẽ canh chỉnh cho phù hợp.
    hà, căng à, lại phải chia 2 trường hợp:
    a. phát nhạc từ máy tính (nhạc từ HDD): nhạc này thì cứ mỗi bài 1 artist art. loại này, fb2k hỗ trợ rất nhiều callback nhưng cũng chỉ xảy ra ngay khi bài hát phát hay khoảng 60 giây sau, trong lúc thời gian download xong artist arts khó đoán định. ok salem, vậy tự lực cách sinh, lâu lâu vài giây thăm chừng. ta sẽ dùng on_playback_time(), bạn này chăm chỉ và cập nhật đều đều từng giây (1 second). ta sẽ có 1 bộ đếm là refresh_timer_cover. sau 5 giây khi fb2k play 1 bài hát thì vô folder định trước nghía, lần nghía sau lâu gấp đôi (tức là 5,10,20,40 giây từ lúc fb2k phát bài mới, sau đó không có thì khỏi làm gì nữa - mệt và giận rồi, bỏ). các bác có thể tùy biến thời gian này và tránh việc quay lại nghía folder này nếu lần thăm chừng trước đó đã có artist arts rồi.
    Code:
    var refresh_timer_cover = 5;
    function on_playback_time(time) {
     
        if (time <= 2) refresh_timer_cover= 5;
        if (time > 2 && time < 60 && (time % refresh_timer_cover) == 0) {
            cache_covers();
            refresh_timer_cover=refresh_timer_cover*2; // check in 5,10,20,40 secs
        }
    }
    
    a. phát nhạc từ internet: loại này thì chỉ phát '1 bài' - phát liên tục (ví dụ: radio), tức là không có khái niệm new_track, nhưng cũng bài hát này qua bài hát khác.
    tuy nhiên, chính sự thay đổi bài hát/ca sỹ này lại được fb2k theo dõi qua callback on_playback_dynamic_info_track() nên ta có cơ sở để 'dò' tín hiệu. ngoài ra, nhạc stream online thường là loại VBR (ngược lại là CBR), tức là bitrate thay đổi để tối ưu hóa dung lượng file (1 kiểu nén file), đổi lại thì phải mất sức giải nén nhưng đáng là bao. (với nhạc giao hưởng có khi cứ đều đều 1 nhịp thì cứ nén ra flac cũng tiết kiệm được kha khá dung lượng HDD) -> với dạng VBR này, fb2k có callback 'bắt được nhịp' on_playback_dynamic_info(). tuy nhiên, do VBR nhảy như lambada nên ta tránh dùng callback này kẻo fb2k đột quỵ sớm.

    nhưng, lại nhưng, on_playback_dynamic_info_track() cũng được fb2k tác động khi phát nhạc từ máy tính. éo le là, như trên nói, với radio online, fb2k sẽ dành gần như hết nguồn lực để phát nhạc, mà mấy đài xa hay chậm chạp, nên không biết khi nào fb2k mới cho modul làm nhiệm vụ download artist arts vào việc, nên khả năng đếm 5,10, 20 giây là phá sản.
    ơn trời là chính việc triệu hồi on_playback_dynamic_info_track() cũng có độ trễ nhất định, nên hầu như ngay khi callback này được fb2k thi hành thì modul download artist arts cũng xong chuyện.

    vậy là chỉ cần xem nhạc đang phát là online stream hay không ? nếu đúng thì vô cập nhật lại artist arts.
    nếu máy tính mạnh thì khỏi kiểm tra, cứ để calback này thi hành cùng với bộ định thời 5,10,20 giây nêu trên.
    Code:
    function on_playback_dynamic_info_track(type){
        //type ==0 : online continuous streaming, ie. radio
        // type == 1: youtube, local track
        if (fb.TitleFormat('$if2(%length%,stream)').Eval() == "stream") update_album_art();
    }
    
    vậy thôi.
    
     
    Last edited: 31/3/24
  14. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    1.2; có hơn 1 ca sỹ trình diễn 1 bài hát:
    có thể nói, đa số fb2k skin và các bản mod chỉ hỗ trợ 1 ca sỹ/artist, tức là artist arts sẽ ưu tiên cho 1 ca sỹ này và thường là ca sỹ xuất hiện đầu tiên trong tag bài hát. cú pháp meta(artist,0) cho phép ta lấy ra tên ca sỹ thứ 1 trong khi %artist% cho ra kết quả hơi khác và thường là tất cả.
    tệ hơn, tên các artists được để chung, phân cách bởi dấu '&' hay 'and/và' hay '-'. vậy là chạy trời không khỏi nắng.

    nhưng đôi khi artist arts của ca sĩ thứ hai lại có, ca sỹ 1 thì không hay không có hay tên ca sỹ 1 bị lỗi chính tả nên không download được arts nào cả . . . các bác có lý.
    có nhiều cách tiếp cận cho vấn đề này, đơn giản nhất là làm thêm 1 modul download nữa là xong, khi có ca sỹ thứ hai xuất hiện thì đẩy cho hắn. modul thứ hai này thuộc 1 panel riêng biệt nên không lo lắng về xung đột tài nguyên, khỏe re. nhưng có ca sỹ thứ 3,4 . . thì sao !? trường hợp này hiếm nên mình không tính tới (sẽ nhắc tới trong phần download lyrics).

    vậy thôi, trong modul 1 cần kiểm tra xem có ca sỹ thứ 2 không ? nếu có thì gọi modul 2. cơ chế nghe gọi đã đề cập ở trên.

    --> bên gọi : window.NotifyOthers("JSP3_cover_art_check_artist_art2", artist2);

    Code:
       
    . . .     
                    this.artist = fb.TitleFormat(DEFAULT_ARTIST).EvalWithMetadb(this.jsp3_metadb);
                  
                    if (this.artist == "" || !this.artist) { // $meta(artist,0) tittleFormatting does not work well with online stream, as online radio
                        this.artist = fb.TitleFormat("[%artist%]").EvalWithMetadb(this.jsp3_metadb);
                    
                    }
                    if (this.artist =="" || !this.artist) { // radio source, evaluated without metadb
                        this.artist = fb.TitleFormat("[%artist%]").Eval();
                     
                    }
                  
                    if (_tagged(this.artist)){ // if Various Artist, then get the 1st one
                        var checkVA=this.artist.split(",");
                        if (checkVA.length < 2) checkVA=this.artist.split(" and ");
                        if (checkVA.length < 2) checkVA=this.artist.split("&");
                     
                        this.artist = checkVA[0].replace(/^\s+|\s+$/gm,'');
                      
                        if (checkVA.length >=2 && _tagged(checkVA[1])) {
                            var artist2 = checkVA[1].replace(/^\s+|\s+$/gm,'');
                            this.folderOnline1 = this.artistFolder(artist2);
                            window.NotifyOthers("JSP3_cover_art_check_artist_art2", artist2);
                        }
                        // if there is another artist, then . . . we do not think so.
                    } else this.artist ="Variousr Artists"; // still nothing (even local album does not contain tags, including artist), then feed it with a GENERAL name
                    
    --> bên nhận: modul này, như đề cập, không có vẽ vời gì, chỉ là logic nên không cần cấp đất. trong script đính kèm theo bản fb2k_vnav_2.2, mình có giải thích kỹ cách (bước 1) download HTML từ last.fm, sau đó tách/parsing HTML tag để lấy thông tin cần thiết (bước 2) download artist arts.
    Code:
    function on_notify_data(whoCallMe,artist2) {
        if (whoCallMe == "JSP3_cover_art_check_artist_art2" ) {
            if (!_tagged(artist2)) return;
            downloadArt.download_artist_arts_lastfm(artist2);
            console.log("\n\nJSP3_cover_art_check_artist_art"," --> JSP3 download artist art 2 :",artist2);
        }
    }
    vậy thôi
    đơn giản mà hiệu quả.
    với các yêu cầu liên quan đến default cover arts, fb2k_vnav_2.2 đều đã có hỗ trợ sẵn.
     
    Last edited: 31/3/24
  15. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    2. lời bái hát/lyrics:
    vì tiêu chí bản dựng foobar2000 , fb2k_vnav_2.2 là tận dụng Internet nên yêu cầu này cũng hết sức hợp lý.
    bản dựng có sẵn 2 plugin lyrics và làm khá tốt, đặc biệt là các nguồn synced lyrics như karaoke và khai thác database đặc thù nên ưu thế hơn cả Google. mình cũng cài thêm 1 plugin lyric (openlyrics) nhưng kết quả khá khiêm tốn để các bác tham khảo thêm.

    với cả 3 lyrics plugin, không phải lúc nào lyrics cũng có, trong khi nghe mấy bài hát nước ngoài mà không hiểu lời thì khá mất thú vị.

    tiếp cận: dùng Google để tìm lời bài hát. cách này có ưu điểm tận dụng khả năng tuyệt vời của cỗ máy tìm kiếm hạng nặng này, nhưng chúng ta phải tự sync bằng 'cơm' và kết quả không phải lúc nào cũng chuẩn. nhưng có cách nào hơn Google !? mình không biết. hy vọng 1 ngày gần đây, Open AI sẽ mở port và hỗ trợ tìm beat nhạc như app trên smartphone, khả năng sync lyrics sẽ cao hơn.

    có gì dùng đó vậy !

    từng đề cập, việc tìm bio ca sỹ/review bài hát, đánh vật với HTML tag để tìm thông tin liên quan - hữu ích như mèo vờn chuột. tìm lyrics từ Google cũng không khác gì.

    điểm thuận lợi:
    • HTML là 1 dạng dữ liệu gần như có cấu trúc, các thông tin cần tìm nằm trong khối <body . . . . >. . .</body>, thông tin cần tìm sẽ thường đi cùng với 1 vài tag nào đó với những tên/ký tự đặc thù, thường là tag 'chung chung' như <span calss=""> hay <div calss="">. định vị được các HTML tag này, chúng ta đã có thông tin cần tìm.
    • Tên bài hát, nếu không nhúng tag thì thường là tên file : xem như tên bài hát lúc nào cũng có, trừ 1 vài trường hợp quá chung chung như track1, track2 . . .
    • Google có thể thông minh 'tự hiểu' và diễn giải đúng từ khóa tìm kiếm nếu tên bài hát không chuẩn, sai chính tả.
    • Google hỗ trợ dịch khi search trực tiếp trên google site (các bác right click trên control bar, chọn Google lyrics search qua context menu).
    • Google hỗ trợ nhiều từ khóa boolean, kiểu tìm kiếm. ví dụ, có thể kết hợp search 1 lúc các từ khóa nếu dùng OR, hay AND và NOT, loại trừ . . . hay tìm tương tự allintext, tìm trong 1 site nào đó . . .

    Tuy nhiên, không dám chắc các tag này có thay tên đổi họ vào ngày mai. vì vậy, trong script, mình cố gắng giải thích rõ cách tìm các tag này và xử lý chúng đơn giản nhất để lấy thông tin. các cao thủ hay dụng RegEx hay thư viện có sẵn (ví dụ. lodash) nhưng khá khó hiểu nên mình chỉ dùng duy nhất cách 'chẻ' các tag ra Array và xử lý tuần tự, dễ coding, dễ sửa, dễ debug.
    mình để nguyên code ở đây để các bác dễ tham khảo. modul này cài đặt trong mục [Library filter].

    Hiện giờ, modul chỉ có hỗ trợ tìm kiếm lyrics từ google (mình gọi là HTML Parser engine), các bác có thể thêm các engine tìm lyrics từ các site khác (có thể có site cần đăng ký, nhưng sẽ sync với bài hát, chuẩn chỉnh hơn). cách load các engine này cũng đã đề cập trong script.

    Google HTML Parser engine hỗ trợ title có nhiều thông tin (tên bài hát + tên ca sỹ), phân cách bởi ký tự đặc biệt '(', hay '{' hay '-' (thông tin các bác đưa thì dấu - dùng khá nhiều).

    vậy thôi,

    Code:
    // ==PREPROCESSOR==
    // @name "Lyrics download manager"
    // @author "vnav - based on JSP3"
    // @import "%fb2k_profile_path%skins\tech\scripts\jsp3_lib\helpers.txt" // Marc2k3's supporting functions
    // @import "%fb2k_profile_path%skins\tech\scripts\jsp3_lib\common.js" // Marc2k3's lib
    // ==/PREPROCESSOR==
    
    // vnav -->Lyrics searching for personal purpose only.
    
    
    /* vnav Mar24:     The 'structured alike' HTML file returned by Google searching for lyrics as follows.
                    As these HTML tag(s) may vary from time to time,
                    revive it/them accordingly if Google changes [it/them] to keep lyrics searching worked.
     
            . . .
        1. var tagTitle -->  '<div class=\"PZPZlf';
            <div class="PZPZlf ssJ7i B5dxMb" aria-level="2" data-attrid="title" role="heading">Old Devil Moon</div>  </div>
            . . .
        2. var tagHTML ---> '<span jsname=\"YS01Ge\">';
            <span jsname="YS01Ge">I look at you and suddenly</span><br aria-hidden="true">
            <span jsname="YS01Ge">Something in your eyes I see</span><br aria-hidden="true">
            <span jsname="YS01Ge">Soon begins bewitching me</span><br aria-hidden="true">
            . . .
        3. var tagSource --> '<div class=\"f41I7'; OR  '<div class=\"j04ED';
            <div class="f41I7 ai4HXb j04ED">Nguồn tin:&nbsp;
            <span class="S4TQId">
            <a href="https://lyrics.lyricfind.com/" . . . >LyricFind</a></span></div>
    
            <div class="f41I7 ai4HXb" data-ved="2ahUKEw . . . ">                           <<-- sometimes not existing
    
        4.var tagCopyright ---> '<div class=\"auw0zb\">';
            <div class="auw0zb">Nhạc sĩ: Burton Lane /  E. Y. Harburg</div>
            <div class="auw0zb">Lời bài hát Old Devil Moon © Royalty Network, Warner Chappell Music, Inc, Wixen Music Publishing</div>
            . . .
    */
    
    var WshShell = new ActiveXObject("WScript.Shell");
    var cfgPath = fb.ProfilePath + 'skins\\tech\\misc\\google_lyrics_tag.ini';
    var tagHTML = utils.ReadINI(cfgPath, 'google_lyrics_tag', 'tag_html_line_lyric');
        tagHTML = tagHTML.replace(/^\s+|\s+$/gm,'');
    
    
    
    if (!_tagged(tagHTML)) tagHTML='<span jsname=\"YS01Ge';
    var tagSource= '<div class=\"f41I7';
    var tagSource1 = '<div class=\"j04ED';
    var tagCopyright = '<div class=\"auw0zb';
    var tagTitle = '<div class=\"PZPZlf';
    
    
    
    //var imgBckg = utils.LoadImage(fb.ProfilePath + 'skins\\tech\\images\\google_lyrics.jpg');
     
    var fontLyrics = utils.CheckFont("Segoe UI") ?  "Segoe UI" : "Trebuchet MS";
    var lyrics=[];
    var offset = 1;
    var text_height = 0;
    var colour_string = '';
    var box = {    x : 5, y : 5, w : 0, h : 0, }
    var layout = utils.CreateTextLayout('fb2k_vnav_2.2', fontLyrics , 20);
    
    var downloadLyrics = new _downloadLyrics();
    downloadLyrics.download_lyrics_manager('dnuof ton sciryL ◀︎');
    
    
    
    function on_paint(gr) {
        gr.Clear(RGB(18, 68, 101));
      
        //try {gr.DrawImage(imgBckg,0,0,window.Width,window.Height,0,0,imgBckg.Width,imgBckg.Height); } catch (e) {}
        gr.DrawRectangle(box.x-3, box.y-3, box.w , box.h, 1, RGB(20, 20, 20));
        var text ="";     
        for (var i = 0; i< lyrics.length; i++) text = text + "\n" + lyrics[i];
        layout = utils.CreateTextLayout(text, fontLyrics, 20);
        on_size();
        try { gr.WriteTextLayout(layout, colour_string, box.x, box.y, box.w, box.h, offset);} catch (e)
            { gr.WriteText((" ▶ Lyrics not found due to following error:\nInternet is disconnected or\n"+  e.message),fontLyrics,RGB(200,200,200),2,2,window.Width,window.Height,2,2,3,2);}
    }
    
    
    function _downloadLyrics() {
      
        this.task_id;
        this.title=""; // title maybe a song title, or a rich infomation title as song title+artist+tracknum+... 
        this.song="";  // song title that  a plugin treated as key word searching for lyrics. usually, song == title
        this.songs=[];
        this.songIdx=0;
      
        this.download_lyrics_manager = function (title) {
            // reset global var
            lyrics=[];
          
            // reset loccal var
            this.task_id=null;
            //delimiter/separator in case titile as [01 - song title - artist]
            if     (title.indexOf('(') > 0 ) { title=title.replace('(','-'); title=title.replace(')','');     }
            if     (title.indexOf('{') > 0 ) { title=title.replace('{','-'); title=title.replace('}','');     }
            if     (title.indexOf('=') > 0 ) { title=title.replace('=','-');                                }
          
            this.title = title;
            this.songs = this.title.split('-');
          
            this.song = this.songs[1];
            this.songIdx=0; //0-indexed array
      
            /* load HTML Parser engine : vnav Mar24
                actually, each engine owns a [humble] HTML tag parser to translate its rich content to human-being text.
                if any, any DIYers' idea or AI applied is more than welcomed.   
              
                - can let user decide which engine to be loaded via contextmenu.
                - can load engine script from file via eVal() function as follwing sample pseudo-script:
                      
                        var fileEngine = "skins\\tech\\scripts\\googleLyricsHTMLParser.txt"; // 'UTF-8 with BOM' encoded text file.
                        var txtLines = utils.ReadTextFile(fb.ProfilePath + fileEngine).split("\n");
                      
                        var parserEngine = "";
                        for (var i = 0; i < txtLines.length; i++) {
                            if (txtLines[i].indexOf("//") < 0) parserEngine = parserEngine + txtLines[i] + "\n";
                        }
                        try { eVal(parserEngine); } catch (e) {console.log("Error loading script, check out error " + e.message);}
              
                - or engine can load per built-in methods as in this modul.
    
            */
            //1 . Google plugin HTML parser.  this supports OR/AND/NOT boolean
            // lyrics sites under 'public-domain !?' (public accessed)  as MusixMatch, LyricFind . . . let Google do search. 
          
            this.plugin_google_lyrics_loading(this.title);
          
            //2. . . . AZlyrics plugin
        }
      
        this.plugin_google_lyrics_loading = function (song) {
            this.song = song;
            if ( !_tagged(this.song)) return;
          
            var header = JSON.stringify({
                                'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0',
                                'Referer' : 'https://www.google.com'
                                });
            try {
                var url = "https://www.google.com/search?q=lyrics+loi+bai+hat:"+encodeURIComponent(this.song);
                this.task_id = utils.HTTPRequestAsync(window.ID, 0, url, header);
            } catch (e) {console.log("google search lyrics error : ",e);}
        }
      
    
        this.http_request_done = function (id, success, response_text, status,response_headers) {
            lyrics[0]= " ▶ Searching for . . . "+ this.song;
            lyrics[1]= " ▶ Google : Lyrics not found";
                  
            if (success && this.task_id == id) {
                //utils.ShowPopupMessage(response_text);
                try {
                        var rawLyrics = response_text.split(tagHTML);
                        if (rawLyrics.length >1){
    
                            var songProperty1 = songProperty2 = txtArray= [];
                            var songTitle = source = songwriter = copyright ="";
                          
                            // title
                            songProperty1 = response_text.split(tagTitle);
                            if (songProperty1.length >1){
                                songTitle= songProperty1[1].substring(0, songProperty1[1].indexOf("<"));
                                songTitle= songTitle.substring(1+songTitle.indexOf(">")).replace(/^\s+|\s+$/gm,'').replace('&nbsp;','');
                            }
                            lyrics[1]=" ▶ Google : " + songTitle;
                            //1. lyrisc
                          
                            lyrics[2] = "  - - - - - - - - - ༺☆༻  - - - - - - - - -  ";
                            var ln="";
                            for (var i = 1; i< rawLyrics.length; i++){
                                ln = (rawLyrics[i].indexOf("<div") > 0 || rawLyrics[i].indexOf("class") > 0)  ? "\n" : "" ;
                                lyrics[i+2] = rawLyrics[i].substring(0, rawLyrics[i].indexOf("</span>")).replace(/^\s+|\s+$/gm,'').replace('&nbsp;','').replace('&amp;','&');
                                lyrics[i+2] = lyrics[i+2].substring(1+lyrics[i+2].indexOf(">")).replace(/^\s+|\s+$/gm,'') + ln;
                            }
    
                            lyrics[i+2]=" - - - - - - - - - - - - - - -  - - - - - - - ";
                          
                          
                            // title, source, songwriter, copyright //
                          
                            songProperty1 = response_text.split(tagSource);
                            songProperty1 = (songProperty1.length <2) ? response_text.split(tagSource1) : songProperty1; // try other HTML tag
                          
                            if (songProperty1.length >1){
                              
                                songProperty2 = songProperty1.length > 2 ? songProperty1[2].split(tagCopyright) : songProperty1[1].split(tagCopyright);
                              
                                //--> lyrics web source from 2nd array element
                                songProperty1[1]= songProperty1[1].substring(0, songProperty1[1].indexOf("</div>")).replace(/^\s+|\s+$/gm,'').replace('&nbsp;','');
                                txtArray = songProperty1[1].split(">");
                                for (var j=0; j< txtArray.length; j++)
                                source = source+ txtArray[j].substring(0, txtArray[j].indexOf("<")).replace(/^\s+|\s+$/gm,'');
                          
                                //---> the last array element contains the whole html file -> cut to release memory
                                songProperty2[songProperty2.length-1] = songProperty2[songProperty2.length-1].substring(0, songProperty2[songProperty2.length-1].indexOf("</div>")).replace(/^\s+|\s+$/gm,'').replace('&nbsp;','');
                                // ---> songwriter comes from 1st array element
                                songwriter = songProperty2.length > 0 ? songProperty2[1].replace('</div>','').replace('&nbsp;','') : songwriter;
                                songwriter = songwriter.substring(1+songwriter.indexOf(">")).replace(/^\s+|\s+$/gm,'');
    
                                // ---> copyright comes from lst/2nd array element
                                copyright = songProperty2.length > 1 ? songProperty2[2] : copyright;
                                copyright = copyright.substring(1+copyright.indexOf(">")).replace(/^\s+|\s+$/gm,'');
                              
                                source =  _tagged(source) ? " -> " + source : "";
                                songwriter =  _tagged(songwriter) ? " -> " + songwriter : "";
                                copyright =  _tagged(copyright) ? " -> " + copyright : "";
                                // add to text
                                lyrics[i+3] = "/* "+ songTitle + source + songwriter + copyright +" */";
    
                            } 
                        }
                  
                  
                    refresh(); 
                    print_lyrics();
                    offset = 0;
                    window.Repaint();
          
                } catch(e) {MessageBox(e.message,"Google lyrics parsing error");}
            }
          
            if ( 
                    (lyrics[1] == " ▶ Google : Lyrics not found"))
                {
                // attemp more times, if any title is multi-information                       
                if (this.songs.length > 1 && this.songIdx < this.songs.length) {
                    try {var song = this.songs[this.songIdx].replace(/^\s+|\s+$/gm,''); } catch (e) {}
                    this.songIdx++;
                    this.plugin_google_lyrics_loading(song);
                }
            }
        }
    
    }
    
    function on_http_request_done(task_id, success, response_text, status,response_headers) {
        downloadLyrics.http_request_done(task_id, success, response_text);
    }
    
    function on_notify_data(name,info){
        if (fb.TitleFormat('$if2(%length%,stream)').Eval() == "stream") {
            var title = fb.TitleFormat("$if2(%title%,%filename%)").Eval();
            downloadLyrics.download_lyrics_manager(title);
        }
    }
    
    //online streaming as radio changes title and Fb2k calls this callback
    function on_playback_dynamic_info_track_(type) {
        if (fb.TitleFormat('$if2(%length%,stream)').Eval() == "stream") {
            var title = fb.TitleFormat("$if2(%title%,%filename%)").Eval();
            downloadLyrics.download_lyrics_manager(title);
        }
    }
    
    function on_playback_new_track(handle){
      
        var g_metadb = fb.GetNowPlaying();
        if (!g_metadb) return;
        var title = "";     
        try {
            if (fb.TitleFormat('$if2(%length%,stream)').Eval() == "stream") title = fb.TitleFormat("$if2(%title%,%filename%)").Eval();
            else title = fb.TitleFormat("$if2(%title%,%filename%)").EvalWithMetadb(g_metadb);
        } catch (e) {return; }
              
        downloadLyrics.download_lyrics_manager(title);
          
    }
    
    function print_lyrics(){
            if (lyrics.length >1){
                for (var i = 0; i< lyrics.length; i++) console.log(lyrics[i]);
            } else {
                console.log("-> Google : Lyrics not found");
            }
    }
    
    function on_mouse_rbtn_down(x, y) { ShiftDown = utils.IsKeyPressed(0x10) ? true : false; }
    
    function on_mouse_rbtn_up(x, y) {
      
        var _menu = window.CreatePopupMenu();
        _menu.AppendMenuItem(MF_STRING, 1, "Google searching for lyrics");
      
        if (ShiftDown) {
        _menu.AppendMenuItem(MF_SEPARATOR, 0, 0);
        //_menu.AppendMenuItem(MF_STRING, 10, "Properties");
        _menu.AppendMenuItem(MF_STRING, 11, "Configure...");
        }
    
        idx = _menu.TrackPopupMenu(x, y);
        switch (idx) {
        case 1:
                try { var defaultVal = fb.TitleFormat("$if2(%title%,%filename%)").EvalWithMetadb(fb.GetNowPlaying());
                } catch (e) {}
                defaultVal = defaultVal === "undefined" || !defaultVal ? "" : defaultVal;
                var nl = "\" + chr(10) + \"";
                var title =">> Google lyrics searching ["+ window.Name+"]";
                var msg = "Google searching for lyrics may always get text, but not always as exptected."
                    msg = msg +nl+nl+"Enter song tiltle for Google:";
    
                    try {
                                      
                        var string = input_box(msg,defaultVal , title);
                        if (string) downloadLyrics.download_lyrics_manager(string.replace(/^\s+|\s+$/gm,''));
                  
                    } catch (e) {MessageBox(e,"Error : Input Google searching");}
          
            window.Repaint();
            break;
        case 10:
            window.ShowProperties();
            break;
        case 11:
            window.ShowConfigure();
            break;
        }
        _menu.Dispose();
        return true
    }
    
    
    
    function refresh() {
        var colours = [];
        var start = 0;
        if (lyrics.length <1) return;
        lyrics.forEach(function(word, i) {
            // length of word plus following space
            var len = word.length + 1;
    
            colours.push({
                // when using an array, Start and Length are mandatory
                Start : start,
                Length : len,
                Colour : RGB(200, 200, 200),
                //Colour : RGB(Math.random() * 200, Math.random() * 200, Math.random() * 200),
            });
    
            // increment start position for next word
            start += len;
        });
        colour_string = JSON.stringify(colours);
    }
    
    function MessageBox(txt,title){
        if  (txt== "") return;
        if  (title== "") title="FB2k_VNAV"
        var button_type = 0x0;
        var icon_type = 0x20;
    
        var btn = WshShell.Popup(txt, 0, title, button_type + icon_type);
    }
    
    function input_box(p_text, d_text, w_title){
        vbe = new ActiveXObject('ScriptControl');
        vbe.Language = 'VBScript';
        return vbe.eval( "InputBox(\"" + p_text + "\",\"" + w_title + "\",\"" + d_text + "\")");
    }
    
    function on_mouse_wheel(step) {
        if (text_height < box.h) return;
        offset += step * 60;
        if (offset > 0) offset = 0;
        else if (offset < box.h - text_height) offset = box.h - text_height;
        window.Repaint();
    }
    
    function on_size() {
        box.w = window.Width -box.x;
        box.h = window.Height -box.y;
        text_height = layout.CalcTextHeight(box.w);
        if (text_height < box.h) offset = 0;
        else if (offset < box.h - text_height) offset = box.h - text_height;
    }
    
    
    //EOF vnav 28Mar24
    
    
     
    Last edited: 31/3/24
  16. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    giờ chót, cập nhật thêm 2 chi tiết nhỏ cho bác xa thật là xa:

    1. có tiếng nhạc báo khi có sự kiện:
    dùng quyền trợ giúp từ 1 ActiveX của OS Windows (không dùng foobar2000), activeX này sẽ play 1 file nhạc (bác có thể tìm trong thư mục của windows, có sẵn nhiều file -> từ khóa *.wav). đoạn script sau đã được thêm vào modul theo dõi sự kiện và quy đổi ngày âm-dương lịch của fb2k_vnav_2.2; khi khởi động foobar2000 hay khi chuyển ngày, foobar2000 sẽ tự make noise nếu có sự kiện. nếu sau này đổi ý thì bác xóa file wav kia đi. script đã bẫy lỗi nên không crash. bác có thể tự DIY 1 mục trong context menu cho phép tùy chọn bật/tắt âm thanh.
    Code:
    var soundFile = fb.ProfilePath +"skins\\tech\\misc\\APPLAUSE.WAV"; // @ Microsoft
    function makeNoise(soundFile) {
        try {
            WMPlayer = new ActiveXObject("WMPlayer.OCX");
            WMPlayer.URL = soundFile;
            WMPlayer.Controls.Play();
        } catch (e) { fb.trace(e.message); }
    }
    2. các folder chứa cover arts. (art folder)
    post trước có đề cập các folder có thể chứa cover arts (art, arts, artwork, folder, image, . . . ) mà lại để trong script, nhiều modul khác nhau nên khó sửa, không đồng nhất.

    trừ file ext (png, jpg,tif . . . ) vì khác nhau theo script plugin (JSP3, WSH, SMP) vẫn hard code theo script, các art folder được để trong 1 file text. các bác có thể sửa trực tiếp file này.
    - path:
    Code:
    [foobar2000]\skins\tech\misc\art_sub_folder.ini
    - nội dung file:
    Code:
    // vnav Mar24: local SUB folders of cover arts
    // these settings are applicable for most cover arts moduls, deployed within Fb2k-vnav 2.2 ++
    
    //    Enter sub art folders of playing track's path.
    //     These are up/down ONE folder accordingly.
    //    ie : we have sub folder art as [cover]
    //
    //        > playing track is D:\Music\path1\album1\abc.flac
    //        > up 1 folder :  D:\Music\path1\cover\
    //        > down 1 folder :  D:\Music\path1\album1\cover\
    
    
    cover
    covers
    scan
    scans
    artwork
    artworks
    art
    image
    img
    book
    booklet
    folder
    - script lấy các art folder:
    Code:
    var imgExt =["jpg","jpeg","png","tif"];
    var artFile = fb.ProfilePath + "skins\\tech\\misc\\art_sub_folder.ini";
    var text = utils.ReadTextFile(artFile);
    var lines = text.split("\n");
    var subFolder = [];
    subFolder[0] ="";
    var lineN = "";
    for (var i = 0; i < lines.length; i++) {
        lineN = lines[i].replace(/^\s+|\s+$/gm,'');
        if (lineN.indexOf("//") <0 && lineN != "") {
                subFolder.push(("\\"+lineN));
        }
    }
    subFolder.push("\\{♪_jsvnav_smooth_cache_♪}");
    if (subFolder.length == 2) subFolder = ["","\\cover","\\covers","\\scan","\\scans","\\artwork","\\artworks","\\art","\\image","\\img","\\book","\\folder","\\{♪_jsvnav_smooth_cache_♪}"];
    // -
    
    cuối cùng, folder art lưu artist arts download từ last.fm
    Code:
    var js_data = fb.ProfilePath + "js_data\\artists\\"; // JSP3 cover art last.fm artist arts
    var yttm = fb.ProfilePath + "yttm\\art_img\\"; // SMP Youtube Track manager or Biography artist arts
    vậy thôi,
    chúc các bác vui
     
    Last edited: 31/3/24
    quangng and Scorpio like this.
  17. Scorpio

    Scorpio Moderator

    Joined:
    2/12/05
    Messages:
    7.233
    Likes Received:
    3.297
    Location:
    VNAV
    Khi làm tag album mình hay xuất luôn coverart album ra file folder.jpg nằm luôn trong thư mục của album
    Sau dùng nhiều software khác cũng tiện
     
    viking likes this.
  18. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    vâng anh, trong khả năng mang niềm vui đến với anh em gần xa thôi ạ.
     
    thienphuc74 and Scorpio like this.
  19. thienphuc74

    thienphuc74 Advanced Member

    Joined:
    4/9/15
    Messages:
    112
    Likes Received:
    13
    MÌNH ĐÃ CÀI UPnP MediaRenderer Output.fb2k-component TRONG FB ĐÃ NHẬN DIỆN ĐƯỢC THIẾT BỊ LÀ SD-9 NHƯNG KHI CHỌN SD-9 THÌ NHẠC KO PHÁT VÀ BÁO LỖI NHƯ HÌNH
     

    Attached Files:

    viking likes this.
  20. thienphuc74

    thienphuc74 Advanced Member

    Joined:
    4/9/15
    Messages:
    112
    Likes Received:
    13
    Unrecoverable playback error: Transition not allowed
     
  21. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    mình không rành lĩnh vực này bạn.
    nhân tiện 1 bác giới thiệu Dmitri mới ra plugin giả lập DAC R2R, các bác tham khảo theo link sau và cân nhắc cẩn trọng khi cài đặt vì ransomware mã hóa đang hot trend.
    cá nhân mình mù tịt VST và config nên không thử.

    https://reference-audio-analyzer.pro/en/faq-foobar2000-nos-r2r-simulator.php#gsc.tab=0

    P/s. Khi download file nhạc, các bác lưu ý file *.db kèm theo. đây là file hay đi theo image trên Windows nhưng hình như cũng bị lợi dụng để lại back door.
     
    Last edited: 8/4/24
    thienphuc74 likes this.
  22. thienphuc74

    thienphuc74 Advanced Member

    Joined:
    4/9/15
    Messages:
    112
    Likes Received:
    13
    đã thử nghe âm rộng hơn . rất hay Bác ạ. THANKS!

    upload_2024-4-8_18-36-53.png
     
    viking likes this.
  23. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    vâng anh.
    có anh đi trước dò đường thì các bác khác thắc mắc cũng tự tin.
    xưa có plugin tube và cũng được quan tâm kha khá, đánh giá theo quan điểm của nhiều người.
    anh nhớ bật Windows defender vì plugin này không nằm trong trang foobar2000.org chính thức ( nhóm plugin của bên thứ ba) và cũng khá mới.
     
    Last edited: 9/4/24
    thienphuc74 likes this.
  24. thienphuc74

    thienphuc74 Advanced Member

    Joined:
    4/9/15
    Messages:
    112
    Likes Received:
    13
    KHI CHẠY plugin giả lập DAC R2R NÓ UP FILE NHẠC LÊN
    upload_2024-4-10_14-29-7.png
     
    viking likes this.
  25. viking

    viking Advanced Member

    Joined:
    31/7/09
    Messages:
    328
    Likes Received:
    291
    Thân gửi anh em 4rum vnav,
    Bản dựng foobar2000 - vnav V2.2 mới nhất đã có tại đây: ( 858 MB)
    link MediaFire : https://www.mediafire.com/file/lup6...0_2.2_21May24_32bit__tech_vnav_2.2.0.rar/file

    Có điểm gì mới :
    1. về hình thức/tính năng:
    - tự tìm kiếm lời bài hát (lyrics) từ Google. có hỗ trợ nhập tựa bài hát để tìm. không syn với beat.
    - duyệt thư viện full-screen.
    - biến cover art thành CD/LP xoay xoay cho vui mắt + kệ đựng LP (bản quyền các tác giả ban đầu có trong script)

    hỗ trợ tìm kiếm tựa bài hát dựa trên beat (như ứng dụng shazam - giải thuật https://www.ee.columbia.edu/~dpwe/papers/Wang03-shazam.pdf) theo yêu cầu các bác: do shazam chỉ cho free 5 bài hát và hỗ trợ kỹ thuật chậm nên mình bỏ qua. hiện tại chưa tìm được cách tối ưu nên cơ bản anh em vẫn phải mò thủ công: chương trình sẽ hiện các image, hy vọng có back cover chứa tên bài hát để nhập vào phần tìm kiếm lyrics.

    2. kỹ thuật:
    - cập nhật core lên foobar2000 2.2 beta ngày 17/5/2024.
    - cập nhật JSPanel 3 lên 3.5.1 ngày 17/5/2024.
    - cập nhật youtube lên 3.9.1 ngày 6/5/2024
    - tinh chỉnh vài bugs.


    Bản này được test trên màn hình 1920 x 1080, scale 150%. với các màn hình độ phân giải cao hơn, các bác tham khảo thông tin sau giúp (1 bác đưa, mình không có điều kiện test):
    https://www.quadraphonicquad.com/forums/threads/foobar2000-on-4k-display.29342/.

    Đã test trên máy cấu hình tương đương:
    Windows 11,
    CPU: AMD 7, RAM 12 GB
    Direct X: 12

    - Để hiển thị đúng các biểu tượng trong control bar (phía dưới), thông tin thời gian audio track (phía trên), các bác cân nhắc cài các font trong thư mục 'foobar. . . /skin/tech fonts'
    - Để MilkDrop visualization hoạt động: vui lòng chỏ đường dẫn (trong phần reference) đến đúng set milkdrop (trong thư mục milkdrop_visualization).
    - Sau 1 thời gian, các artist arts (hình ca sỹ) có thể được download quá nhiều làm tăng dung lượng lưu trữ ở các thư mục js_data\artists ; yttm\art_img và file wavecache.db (chứa data các file nhạc của plugin foo_wave_seekbar.dll) => xem xét xóa bớt file nếu đã thay đổi thư viện nhạc (bớt file nhạc khỏi thư viện).

    chúc các bác sức khỏe và luôn yêu đời.

    lưu ý: Foobar2000 và các plugin, chương trình đình kèm thuộc bản quyền của các nhà phát triển tương ứng. mình đã cẩn thận quét virus và xem xét nguồn nhưng mình và mạng nghe nhìn Việt nam (vnan.vn) không chịu trách nhiệm với các rủi ro liên quan (nếu có).
     

    Attached Files:

    Last edited: 21/5/24
Tags:

Share This Page

Loading...