├── res ├── 00.jpg ├── 01.jpg ├── 02.jpg ├── tv.gif ├── icon.png ├── next.png ├── pause.png ├── play.png ├── sound.png ├── stop.png ├── previous.png ├── version.png ├── INFO ├── THANKS ├── Res.qrc └── DATA ├── BiliLocal.ico ├── README.md ├── .gitignore ├── src ├── Jump.h ├── Editor.h ├── Shield.h ├── Plugin.h ├── Local.h ├── Post.h ├── Graphic.h ├── Info.h ├── Menu.h ├── Search.h ├── Jump.cpp ├── Plugin.cpp ├── Render.h ├── APlayer.h ├── List.h ├── Danmaku.h ├── Interface.h ├── Shield.cpp ├── Load.h ├── Utils.h ├── Config.h ├── Local.cpp ├── Post.cpp ├── Utils.cpp ├── Info.cpp ├── Graphic.cpp ├── List.cpp ├── Danmaku.cpp ├── Menu.cpp ├── Search.cpp ├── Editor.cpp └── APlayer.cpp └── BiliLocal.pro /res/00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/00.jpg -------------------------------------------------------------------------------- /res/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/01.jpg -------------------------------------------------------------------------------- /res/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/02.jpg -------------------------------------------------------------------------------- /res/tv.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/tv.gif -------------------------------------------------------------------------------- /BiliLocal.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/BiliLocal.ico -------------------------------------------------------------------------------- /res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/icon.png -------------------------------------------------------------------------------- /res/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/next.png -------------------------------------------------------------------------------- /res/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/pause.png -------------------------------------------------------------------------------- /res/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/play.png -------------------------------------------------------------------------------- /res/sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/sound.png -------------------------------------------------------------------------------- /res/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/stop.png -------------------------------------------------------------------------------- /res/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/previous.png -------------------------------------------------------------------------------- /res/version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esterTion/BiliLocal/HEAD/res/version.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BiliLocal 2 | ========= 3 | 4 | Add danmaku to local videos 5 | 6 | ![W](res/00.jpg) 7 | 8 | ![L](res/01.jpg) 9 | 10 | ![W](res/02.jpg) 11 | -------------------------------------------------------------------------------- /res/INFO: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "0.4.2", 3 | "String": "[2015/2/18]\nVer_0.4.2\n增加纯弹幕透明模式\n增加A站番剧/C站支持\n大量问题修复", 4 | "Url": "http://tieba.baidu.com/p/3591706098" 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.user 4 | *.sh 5 | *.qm 6 | *.sln 7 | *.sdf 8 | *.suo 9 | *.vcxproj 10 | *.rc 11 | *.filters 12 | *.opensdf 13 | *.psd 14 | *.bak 15 | bin 16 | android -------------------------------------------------------------------------------- /res/THANKS: -------------------------------------------------------------------------------- 1 | 感谢以下人员对BiliLocal做出的贡献 2 | 3 | Chaserhkj: 4 | 搜索窗口 5 | 界面结构调整 6 | 7 | 那么开始零崎吧(zhengdanwei): 8 | Mac OS版本编译 9 | 弹幕发送功能 10 | 11 | 云娘_PAPA本命: 12 | 测试 13 | 使用说明 14 | 15 | hungkie(samlls): 16 | 阻止屏幕变暗或关闭 17 | 18 | BiliLocal使用了以下库和插件 19 | 20 | VLC - Copyright (C) 1998-2013 VLC authors and VideoLAN 21 | FFmpeg - Copyright (C) 2000-2013 the FFmpeg developers 22 | Qt5 - Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 23 | OpenSSL - Copyright (C) 1998-2013 The OpenSSL Project. -------------------------------------------------------------------------------- /res/Res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | tv.gif 4 | pause.png 5 | play.png 6 | stop.png 7 | version.png 8 | icon.png 9 | sound.png 10 | next.png 11 | previous.png 12 | 13 | 14 | COPYING 15 | THANKS 16 | DATA 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Jump.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Jump.h 6 | * Time: 2013/04/22 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef JUMP_H 28 | #define JUMP_H 29 | 30 | #include 31 | #include 32 | 33 | class Jump:public QWidget 34 | { 35 | Q_OBJECT 36 | public: 37 | Jump(QWidget *parent); 38 | 39 | private: 40 | QLineEdit * fileL; 41 | QPushButton * jumpB; 42 | }; 43 | 44 | #endif // JUMP_H 45 | -------------------------------------------------------------------------------- /src/Editor.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Editor.h 6 | * Time: 2013/06/30 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef EDITOR_H 28 | #define EDITOR_H 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | class Editor:public QDialog 36 | { 37 | Q_OBJECT 38 | public: 39 | static void exec(QWidget *parent=0); 40 | 41 | private: 42 | QWidget *list; 43 | QWidget *pool; 44 | explicit Editor(QWidget *parent=0); 45 | }; 46 | 47 | #endif // EDITOR_H 48 | -------------------------------------------------------------------------------- /src/Shield.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Shield.h 6 | * Time: 2013/05/20 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef SHIELD_H 28 | #define SHIELD_H 29 | 30 | #include 31 | 32 | class Comment; 33 | 34 | class Shield 35 | { 36 | public: 37 | enum {Top,Bottom,Slide,Reverse,Guest,Advanced,Color,Whole}; 38 | static bool shieldG[8]; 39 | static QSet shieldS; 40 | static QList shieldR; 41 | static void load(); 42 | static void save(); 43 | static bool isBlocked(const Comment &comment); 44 | }; 45 | 46 | #endif // SHIELD_H 47 | -------------------------------------------------------------------------------- /src/Plugin.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Plugin.h 6 | * Time: 2013/04/23 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef PLUGIN_H 28 | #define PLUGIN_H 29 | 30 | #include 31 | #include 32 | 33 | class Plugin 34 | { 35 | public: 36 | typedef void (*RegistPtr)(const QHash &); 37 | typedef void (*ConfigPtr)(QWidget *); 38 | typedef QString (*StringPtr)(QString); 39 | 40 | static QList plugins; 41 | static void loadPlugins(); 42 | 43 | Plugin(QString path); 44 | bool loaded(); 45 | void regist(const QHash &); 46 | void config(QWidget *); 47 | QString string(QString query); 48 | 49 | private: 50 | RegistPtr m_regist; 51 | ConfigPtr m_config; 52 | StringPtr m_string; 53 | }; 54 | 55 | #endif // PLUGIN_H 56 | -------------------------------------------------------------------------------- /src/Local.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Local.h 6 | * Time: 2014/05/10 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef LOCAL_H 28 | #define LOCAL_H 29 | 30 | #include 31 | #include 32 | 33 | #define lApp (static_cast(QCoreApplication::instance())) 34 | 35 | class Local:public QApplication 36 | { 37 | Q_OBJECT 38 | public: 39 | Local(int &argc,char **argv); 40 | 41 | static Local *instance() 42 | { 43 | return lApp; 44 | } 45 | 46 | QWidget *mainWidget() 47 | { 48 | return qobject_cast(objects["Interface"]); 49 | } 50 | 51 | static QHash objects; 52 | 53 | public slots: 54 | QString suggestion(int code); 55 | void exit(int code=0); 56 | 57 | void synchronize(quintptr func) 58 | { 59 | ((void (*)())func)(); 60 | } 61 | }; 62 | 63 | #endif // LOCAL_H 64 | -------------------------------------------------------------------------------- /res/DATA: -------------------------------------------------------------------------------- 1 | { 2 | "AcFunChannel": { 3 | "1": "动画", 4 | "100": "纪录片", 5 | "101": "演唱·乐器", 6 | "102": "宅舞", 7 | "103": "Vocaloid", 8 | "104": "ACG音乐", 9 | "105": "流行音乐", 10 | "106": "动画短片", 11 | "107": "MAD·AMV", 12 | "108": "MMD·3D", 13 | "109": "动画合集", 14 | "110": "文章综合", 15 | "42": "图库", 16 | "58": "音乐", 17 | "59": "游戏", 18 | "60": "娱乐", 19 | "63": "文章", 20 | "67": "新番连载", 21 | "68": "影视", 22 | "69": "体育", 23 | "70": "科技", 24 | "71": "Flash游戏", 25 | "72": "Mugen", 26 | "73": "工作·情感", 27 | "74": "动漫文化", 28 | "75": "漫画·小说", 29 | "76": "页游资料", 30 | "77": "1区", 31 | "78": "21区", 32 | "79": "31区", 33 | "80": "41区", 34 | "81": "文章里区(不审)", 35 | "82": "视频里区(不审)", 36 | "83": "游戏集锦", 37 | "84": "实况解说", 38 | "85": "英雄联盟", 39 | "86": "生活娱乐", 40 | "87": "鬼畜调教", 41 | "88": "萌宠", 42 | "89": "美食", 43 | "90": "科普", 44 | "91": "数码", 45 | "92": "军事", 46 | "93": "惊奇体育", 47 | "94": "足球", 48 | "95": "篮球", 49 | "96": "电影", 50 | "97": "剧集", 51 | "98": "综艺", 52 | "99": "特摄·霹雳" 53 | }, 54 | "AcPlayChannel": { 55 | "1": "TV剧集", 56 | "2": "TV特别篇", 57 | "3": "OVA", 58 | "4": "剧场版", 59 | "5": "MV", 60 | "6": "网络放送", 61 | "7": "其他", 62 | "99": "未知" 63 | }, 64 | "Logo": { 65 | "DirectX": "https://upload.wikimedia.org/wikipedia/commons/1/11/DirectX_logo.png", 66 | "FFmpeg": "http://www.ffmpeg.org/ffmpeg-logo.png", 67 | "OpenGL": "https://www.khronos.org/assets/uploads/ceimg/made/assets/uploads/apis/OpenGL-ES_300_199_75.png", 68 | "VLC": "http://images.videolan.org/images/logoOrange.png" 69 | }, 70 | "Version": "0.4.2" 71 | } 72 | -------------------------------------------------------------------------------- /src/Post.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Post.h 6 | * Time: 2013/05/23 7 | * Author: zhengdanwei 8 | * Contributor: Lysine 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #ifndef POST_H 29 | #define POST_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include "Utils.h" 35 | 36 | class Post:public QWidget 37 | { 38 | Q_OBJECT 39 | public: 40 | Post(QWidget *parent); 41 | 42 | private: 43 | QAction * commentA; 44 | QLineEdit * commentL; 45 | QComboBox * commentS; 46 | QComboBox * commentM; 47 | QPushButton * commentC; 48 | QPushButton * commentB; 49 | QNetworkAccessManager *manager; 50 | static Post *ins; 51 | 52 | Comment getComment(); 53 | QList getRecords(); 54 | 55 | signals: 56 | void posted(const Comment *); 57 | 58 | public slots: 59 | QColor getColor(); 60 | void setColor(QColor); 61 | void postComment(); 62 | bool isValid(); 63 | void setVisible(bool); 64 | }; 65 | 66 | #endif // POST_H 67 | -------------------------------------------------------------------------------- /src/Graphic.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Graphic.h 6 | * Time: 2013/10/19 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef GRAPHIC_H 28 | #define GRAPHIC_H 29 | 30 | #include 31 | #include 32 | #include "Utils.h" 33 | 34 | class Graphic 35 | { 36 | public: 37 | virtual bool move(qint64 time)=0; 38 | virtual void draw(QPainter *painter)=0; 39 | virtual uint intersects(Graphic *other)=0; 40 | virtual QRectF ¤tRect(){return rect;} 41 | virtual ~Graphic(){} 42 | 43 | inline int getMode(){return source?source->mode:0;} 44 | 45 | inline bool isEnabled(){return enabled;} 46 | inline void setEnabled(bool _enabled){enabled=_enabled;} 47 | 48 | inline quint64 getIndex(){return index;} 49 | void setIndex(); 50 | 51 | inline const Comment *getSource(){return source;} 52 | inline void setSource(const Comment *_source){source=_source;} 53 | 54 | static Graphic *create(const Comment &comment); 55 | 56 | protected: 57 | bool enabled; 58 | QRectF rect; 59 | quint64 index; 60 | const Comment *source; 61 | Graphic():enabled(false),source(NULL){} 62 | }; 63 | 64 | #endif // GRAPHIC_H 65 | -------------------------------------------------------------------------------- /src/Info.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Info.h 6 | * Time: 2013/04/05 7 | * Author: Lysine 8 | * Contributor: Chaserhkj 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #ifndef INFO_H 29 | #define INFO_H 30 | 31 | #include 32 | #include 33 | 34 | class Info:public QWidget 35 | { 36 | Q_OBJECT 37 | public: 38 | explicit Info(QWidget *parent=0); 39 | bool isShown(){return isPoped;} 40 | bool preferStay(){return isStay;} 41 | 42 | private: 43 | bool isStay; 44 | bool isPoped; 45 | bool updating; 46 | qint64 duration; 47 | 48 | QLabel *duraT; 49 | QLabel *timeT; 50 | QLabel *volmT; 51 | QLabel *plfmT; 52 | QSlider *timeS; 53 | QSlider *volmS; 54 | QLineEdit *plfmL; 55 | QTableView *danmV; 56 | QPushButton *playB; 57 | QPushButton *stopB; 58 | QAction *playA; 59 | QAction *stopA; 60 | QIcon playI,stopI,pausI; 61 | QPropertyAnimation *animation; 62 | void resizeEvent(QResizeEvent *e); 63 | void setTime(qint64 _time); 64 | void setDuration(qint64 _duration); 65 | 66 | public slots: 67 | void pop(); 68 | void push(bool force=false); 69 | void terminate(); 70 | void resizeHeader(); 71 | 72 | }; 73 | 74 | #endif // INFO_H 75 | -------------------------------------------------------------------------------- /BiliLocal.pro: -------------------------------------------------------------------------------- 1 | QT += \ 2 | core \ 3 | gui \ 4 | network \ 5 | widgets 6 | 7 | TARGET = BiliLocal 8 | 9 | TEMPLATE = app 10 | 11 | CONFIG += c++11 12 | 13 | INCLUDEPATH += \ 14 | C:/msys64/usr/local/include 15 | 16 | LIBS += \ 17 | -LC:/msys64/usr/local/lib 18 | 19 | SOURCES += \ 20 | src/APlayer.cpp \ 21 | src/Config.cpp \ 22 | src/Danmaku.cpp \ 23 | src/Graphic.cpp \ 24 | src/List.cpp \ 25 | src/Load.cpp \ 26 | src/Local.cpp \ 27 | src/Render.cpp \ 28 | src/Shield.cpp \ 29 | src/Utils.cpp \ 30 | src/Interface.cpp \ 31 | src/Menu.cpp \ 32 | src/Info.cpp \ 33 | src/Editor.cpp \ 34 | src/Post.cpp \ 35 | src/Jump.cpp \ 36 | src/Search.cpp \ 37 | src/Plugin.cpp 38 | 39 | HEADERS += \ 40 | src/APlayer.h \ 41 | src/Config.h \ 42 | src/Danmaku.h \ 43 | src/Graphic.h \ 44 | src/List.h \ 45 | src/Load.h \ 46 | src/Local.h \ 47 | src/Render.h \ 48 | src/Shield.h \ 49 | src/Utils.h \ 50 | src/Interface.h \ 51 | src/Menu.h \ 52 | src/Info.h \ 53 | src/Editor.h \ 54 | src/Post.h \ 55 | src/Jump.h \ 56 | src/Search.h \ 57 | src/Plugin.h 58 | 59 | RESOURCES += \ 60 | res/Res.qrc 61 | 62 | TRANSLATIONS += \ 63 | res/zh_CN.ts \ 64 | res/zh_TW.ts 65 | 66 | linux{ 67 | DEFINES += \ 68 | BACKEND_VLC 69 | 70 | DEFINES += \ 71 | RENDER_RASTER \ 72 | RENDER_OPENGL 73 | } 74 | 75 | win32{ 76 | RC_ICONS = BiliLocal.ico 77 | 78 | DEFINES += \ 79 | BACKEND_VLC \ 80 | BACKEND_QMM \ 81 | BACKEND_NIL 82 | 83 | DEFINES += \ 84 | RENDER_RASTER \ 85 | RENDER_OPENGL \ 86 | RENDER_DETACH 87 | } 88 | 89 | macx{ 90 | DEFINES += \ 91 | BACKEND_VLC 92 | 93 | DEFINES += \ 94 | RENDER_OPENGL 95 | } 96 | 97 | contains(DEFINES, BACKEND_QMM){ 98 | QT += \ 99 | multimedia 100 | } 101 | 102 | contains(DEFINES, BACKEND_VLC){ 103 | LIBS += \ 104 | -lvlc \ 105 | -lvlccore 106 | } 107 | 108 | contains(DEFINES, RENDER_RASTER){ 109 | LIBS += \ 110 | -lswscale \ 111 | -lavutil 112 | } 113 | -------------------------------------------------------------------------------- /src/Menu.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Menu.h 6 | * Time: 2013/04/05 7 | * Author: Lysine 8 | * Contributor: Chaserhkj 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #ifndef MENU_H 29 | #define MENU_H 30 | 31 | #include 32 | #include 33 | 34 | class Menu:public QWidget 35 | { 36 | Q_OBJECT 37 | public: 38 | explicit Menu(QWidget *parent=0); 39 | bool isShown(){return isPoped;} 40 | bool preferStay(){return isStay||!danmC->popup()->isHidden();} 41 | 42 | private: 43 | bool isStay; 44 | bool isPoped; 45 | QLineEdit *fileL; 46 | QLineEdit *danmL; 47 | QLineEdit *sechL; 48 | QCompleter *fileC; 49 | QCompleter *danmC; 50 | QPushButton *fileB; 51 | QPushButton *danmB; 52 | QPushButton *sechB; 53 | QAction *fileA; 54 | QAction *danmA; 55 | QAction *sechA; 56 | QLabel *alphaT; 57 | QSlider *alphaS; 58 | QLabel *powerT; 59 | QLineEdit *powerL; 60 | QLabel *localT; 61 | QCheckBox *localC; 62 | QLabel *subT; 63 | QCheckBox *subC; 64 | QLabel *loopT; 65 | QCheckBox *loopC; 66 | QPropertyAnimation *animation; 67 | void resizeEvent(QResizeEvent *e); 68 | bool eventFilter(QObject *o,QEvent *e); 69 | 70 | public slots: 71 | void pop(); 72 | void push(bool force=false); 73 | void terminate(); 74 | 75 | }; 76 | 77 | #endif // MENU_H 78 | -------------------------------------------------------------------------------- /src/Search.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Search.h 6 | * Time: 2013/04/18 7 | * Author: Chaserhkj 8 | * Contributor: Lysine 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #ifndef SEARCH_H 29 | #define SEARCH_H 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | class Search:public QDialog 36 | { 37 | Q_OBJECT 38 | public: 39 | explicit Search(QWidget *parent=0); 40 | inline QString getKey(){return key;} 41 | inline QString getAid(){return aid;} 42 | 43 | private: 44 | QLabel *statusL; 45 | QLabel *pageT; 46 | QLabel *pageL; 47 | QLineEdit *keywE; 48 | QLineEdit *pageE; 49 | QComboBox *orderC; 50 | QComboBox *sitesC; 51 | QPushButton *okB; 52 | QPushButton *ccB; 53 | QPushButton *searchB; 54 | QPushButton *pageUpB; 55 | QPushButton *pageDnB; 56 | QPushButton *pageGoB; 57 | QTreeWidget *resultW; 58 | QNetworkAccessManager *manager; 59 | QSet remain; 60 | 61 | QString key; 62 | QString aid; 63 | 64 | int pageNum; 65 | int pageCur; 66 | bool isWaiting; 67 | 68 | void getData(int pageNum); 69 | QList getOrder(int site); 70 | 71 | public slots: 72 | void setText(QString text); 73 | void setSite(); 74 | void startSearch(); 75 | void clearSearch(); 76 | void accept(); 77 | 78 | }; 79 | 80 | #endif // SEARCH_H 81 | -------------------------------------------------------------------------------- /src/Jump.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Jump.cpp 6 | * Time: 2013/04/22 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Jump.h" 28 | #include "APlayer.h" 29 | #include "List.h" 30 | #include "Local.h" 31 | 32 | Jump::Jump(QWidget *parent): 33 | QWidget(parent) 34 | { 35 | setFixedSize(parent->minimumWidth(),25); 36 | setObjectName("Jump"); 37 | 38 | auto layout=new QHBoxLayout(this); 39 | layout->setMargin(0);layout->setSpacing(0); 40 | 41 | fileL=new QLineEdit(this); 42 | fileL->setReadOnly(true); 43 | fileL->setFocusPolicy(Qt::NoFocus); 44 | layout->addWidget(fileL); 45 | 46 | jumpB=new QPushButton(this); 47 | jumpB->setFixedWidth(55); 48 | connect(jumpB,&QPushButton::clicked,[this](){ 49 | QStandardItem *c=List::instance()->getCurrent(); 50 | APlayer::instance()->setTime(c->data(List::TimeRole).toDouble()); 51 | hide(); 52 | }); 53 | layout->addWidget(jumpB); 54 | 55 | connect(APlayer::instance(),&APlayer::begin,[this](){ 56 | QStandardItem *c=List::instance()->getCurrent(); 57 | if(!c){ 58 | return; 59 | } 60 | qint64 t=c->data(List::TimeRole).toDouble()/1000; 61 | if (t<=0){ 62 | return; 63 | } 64 | fileL->setText(c->text()); 65 | QString time("%1:%2"); 66 | time=time.arg(t/60,2,10,QChar('0')); 67 | time=time.arg(t%60,2,10,QChar('0')); 68 | jumpB->setText(time); 69 | show(); 70 | }); 71 | hide(); 72 | } 73 | -------------------------------------------------------------------------------- /src/Plugin.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Plugin.cpp 6 | * Time: 2013/04/23 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Plugin.h" 28 | #include "Local.h" 29 | #include "Config.h" 30 | 31 | QList Plugin::plugins; 32 | 33 | Plugin::Plugin(QString path) 34 | { 35 | m_regist=(RegistPtr)QLibrary::resolve(path,"regist"); 36 | m_config=(ConfigPtr)QLibrary::resolve(path,"config"); 37 | m_string=(StringPtr)QLibrary::resolve(path,"string"); 38 | } 39 | 40 | bool Plugin::loaded() 41 | { 42 | return m_regist&&m_config&&m_string; 43 | } 44 | 45 | void Plugin::regist(const QHash &objects) 46 | { 47 | if (m_regist) 48 | m_regist(objects); 49 | } 50 | 51 | void Plugin::config(QWidget *parent) 52 | { 53 | if (m_config) 54 | m_config(parent); 55 | } 56 | 57 | QString Plugin::string(QString query) 58 | { 59 | return m_string(query); 60 | } 61 | 62 | void Plugin::loadPlugins() 63 | { 64 | QFileInfoList list = QDir("./plugins/bililocal/").entryInfoList(); 65 | for(const QFileInfo &info:list){ 66 | if(info.isFile()&&QLibrary::isLibrary(info.fileName())){ 67 | Plugin lib(info.absoluteFilePath()); 68 | if(lib.loaded()){ 69 | if(Config::getValue("/Plugin/"+lib.string("Name"),true)){ 70 | lib.regist(Local::objects); 71 | } 72 | else{ 73 | lib.m_config=nullptr; 74 | lib.m_regist=nullptr; 75 | } 76 | plugins.append(lib); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Render.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Render.h 6 | * Time: 2013/12/27 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef RENDER_H 28 | #define RENDER_H 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | class RenderPrivate; 35 | 36 | class Render:public QObject 37 | { 38 | Q_OBJECT 39 | public: 40 | class ICache 41 | { 42 | public: 43 | virtual void draw(QPainter *,QRectF)=0; 44 | virtual ~ICache()=default; 45 | }; 46 | 47 | virtual ~Render(); 48 | static Render *instance(); 49 | 50 | protected: 51 | static Render *ins; 52 | RenderPrivate *const d_ptr; 53 | Q_DECLARE_PRIVATE(Render) 54 | 55 | Render(RenderPrivate *data,QObject *parent=0); 56 | 57 | signals: 58 | void refreshRateChanged(int); 59 | 60 | public slots: 61 | virtual QList getBuffer(); 62 | virtual void releaseBuffer(); 63 | virtual void setBuffer(QString &chroma,QSize size,QList *bufferSize=0); 64 | virtual ICache *getCache(const QImage &)=0; 65 | 66 | void setBackground(QString path); 67 | void setMusic(bool music); 68 | void setDisplayTime(double t); 69 | void setVideoAspectRatio(double ratio); 70 | void setPixelAspectRatio(double ratio); 71 | virtual quintptr getHandle()=0; 72 | virtual void resize(QSize size)=0; 73 | virtual void setRefreshRate(int rate,bool soft=false); 74 | virtual QSize getBufferSize()=0; 75 | virtual QSize getActualSize()=0; 76 | virtual QSize getPreferredSize(); 77 | virtual void draw(QRect rect=QRect())=0; 78 | }; 79 | 80 | #endif // RENDER_H 81 | -------------------------------------------------------------------------------- /src/APlayer.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: APlayer.h 6 | * Time: 2013/03/18 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef APLAYER_H 28 | #define APLAYER_H 29 | 30 | #include 31 | #include 32 | 33 | class APlayer:public QObject 34 | { 35 | Q_OBJECT 36 | public: 37 | enum State 38 | { 39 | Stop, 40 | Play, 41 | Pause, 42 | Loop 43 | }; 44 | 45 | enum Error 46 | { 47 | UnknownError, 48 | ResourceError, 49 | FormatError, 50 | NetworkError, 51 | AccessDeniedError, 52 | ServiceMissingError 53 | }; 54 | 55 | virtual QList getTracks(int type)=0; 56 | static APlayer *instance(); 57 | 58 | protected: 59 | static APlayer *ins; 60 | APlayer(QObject *parent=0):QObject(parent){} 61 | 62 | signals: 63 | void errorOccurred(int); 64 | void begin(); 65 | void reach(bool); 66 | void decode(); 67 | void jumped(qint64); 68 | void timeChanged(qint64); 69 | void stateChanged(int); 70 | void mediaChanged(QString); 71 | void volumeChanged(int); 72 | 73 | public slots: 74 | virtual void play()=0; 75 | virtual void stop(bool manually=true)=0; 76 | virtual int getState()=0; 77 | 78 | virtual void setTime(qint64 _time)=0; 79 | virtual qint64 getTime()=0; 80 | 81 | virtual void setMedia(QString _file,bool manually=true)=0; 82 | virtual QString getMedia()=0; 83 | 84 | virtual qint64 getDuration()=0; 85 | virtual void addSubtitle(QString _file)=0; 86 | 87 | virtual void setVolume(int _volume)=0; 88 | virtual int getVolume()=0; 89 | 90 | virtual void event(int type)=0; 91 | }; 92 | 93 | #endif // APLAYER_H 94 | -------------------------------------------------------------------------------- /src/List.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: List.h 6 | * Time: 2014/11/19 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef LIST_H 28 | #define LIST_H 29 | 30 | #include 31 | #include 32 | 33 | class List:public QStandardItemModel 34 | { 35 | Q_OBJECT 36 | public: 37 | enum Role 38 | { 39 | FileRole=Qt::UserRole, 40 | CodeRole, 41 | TimeRole, 42 | DateRole 43 | }; 44 | 45 | enum Danm 46 | { 47 | Records, 48 | Inherit, 49 | Surmise 50 | }; 51 | 52 | ~List(); 53 | void saveList(); 54 | static List *instance(); 55 | QStringList mimeTypes() const; 56 | QMimeData *mimeData(const QModelIndexList &) const; 57 | bool dropMimeData(const QMimeData *,Qt::DropAction,int,int,const QModelIndex &p); 58 | 59 | private: 60 | QStandardItem *cur; 61 | qint64 time; 62 | QList icons; 63 | static List *ins; 64 | List(QObject *parent); 65 | void setRelated(const QModelIndexList &indexes,int reason); 66 | 67 | public slots: 68 | QString defaultPath(int type); 69 | QStandardItem *getCurrent(){return cur;} 70 | QStandardItem *itemFromFile(QString file,bool create=false); 71 | bool finished(); 72 | void appendMedia(QString file); 73 | void updateCurrent(); 74 | void split(const QModelIndex &index); 75 | void split(const QModelIndexList &indexes); 76 | void waste(const QModelIndex &index); 77 | void waste(const QModelIndexList &indexes); 78 | void merge(const QModelIndexList &indexes); 79 | void group(const QModelIndexList &indexes); 80 | void jumpToLast(); 81 | void jumpToNext(); 82 | bool jumpToIndex(const QModelIndex &index,bool manually=true); 83 | }; 84 | 85 | #endif // LIST_H 86 | -------------------------------------------------------------------------------- /src/Danmaku.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Danmaku.h 6 | * Time: 2013/03/18 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef DANMAKU_H 28 | #define DANMAKU_H 29 | 30 | #include 31 | #include 32 | #include "Utils.h" 33 | 34 | class Graphic; 35 | 36 | class Danmaku:public QAbstractItemModel 37 | { 38 | Q_OBJECT 39 | public: 40 | ~Danmaku(); 41 | QList &getPool(){return pool;} 42 | void draw(QPainter *painter,qint64 move); 43 | QVariant data(const QModelIndex &index,int role) const; 44 | int rowCount(const QModelIndex &parent=QModelIndex()) const; 45 | int columnCount(const QModelIndex &parent=QModelIndex()) const; 46 | QModelIndex parent(const QModelIndex &) const; 47 | QModelIndex index(int row,int colum,const QModelIndex &parent=QModelIndex()) const; 48 | QVariant headerData(int section,Qt::Orientation orientation,int role) const; 49 | static Danmaku *instance(); 50 | 51 | private: 52 | int cur; 53 | qint64 time; 54 | QList pool; 55 | QList danmaku; 56 | QList current; 57 | mutable QReadWriteLock lock; 58 | static Danmaku *ins; 59 | 60 | Danmaku(QObject *parent=0); 61 | void setTime(qint64 _time); 62 | 63 | signals: 64 | void alphaChanged(int); 65 | void unrecognizedComment(const Comment *); 66 | 67 | public slots: 68 | void setAlpha(int _alpha); 69 | void resetTime(); 70 | void clearPool(); 71 | void appendToPool(const Record *record); 72 | void appendToPool(QString source,const Comment *comment); 73 | void clearCurrent(bool soft=false); 74 | void insertToCurrent(Graphic *graphic,int index=-1); 75 | const Comment *commentAt(QPoint point) const; 76 | void parse(int flag=0); 77 | void delayAll(qint64 _time); 78 | void jumpToTime(qint64 _time); 79 | void saveToFile(QString file); 80 | }; 81 | 82 | #endif // DANMAKU_H 83 | -------------------------------------------------------------------------------- /src/Interface.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Interface.h 6 | * Time: 2013/03/18 7 | * Author: Lysine 8 | * Contributor: Chaserhkj 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #ifndef INTERFACE_H 29 | #define INTERFACE_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | class Render; 37 | class Menu; 38 | class Info; 39 | class Jump; 40 | class Post; 41 | class List; 42 | class Load; 43 | class APlayer; 44 | class Danmaku; 45 | class Message; 46 | 47 | class Interface:public QWidget 48 | { 49 | Q_OBJECT 50 | public: 51 | explicit Interface(QWidget *parent=0); 52 | 53 | private: 54 | QTimer *timer; 55 | QTimer *delay; 56 | 57 | QAction *quitA; 58 | QAction *fullA; 59 | QAction *confA; 60 | QAction *toggA; 61 | QAction *listA; 62 | QAction *postA; 63 | QMenu *rat; 64 | QMenu *sca; 65 | 66 | Render *render; 67 | Menu *menu; 68 | Info *info; 69 | Jump *jump; 70 | Post *post; 71 | List *list; 72 | Load *load; 73 | APlayer *aplayer; 74 | Danmaku *danmaku; 75 | Message *message; 76 | 77 | QPoint sta; 78 | QPoint wgd; 79 | QByteArray geo; 80 | 81 | bool showprg; 82 | bool sliding; 83 | 84 | void closeEvent(QCloseEvent *e); 85 | void dragEnterEvent(QDragEnterEvent *e); 86 | void dropEvent(QDropEvent *e); 87 | void mouseDoubleClickEvent(QMouseEvent *e); 88 | void mouseMoveEvent(QMouseEvent *e); 89 | void mousePressEvent(QMouseEvent *e); 90 | void mouseReleaseEvent(QMouseEvent *e); 91 | void resizeEvent(QResizeEvent *e); 92 | 93 | signals: 94 | void windowFlagsChanged(QFlags); 95 | 96 | public slots: 97 | void tryLocal(QString p); 98 | void tryLocal(QStringList p); 99 | void setWindowFlags(); 100 | 101 | private slots: 102 | void checkForUpdate(); 103 | void setCenter(QSize s,bool f); 104 | void showContextMenu(QPoint p); 105 | }; 106 | 107 | #endif // INTERFACE_H 108 | -------------------------------------------------------------------------------- /src/Shield.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Shield.cpp 6 | * Time: 2013/05/20 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Shield.h" 28 | #include "Utils.h" 29 | #include "Config.h" 30 | 31 | bool Shield::shieldG[8]; 32 | QSet Shield::shieldS; 33 | QList Shield::shieldR; 34 | 35 | void Shield::load() 36 | { 37 | QJsonArray s=Config::getValue("/Shield/Sender"); 38 | QJsonArray r=Config::getValue("/Shield/Regexp"); 39 | for(const QJsonValue &item:s){ 40 | shieldS.insert(item.toString()); 41 | } 42 | for(const QJsonValue &item:r){ 43 | shieldR.append(QRegularExpression(item.toString())); 44 | } 45 | int group=Config::getValue("/Shield/Group",0); 46 | for(int i=7;i>=0;--i){ 47 | shieldG[i]=group&1; 48 | group=group>>1; 49 | } 50 | } 51 | 52 | void Shield::save() 53 | { 54 | QJsonArray s,r; 55 | for(auto &item:shieldS){ 56 | s.append(item); 57 | } 58 | Config::setValue("/Shield/Sender",s); 59 | for(auto &item:shieldR){ 60 | r.append(item.pattern()); 61 | } 62 | Config::setValue("/Shield/Regexp",r); 63 | int g=0; 64 | for(int i=0;i<8;++i){ 65 | g=(g<<1)+shieldG[i]; 66 | } 67 | Config::setValue("/Shield/Group",g); 68 | } 69 | 70 | bool Shield::isBlocked(const Comment &comment) 71 | { 72 | if(shieldG[Whole] 73 | ||(comment.mode==1&&shieldG[Slide]) 74 | ||(comment.mode==4&&shieldG[Bottom]) 75 | ||(comment.mode==5&&shieldG[Top]) 76 | ||(comment.mode==6&&shieldG[Reverse]) 77 | ||(shieldG[Advanced]&&(comment.mode==7||comment.mode==8)) 78 | ||(comment.color!=0xFFFFFF&&shieldG[Color])){ 79 | return true; 80 | } 81 | if(shieldG[Guest]){ 82 | if(comment.sender.length()==14&&comment.sender[3]=='k'){ 83 | return true; 84 | } 85 | if(comment.sender.startsWith('D',Qt::CaseInsensitive)){ 86 | return true; 87 | } 88 | if(comment.sender=="0"){ 89 | return true; 90 | } 91 | } 92 | if(shieldS.contains(comment.sender)){ 93 | return true; 94 | } 95 | for(const QRegularExpression &r:shieldR){ 96 | if(r.match(comment.string).hasMatch()){ 97 | return true; 98 | } 99 | } 100 | return false; 101 | } 102 | -------------------------------------------------------------------------------- /src/Load.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Load.h 6 | * Time: 2014/04/22 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef LOAD_H 28 | #define LOAD_H 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | class Record; 36 | 37 | class Load:public QObject 38 | { 39 | Q_OBJECT 40 | public: 41 | enum State 42 | { 43 | None=0, 44 | Page=381, 45 | Part=407, 46 | Code=379, 47 | File=384, 48 | }; 49 | 50 | enum Role 51 | { 52 | UrlRole=Qt::UserRole, 53 | StrRole, 54 | NxtRole 55 | }; 56 | 57 | struct Proc 58 | { 59 | std::function regular; 60 | int priority; 61 | std::function process; 62 | }; 63 | 64 | struct Task 65 | { 66 | QString code; 67 | QNetworkRequest request; 68 | int state; 69 | const Proc *processer; 70 | qint64 delay; 71 | Task():state(None),processer(nullptr),delay(0){} 72 | }; 73 | 74 | Task codeToTask(QString code); 75 | static Load *instance(); 76 | 77 | private: 78 | QStandardItemModel *model; 79 | QList pool; 80 | QNetworkAccessManager *manager; 81 | QQueue queue; 82 | QSet remain; 83 | static Load *ins; 84 | Load(QObject *parent=0); 85 | 86 | signals: 87 | void stateChanged(int state); 88 | void errorOccured(int state); 89 | void progressChanged(double progress); 90 | 91 | public slots: 92 | void addProc(const Proc *proc); 93 | const Proc *getProc(QString code); 94 | 95 | void fixCode(QString &); 96 | bool canLoad(QString); 97 | bool canFull(const Record *); 98 | bool canHist(const Record *); 99 | 100 | void loadDanmaku(QString); 101 | void loadDanmaku(const QModelIndex &index=QModelIndex()); 102 | void fullDanmaku(const Record *); 103 | void loadHistory(const Record *,QDate); 104 | void dumpDanmaku(const QByteArray &data,int site,Record *r); 105 | void dumpDanmaku(const QByteArray &data,int site,bool full); 106 | 107 | QStandardItemModel *getModel(); 108 | int size(){return queue.size();} 109 | void dequeue(); 110 | bool enqueue(const Task &); 111 | Task*getHead(); 112 | void forward(); 113 | void forward(QNetworkRequest); 114 | void forward(QNetworkRequest,int); 115 | }; 116 | 117 | #endif // LOAD_H 118 | -------------------------------------------------------------------------------- /src/Utils.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Utils.h 6 | * Time: 2013/05/10 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef UTILS_H 28 | #define UTILS_H 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | class Comment 35 | { 36 | public: 37 | int mode; 38 | int font; 39 | int color; 40 | qint64 time; 41 | qint64 date; 42 | QString sender; 43 | QString string; 44 | bool blocked; 45 | Comment() 46 | { 47 | mode=font=color=time=date=0; 48 | blocked=false; 49 | } 50 | inline bool operator <(const Comment &o) const 51 | { 52 | return time danmaku; 85 | Record() 86 | { 87 | full=false; 88 | delay=limit=0; 89 | } 90 | }; 91 | 92 | namespace Utils 93 | { 94 | enum Site 95 | { 96 | Unknown, 97 | Bilibili, 98 | AcFun, 99 | Tudou, 100 | Letv, 101 | AcPlay, 102 | AcfunLocalizer, 103 | Niconico, 104 | TuCao 105 | }; 106 | 107 | enum Type 108 | { 109 | Video=1, 110 | Audio=2, 111 | Subtitle=4, 112 | Danmaku=8 113 | }; 114 | 115 | Site parseSite(QString url); 116 | void setCenter(QWidget *widget); 117 | void setGround(QWidget *widget,QColor color); 118 | QString defaultFont(bool monospace=false); 119 | QString customUrl(Site site); 120 | QString decodeXml(QString string,bool fast=false); 121 | QStringList getRenderModules(); 122 | QStringList getDecodeModules(); 123 | QStringList getSuffix(int type,QString format=""); 124 | double evaluate(QString expression); 125 | } 126 | 127 | #endif // UTILS_H 128 | -------------------------------------------------------------------------------- /src/Config.h: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Config.h 6 | * Time: 2013/06/17 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #ifndef CONFIG_H 28 | #define CONFIG_H 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | namespace{ 35 | template 36 | TypeName fromJsonValue(QJsonValue v) 37 | { 38 | QVariant t=v.toVariant(); 39 | if(!t.canConvert()){ 40 | throw("type missmatch"); 41 | } 42 | return t.value(); 43 | } 44 | 45 | template<> 46 | QVariant fromJsonValue(QJsonValue v) 47 | { 48 | return v.toVariant(); 49 | } 50 | 51 | template<> 52 | QJsonArray fromJsonValue(QJsonValue v) 53 | { 54 | if (QJsonValue::Array!=v.type()){ 55 | throw("type missmatch"); 56 | } 57 | return v.toArray(); 58 | } 59 | 60 | template<> 61 | QJsonObject fromJsonValue(QJsonValue v) 62 | { 63 | if (QJsonValue::Object!=v.type()){ 64 | throw("type missmatch"); 65 | } 66 | return v.toObject(); 67 | } 68 | 69 | template 70 | QJsonValue toJsonValue(TypeName v) 71 | { 72 | return QJsonValue(v); 73 | } 74 | 75 | template<> 76 | QJsonValue toJsonValue(QVariant v) 77 | { 78 | return QJsonValue::fromVariant(v); 79 | } 80 | } 81 | 82 | class Config:public QObject 83 | { 84 | Q_OBJECT 85 | public: 86 | explicit Config(QObject *parent=0); 87 | 88 | template 89 | static T getValue(QString key,T def=T()) 90 | { 91 | QStringList tree=key.split('/',QString::SkipEmptyParts); 92 | QString last=tree.takeLast(); 93 | QJsonObject cur=config; 94 | QList path; 95 | for(const QString &k:tree){ 96 | path.append(cur); 97 | cur=cur.value(k).toObject(); 98 | } 99 | if (cur.contains(last)){ 100 | try{ 101 | return fromJsonValue(cur.value(last)); 102 | } 103 | catch(...){} 104 | } 105 | QJsonValue val=toJsonValue(def); 106 | if(!val.isNull()){ 107 | cur[last]=val; 108 | while(!path.isEmpty()){ 109 | QJsonObject pre=path.takeLast(); 110 | pre[tree.takeLast()]=cur; 111 | cur=pre; 112 | } 113 | config=cur; 114 | } 115 | return def; 116 | } 117 | 118 | template 119 | static void setValue(QString key,T set) 120 | { 121 | QStringList tree=key.split('/',QString::SkipEmptyParts); 122 | QString last=tree.takeLast(); 123 | QJsonObject cur=config; 124 | QList path; 125 | for(const QString &k:tree){ 126 | path.append(cur); 127 | cur=cur.value(k).toObject(); 128 | } 129 | cur[last]=toJsonValue(set); 130 | while(!path.isEmpty()){ 131 | QJsonObject pre=path.takeLast(); 132 | pre[tree.takeLast()]=cur; 133 | cur=pre; 134 | } 135 | config=cur; 136 | } 137 | 138 | static Config *instance(); 139 | 140 | private: 141 | static Config *ins; 142 | static QJsonObject config; 143 | 144 | public slots: 145 | static void exec(QWidget *parent=0,int index=0); 146 | static void load(); 147 | static void save(); 148 | static void setManager(QNetworkAccessManager *manager); 149 | void setVariant(QString key,QVariant val); 150 | QVariant getVariant(QString key,QVariant val=QVariant()); 151 | }; 152 | 153 | #endif // CONFIG_H 154 | -------------------------------------------------------------------------------- /src/Local.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Local.cpp 6 | * Time: 2013/03/18 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Local.h" 28 | #include "APlayer.h" 29 | #include "Config.h" 30 | #include "Danmaku.h" 31 | #include "Interface.h" 32 | #include "List.h" 33 | #include "Load.h" 34 | #include "Plugin.h" 35 | #include "Render.h" 36 | #include "Shield.h" 37 | #include "Utils.h" 38 | 39 | QHash Local::objects; 40 | 41 | Local::Local(int &argc,char **argv): 42 | QApplication(argc,argv) 43 | { 44 | QDir::setCurrent(applicationDirPath()); 45 | setPalette(setStyle("Fusion")->standardPalette()); 46 | setAttribute(Qt::AA_UseOpenGLES); 47 | thread()->setPriority(QThread::TimeCriticalPriority); 48 | Config::load(); 49 | Shield::load(); 50 | qsrand(QTime::currentTime().msec()); 51 | } 52 | 53 | void Local::exit(int code) 54 | { 55 | delete List::instance(); 56 | delete Load::instance(); 57 | Shield::save(); 58 | Config::save(); 59 | delete APlayer::instance(); 60 | delete Danmaku::instance(); 61 | QApplication::exit(code); 62 | } 63 | 64 | QString Local::suggestion(int code) 65 | { 66 | switch (code){ 67 | case 3: 68 | case 4: 69 | return tr("check your network connection"); 70 | case 203: 71 | case 403: 72 | case -8: 73 | return tr("access denied, try login"); 74 | default: 75 | return QString(); 76 | } 77 | } 78 | 79 | namespace 80 | { 81 | void setDefaultFont() 82 | { 83 | QString def=Utils::defaultFont(); 84 | QFontInfo i(qApp->font()); 85 | if(!QFontDatabase().families().contains(def)){ 86 | def=i.family(); 87 | } 88 | double p=i.pointSizeF(); 89 | QFont f; 90 | f.setFamily(Config::getValue("/Interface/Font/Family",def)); 91 | f.setPointSizeF(Config::getValue("/Interface/Font/Size",p)); 92 | qApp->setFont(f); 93 | } 94 | 95 | void loadTranslator() 96 | { 97 | QString locale=Config::getValue("/Interface/Locale",QLocale::system().name()); 98 | QFileInfoList list; 99 | list+=QDir("./locale/"+locale).entryInfoList(); 100 | list+=QFileInfo("./locale/"+locale+".qm"); 101 | locale.resize(2); 102 | list+=QDir("./locale/"+locale).entryInfoList(); 103 | list+=QFileInfo("./locale/"+locale+".qm"); 104 | for(QFileInfo info:list){ 105 | if(!info.isFile()){ 106 | continue; 107 | } 108 | QTranslator *trans=new QTranslator(qApp); 109 | if(trans->load(info.absoluteFilePath())){ 110 | qApp->installTranslator(trans); 111 | } 112 | else{ 113 | delete trans; 114 | } 115 | } 116 | } 117 | 118 | void setToolTipBase() 119 | { 120 | QPalette tip=qApp->palette(); 121 | tip.setColor(QPalette::Inactive,QPalette::ToolTipBase,Qt::white); 122 | qApp->setPalette(tip); 123 | QToolTip::setPalette(tip); 124 | } 125 | } 126 | 127 | int main(int argc,char *argv[]) 128 | { 129 | Local a(argc,argv); 130 | int single; 131 | if((single=Config::getValue("/Interface/Single",1))){ 132 | QLocalSocket socket; 133 | socket.connectToServer("BiliLocalInstance"); 134 | if (socket.waitForConnected()){ 135 | QDataStream s(&socket); 136 | s<listen("BiliLocalInstance"); 154 | QObject::connect(server,&QLocalServer::newConnection,[&](){ 155 | QLocalSocket *r=server->nextPendingConnection(); 156 | r->waitForReadyRead(); 157 | QDataStream s(r); 158 | QStringList args; 159 | s>>args; 160 | delete r; 161 | w.tryLocal(args); 162 | }); 163 | } 164 | int r; 165 | if((r=a.exec())==12450){ 166 | if(server){ 167 | delete server; 168 | } 169 | QProcess::startDetached(a.applicationFilePath(),QStringList()); 170 | return 0; 171 | } 172 | else{ 173 | return r; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Post.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Post.cpp 6 | * Time: 2013/05/23 7 | * Author: zhengdanwei 8 | * Contributor: Lysine 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #include "Post.h" 29 | #include "APlayer.h" 30 | #include "Config.h" 31 | #include "Danmaku.h" 32 | #include "Graphic.h" 33 | #include "Local.h" 34 | 35 | Post::Post(QWidget *parent): 36 | QWidget(parent) 37 | { 38 | setFixedSize(parent->minimumWidth(),25); 39 | setObjectName("Post"); 40 | manager=new QNetworkAccessManager(this); 41 | Config::setManager(manager); 42 | auto layout=new QHBoxLayout(this); 43 | layout->setMargin(0); 44 | layout->setSpacing(0); 45 | commentM=new QComboBox(this); 46 | commentM->addItems({tr("Top"),tr("Slide"),tr("Bottom")}); 47 | commentM->setCurrentIndex(1); 48 | commentM->setFixedWidth(commentM->sizeHint().width()); 49 | layout->addWidget(commentM); 50 | commentC=new QPushButton(this); 51 | commentC->setFixedWidth(25); 52 | setColor(Qt::white); 53 | connect(commentC,&QPushButton::clicked,[this](){ 54 | QColor color=QColorDialog::getColor(getColor(),lApp->mainWidget()); 55 | if(color.isValid()){ 56 | setColor(color); 57 | } 58 | }); 59 | layout->addWidget(commentC); 60 | commentL=new QLineEdit(this); 61 | commentL->setFocus(); 62 | layout->addWidget(commentL); 63 | commentS=new QComboBox(this); 64 | layout->addWidget(commentS); 65 | commentB=new QPushButton(tr("Post"),this); 66 | commentB->setDefault(true); 67 | commentB->setFixedWidth(55); 68 | commentB->setToolTip(tr("DA☆ZE!")); 69 | layout->addWidget(commentB); 70 | commentA=new QAction(this); 71 | commentA->setShortcut(QKeySequence("Ctrl+Enter")); 72 | connect(commentB,&QPushButton::clicked,commentA,&QAction::trigger); 73 | connect(commentL,&QLineEdit::returnPressed,commentA,&QAction::trigger); 74 | connect(commentA,&QAction::triggered,[this](){ 75 | if(!commentL->text().isEmpty()){ 76 | postComment(); 77 | commentL->clear(); 78 | hide(); 79 | } 80 | }); 81 | connect(Danmaku::instance(),&Danmaku::modelReset,[this](){ 82 | commentS->clear(); 83 | int w=0; 84 | for(const Record *r:getRecords()){ 85 | commentS->addItem(r->string,(quintptr)r); 86 | w=qMax(w,commentS->fontMetrics().width(r->string)); 87 | } 88 | commentS->setVisible(commentS->count()>=2); 89 | commentS->setFixedWidth(w+30); 90 | }); 91 | hide(); 92 | } 93 | 94 | static int mode(int i) 95 | { 96 | switch(i){ 97 | case 0: 98 | return 5; 99 | case 1: 100 | return 1; 101 | case 2: 102 | return 4; 103 | default: 104 | return 0; 105 | } 106 | } 107 | 108 | Comment Post::getComment() 109 | { 110 | Comment c; 111 | c.mode=mode(commentM->currentIndex()); 112 | c.font=25; 113 | c.time=qMax(0,APlayer::instance()->getTime()); 114 | c.color=getColor().rgb()&0xFFFFFF; 115 | c.string=commentL->text(); 116 | return c; 117 | } 118 | 119 | QList Post::getRecords() 120 | { 121 | QList list; 122 | for(const Record &r:Danmaku::instance()->getPool()){ 123 | int s=Utils::parseSite(r.source); 124 | if(s==Utils::Bilibili||s==Utils::AcPlay){ 125 | list.append(&r); 126 | } 127 | } 128 | return list; 129 | } 130 | 131 | QColor Post::getColor() 132 | { 133 | QString sheet=commentC->styleSheet(); 134 | return QColor(sheet.mid(sheet.indexOf('#'))); 135 | } 136 | 137 | void Post::setColor(QColor color) 138 | { 139 | commentC->setStyleSheet(QString("background-color:%1").arg(color.name())); 140 | } 141 | 142 | void Post::postComment() 143 | { 144 | const Record *r=(const Record *)commentS->currentData().value(); 145 | QNetworkRequest request; 146 | QByteArray data; 147 | const Comment &c=getComment(); 148 | switch(Utils::parseSite(r->source)){ 149 | case Utils::Bilibili: 150 | { 151 | QString api("http://interface.%1/dmpost"); 152 | api=api.arg(Utils::customUrl(Utils::Bilibili)); 153 | request.setUrl(api); 154 | request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded"); 155 | QUrlQuery params; 156 | params.addQueryItem("cid",QFileInfo(r->source).baseName()); 157 | params.addQueryItem("date",QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 158 | params.addQueryItem("pool","0"); 159 | params.addQueryItem("playTime",QString::number((c.time-r->delay)/1000.0,'f',4)); 160 | params.addQueryItem("color",QString::number(c.color)); 161 | params.addQueryItem("fontsize",QString::number(c.font)); 162 | params.addQueryItem("message",c.string); 163 | params.addQueryItem("rnd",QString::number(qrand())); 164 | params.addQueryItem("mode",QString::number(c.mode)); 165 | data=QUrl::toPercentEncoding(params.query(QUrl::FullyEncoded),"%=&","-.~_"); 166 | break; 167 | } 168 | case Utils::AcPlay: 169 | { 170 | QString api("http://api.%1/api/v1/comment/%2"); 171 | api=api.arg(Utils::customUrl(Utils::AcPlay)); 172 | api=api.arg(QFileInfo(r->source).baseName()); 173 | request.setUrl(api); 174 | request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json"); 175 | QJsonObject params; 176 | params["Token"]=0; 177 | params["Time"]=(c.time-r->delay)/1000.0; 178 | params["Mode"]=c.mode; 179 | params["Color"]=c.color; 180 | params["TimeStamp"]=QDateTime::currentMSecsSinceEpoch(); 181 | params["Pool"]=0; 182 | params["UId"]=0; 183 | params["CId"]=0; 184 | params["Message"]=c.string; 185 | data=QJsonDocument(params).toJson(); 186 | break; 187 | } 188 | default: 189 | break; 190 | } 191 | Danmaku::instance()->appendToPool(r->source,&c); 192 | QNetworkReply *reply=manager->post(request,data); 193 | connect(reply,&QNetworkReply::finished,[=](){ 194 | int error=reply->error(); 195 | if (error==QNetworkReply::NoError){ 196 | switch(Utils::parseSite(reply->url().url())) 197 | { 198 | case Utils::Bilibili: 199 | { 200 | error=qMin(QString(reply->readAll()).toInt(),QNetworkReply::NoError); 201 | break; 202 | } 203 | case Utils::AcPlay: 204 | { 205 | QJsonObject o=QJsonDocument::fromJson(reply->readAll()).object(); 206 | error=o["Success"].toBool()?QNetworkReply::NoError:QNetworkReply::UnknownNetworkError; 207 | break; 208 | } 209 | default: 210 | break; 211 | } 212 | } 213 | if(error!=QNetworkReply::NoError){ 214 | QString info=tr("Network error occurred, error code: %1").arg(error); 215 | QString sugg=Local::instance()->suggestion(error); 216 | QMessageBox::warning(lApp->mainWidget(),tr("Network Error"),sugg.isEmpty()?info:(info+'\n'+sugg)); 217 | } 218 | else{ 219 | emit posted(&c); 220 | } 221 | reply->deleteLater(); 222 | }); 223 | } 224 | 225 | bool Post::isValid() 226 | { 227 | return !getRecords().isEmpty(); 228 | } 229 | 230 | void Post::setVisible(bool visible) 231 | { 232 | QWidget::setVisible(visible); 233 | QWidget *fw=visible?commentL:lApp->mainWidget(); 234 | fw->setFocus(); 235 | } -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Utils.cpp 6 | * Time: 2013/05/10 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Utils.h" 28 | #include "Config.h" 29 | #include 30 | 31 | Utils::Site Utils::parseSite(QString url) 32 | { 33 | url=url.toLower(); 34 | if(-1!=url.indexOf("letv")){ 35 | return Letv; 36 | } 37 | if(-1!=url.indexOf("tudou")){ 38 | return Tudou; 39 | } 40 | if(-1!=url.indexOf("bilibili")){ 41 | return Bilibili; 42 | } 43 | if(-1!=url.indexOf("acfun")){ 44 | return AcFun; 45 | } 46 | if(-1!=url.indexOf("acplay")){ 47 | return AcPlay; 48 | } 49 | if(-1!=url.indexOf("tucao")){ 50 | return TuCao; 51 | } 52 | return Unknown; 53 | } 54 | 55 | void Utils::setCenter(QWidget *widget) 56 | { 57 | QRect rect=widget->geometry(); 58 | QWidget *parent=widget->parentWidget(); 59 | if(parent==NULL){ 60 | rect.moveCenter(QApplication::desktop()->screenGeometry(widget).center()); 61 | } 62 | else{ 63 | if(widget->isWindow()){ 64 | QPoint center=parent->geometry().center(); 65 | if((parent->windowFlags()&Qt::CustomizeWindowHint)){ 66 | center.ry()+=widget->style()->pixelMetric(QStyle::PM_TitleBarHeight)/2; 67 | } 68 | rect.moveCenter(center); 69 | } 70 | else{ 71 | rect.moveCenter(parent->rect().center()); 72 | } 73 | } 74 | widget->setGeometry(rect); 75 | } 76 | 77 | void Utils::setGround(QWidget *widget,QColor color) 78 | { 79 | widget->setAutoFillBackground(true); 80 | QPalette palette=widget->palette(); 81 | palette.setColor(QPalette::Window,color); 82 | widget->setPalette(palette); 83 | } 84 | 85 | namespace{ 86 | template 87 | class SStack 88 | { 89 | public: 90 | inline T &top() 91 | { 92 | if(isEmpty()){ 93 | throw("token mismatch"); 94 | } 95 | return stk.top(); 96 | } 97 | 98 | inline T pop() 99 | { 100 | if(isEmpty()){ 101 | throw("token mismatch"); 102 | } 103 | return stk.pop(); 104 | } 105 | 106 | inline void push(const T &i) 107 | { 108 | stk.push(i); 109 | } 110 | 111 | inline bool isEmpty() 112 | { 113 | return stk.isEmpty(); 114 | } 115 | 116 | private: 117 | QStack stk; 118 | }; 119 | } 120 | 121 | double Utils::evaluate(QString exp) 122 | { 123 | auto priority=[](QChar o){ 124 | switch(o.unicode()) 125 | { 126 | case '(': 127 | return 1; 128 | case '+': 129 | case '-': 130 | return 2; 131 | case '*': 132 | case '/': 133 | return 3; 134 | case '+'+128: 135 | case '-'+128: 136 | return 4; 137 | default: 138 | return 0; 139 | } 140 | }; 141 | exp=exp.trimmed(); 142 | try{ 143 | QString pst; 144 | SStack opt; 145 | int i=0; 146 | opt.push('#'); 147 | while(i num; 187 | i=0; 188 | while(pst[i]!='#'){ 189 | if(pst[i].isDigit()||pst[i]=='.'){ 190 | double n=0; 191 | while(pst[i].isDigit()){ 192 | n=n*10+pst[i++].toLatin1()-'0'; 193 | } 194 | if(pst[i]=='.'){ 195 | ++i; 196 | double d=1; 197 | while(pst[i].isDigit()){ 198 | n+=(d/=10)*(pst[i++].toLatin1()-'0'); 199 | } 200 | } 201 | num.push(n); 202 | } 203 | else{ 204 | switch(pst[i].unicode()){ 205 | case '+'+128: 206 | num.push(+num.pop()); 207 | break; 208 | case '-'+128: 209 | num.push(-num.pop()); 210 | break; 211 | case '+': 212 | { 213 | double r=num.pop(),l=num.pop(); 214 | num.push(l+r); 215 | break; 216 | } 217 | case '-': 218 | { 219 | double r=num.pop(),l=num.pop(); 220 | num.push(l-r); 221 | break; 222 | } 223 | case '*': 224 | { 225 | double r=num.pop(),l=num.pop(); 226 | num.push(l*r); 227 | break; 228 | } 229 | case '/': 230 | { 231 | double r=num.pop(),l=num.pop(); 232 | num.push(l/r); 233 | break; 234 | } 235 | } 236 | i++; 237 | } 238 | } 239 | return num.top(); 240 | } 241 | catch(...){ 242 | return 0; 243 | } 244 | } 245 | 246 | QString Utils::defaultFont(bool monospace) 247 | { 248 | if(monospace){ 249 | #ifdef Q_OS_LINUX 250 | return QStringLiteral("文泉驿等宽正黑"); 251 | #endif 252 | #ifdef Q_OS_WIN32 253 | return QStringLiteral("黑体"); 254 | #endif 255 | #ifdef Q_OS_MAC 256 | return QStringLiteral("华文黑体"); 257 | #endif 258 | } 259 | else{ 260 | #ifdef Q_OS_LINUX 261 | return QStringLiteral("文泉驿正黑"); 262 | #endif 263 | #ifdef Q_OS_WIN32 264 | return QStringLiteral("微软雅黑"); 265 | #endif 266 | #ifdef Q_OS_MAC 267 | return QStringLiteral("华文黑体"); 268 | #endif 269 | } 270 | } 271 | 272 | QString Utils::customUrl(Site site) 273 | { 274 | QString name; 275 | switch (site){ 276 | case AcFun: 277 | name="acfun"; 278 | break; 279 | case Bilibili: 280 | name="bili"; 281 | break; 282 | case AcPlay: 283 | name="acplay"; 284 | break; 285 | case Tudou: 286 | name="tudou"; 287 | break; 288 | case Niconico: 289 | name="nico"; 290 | break; 291 | case TuCao: 292 | name="tucao"; 293 | break; 294 | default: 295 | return QString(); 296 | } 297 | QStringList urls,defs; 298 | defs<<"acfun.tv"<<"bilibili.com"<<"acplay.net"<<"tucao.tv"; 299 | urls=Config::getValue("/Network/Url",defs.join(';')).split(';',QString::SkipEmptyParts); 300 | for (QString iter:urls+defs){ 301 | if (iter.toLower().indexOf(name)!=-1){ 302 | return iter; 303 | } 304 | } 305 | return QString(); 306 | } 307 | 308 | QString Utils::decodeXml(QString string,bool fast) 309 | { 310 | if(!fast){ 311 | QTextDocument text; 312 | text.setHtml(string); 313 | return text.toPlainText(); 314 | } 315 | QString fixed; 316 | fixed.reserve(string.length()); 317 | int i=0,l=string.length(); 318 | for(i=0;i=' '||c=='\n'){ 321 | bool f=true; 322 | switch(c.unicode()){ 323 | case '&': 324 | if(l-i>=4){ 325 | switch(string[i+1].unicode()){ 326 | case 'l': 327 | if(string[i+2]=='t'&&string[i+3]==';'){ 328 | fixed+='<'; 329 | f=false; 330 | i+=3; 331 | } 332 | break; 333 | case 'g': 334 | if(string[i+2]=='t'&&string[i+3]==';'){ 335 | fixed+='>'; 336 | f=false; 337 | i+=3; 338 | } 339 | break; 340 | case 'a': 341 | if(l-i>=5&&string[i+2]=='m'&&string[i+3]=='p'&&string[i+4]==';'){ 342 | fixed+='&'; 343 | f=false; 344 | i+=4; 345 | } 346 | break; 347 | case 'q': 348 | if(l-i>=6&&string[i+2]=='u'&&string[i+3]=='o'&&string[i+4]=='t'&&string[i+5]==';'){ 349 | fixed+='\"'; 350 | f=false; 351 | i+=5; 352 | } 353 | break; 354 | } 355 | } 356 | break; 357 | case '/' : 358 | case '\\': 359 | if(l-i>=2){ 360 | switch(string[i+1].unicode()){ 361 | case 'n': 362 | fixed+='\n'; 363 | f=false; 364 | i+=1; 365 | break; 366 | case 't': 367 | fixed+='\t'; 368 | f=false; 369 | i+=1; 370 | break; 371 | case '\"': 372 | fixed+='\"'; 373 | f=false; 374 | i+=1; 375 | break; 376 | } 377 | } 378 | break; 379 | } 380 | if(f){ 381 | fixed+=c; 382 | } 383 | } 384 | } 385 | return fixed; 386 | } 387 | 388 | QStringList Utils::getRenderModules() 389 | { 390 | QStringList modules; 391 | #ifdef RENDER_OPENGL 392 | modules<<"OpenGL"; 393 | #endif 394 | #ifdef RENDER_RASTER 395 | modules<<"Raster"; 396 | #endif 397 | #ifdef RENDER_DETACH 398 | modules<<"Detach"; 399 | #endif 400 | return modules; 401 | } 402 | 403 | QStringList Utils::getDecodeModules() 404 | { 405 | QStringList modules; 406 | #ifdef BACKEND_VLC 407 | modules<<"VLC"; 408 | #endif 409 | #ifdef BACKEND_QMM 410 | modules<<"QMM"; 411 | #endif 412 | #ifdef BACKEND_NIL 413 | modules<<"NIL"; 414 | #endif 415 | return modules; 416 | } 417 | 418 | QStringList Utils::getSuffix(int type,QString format) 419 | { 420 | QStringList set; 421 | if(type&Video){ 422 | set<<"3g2"<<"3gp"<<"3gp2"<<"3gpp"<<"amv"<<"asf"<<"avi"<<"divx"<<"drc"<<"dv"<< 423 | "f4v"<<"flv"<<"gvi"<<"gxf"<<"hlv"<<"iso"<<"letv"<< 424 | "m1v"<<"m2t"<<"m2ts"<<"m2v"<<"m4v"<<"mkv"<<"mov"<< 425 | "mp2"<<"mp2v"<<"mp4"<<"mp4v"<<"mpe"<<"mpeg"<<"mpeg1"<< 426 | "mpeg2"<<"mpeg4"<<"mpg"<<"mpv2"<<"mts"<<"mtv"<<"mxf"<<"mxg"<<"nsv"<<"nuv"<< 427 | "ogg"<<"ogm"<<"ogv"<<"ogx"<<"ps"<< 428 | "rec"<<"rm"<<"rmvb"<<"tod"<<"ts"<<"tts"<<"vob"<<"vro"<< 429 | "webm"<<"wm"<<"wmv"<<"wtv"<<"xesc"; 430 | } 431 | if(type&Audio){ 432 | int size=set.size(); 433 | set<<"3ga"<<"669"<<"a52"<<"aac"<<"ac3"<<"adt"<<"adts"<<"aif"<<"aifc"<<"aiff"<< 434 | "amr"<<"aob"<<"ape"<<"awb"<<"caf"<<"dts"<<"flac"<<"it"<<"kar"<< 435 | "m4a"<<"m4p"<<"m5p"<<"mka"<<"mlp"<<"mod"<<"mp1"<<"mp2"<<"mp3"<<"mpa"<<"mpc"<<"mpga"<< 436 | "oga"<<"ogg"<<"oma"<<"opus"<<"qcp"<<"ra"<<"rmi"<<"s3m"<<"spx"<<"thd"<<"tta"<< 437 | "voc"<<"vqf"<<"w64"<<"wav"<<"wma"<<"wv"<<"xa"<<"xm"; 438 | std::inplace_merge(set.begin(),set.begin()+size,set.end()); 439 | } 440 | if(type&Subtitle){ 441 | int size=set.size(); 442 | set<<"aqt"<<"ass"<<"cdg"<<"dks"<<"idx"<<"jss"<<"mks"<<"mpl2"<<"pjs"<<"psb"<<"rt"<< 443 | "smi"<<"smil"<<"srt"<<"ssa"<<"stl"<<"sub"<<"txt"<<"usf"<<"utf"; 444 | std::inplace_merge(set.begin(),set.begin()+size,set.end()); 445 | } 446 | if(type&Danmaku){ 447 | int size=set.size(); 448 | set<<"json"<<"xml"; 449 | std::inplace_merge(set.begin(),set.begin()+size,set.end()); 450 | } 451 | if(!format.isEmpty()){ 452 | for(QString &iter:set){ 453 | iter=format.arg(iter); 454 | } 455 | } 456 | return set; 457 | } 458 | -------------------------------------------------------------------------------- /src/Info.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Info.cpp 6 | * Time: 2013/04/05 7 | * Author: Lysine 8 | * Contributor: Chaserhkj 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #include "Info.h" 29 | #include "APlayer.h" 30 | #include "Config.h" 31 | #include "Danmaku.h" 32 | #include "Editor.h" 33 | #include "List.h" 34 | #include "Load.h" 35 | #include "Local.h" 36 | #include "Shield.h" 37 | #include "Utils.h" 38 | 39 | namespace 40 | { 41 | class MTableView:public QTableView 42 | { 43 | public: 44 | MTableView(QWidget *parent=0): 45 | QTableView(parent) 46 | { 47 | } 48 | 49 | void currentChanged(const QModelIndex &c, 50 | const QModelIndex &p) 51 | { 52 | QTableView::currentChanged(c,p); 53 | selectionModel()->setCurrentIndex(QModelIndex(),QItemSelectionModel::NoUpdate); 54 | } 55 | }; 56 | 57 | } 58 | 59 | Info::Info(QWidget *parent): 60 | QWidget(parent) 61 | { 62 | setObjectName("Info"); 63 | isStay=isPoped=updating=false; 64 | Utils::setGround(this,Qt::white); 65 | duration=-1; 66 | animation=new QPropertyAnimation(this,"pos",this); 67 | animation->setDuration(200); 68 | animation->setEasingCurve(QEasingCurve::OutCubic); 69 | timeT=new QLabel(tr("Time"),this); 70 | volmT=new QLabel(tr("Volume"),this); 71 | timeS=new QSlider(this); 72 | volmS=new QSlider(this); 73 | timeS->setOrientation(Qt::Horizontal); 74 | volmS->setOrientation(Qt::Horizontal); 75 | timeS->setRange(0,0); 76 | volmS->setRange(0,100); 77 | timeS->setValue(0); 78 | volmS->setValue(Config::getValue("/Playing/Volume",50)); 79 | timeS->setTracking(false); 80 | connect(timeS,&QSlider::valueChanged,[this](int _time){ 81 | if(duration!=-1&&!updating){ 82 | APlayer::instance()->setTime(duration*_time/400); 83 | } 84 | }); 85 | connect(volmS,&QSlider::sliderMoved,[this](int _volm){ 86 | QPoint p; 87 | p.setX(QCursor::pos().x()); 88 | p.setY(volmS->mapToGlobal(volmS->rect().center()).y()); 89 | QToolTip::showText(p,QString::number(_volm)); 90 | }); 91 | connect(volmS,&QSlider::valueChanged,[this](int _volm){ 92 | if(!updating){ 93 | APlayer::instance()->setVolume(_volm); 94 | } 95 | }); 96 | playB=new QPushButton(this); 97 | stopB=new QPushButton(this); 98 | playI=QIcon::fromTheme("media-playback-start",QIcon(":/Picture/play.png")); 99 | stopI=QIcon::fromTheme("media-playback-stop",QIcon(":/Picture/stop.png")); 100 | pausI=QIcon::fromTheme("media-playback-pause",QIcon(":/Picture/pause.png")); 101 | playB->setIcon(playI); 102 | stopB->setIcon(stopI); 103 | playA=new QAction(playI,tr("Play"),this); 104 | stopA=new QAction(stopI,tr("Stop"),this); 105 | playA->setObjectName("Play"); 106 | stopA->setObjectName("Stop"); 107 | QList playS; 108 | playS<setShortcuts(playS); 113 | stopA->setShortcut(Config::getValue("/Shortcut/Stop",QString())); 114 | addAction(playA); 115 | addAction(stopA); 116 | connect(playA,SIGNAL(triggered()),APlayer::instance(),SLOT(play())); 117 | connect(stopA,SIGNAL(triggered()),APlayer::instance(),SLOT(stop())); 118 | connect(playB,&QPushButton::clicked,playA,&QAction::trigger); 119 | connect(stopB,&QPushButton::clicked,stopA,&QAction::trigger); 120 | duraT=new QLabel(this); 121 | duraT->setAlignment(Qt::AlignRight|Qt::AlignBottom); 122 | duraT->setText("00:00/00:00"); 123 | danmV=new MTableView(this); 124 | danmV->setWordWrap(false); 125 | danmV->setSelectionBehavior(QAbstractItemView::SelectRows); 126 | danmV->setSelectionMode(QAbstractItemView::ExtendedSelection); 127 | danmV->verticalHeader()->hide(); 128 | danmV->setAlternatingRowColors(true); 129 | danmV->setContextMenuPolicy(Qt::CustomContextMenu); 130 | danmV->setModel(Danmaku::instance()); 131 | QHeaderView *header; 132 | header=danmV->horizontalHeader(); 133 | header->setSectionResizeMode(0,QHeaderView::Fixed); 134 | header->setSectionResizeMode(1,QHeaderView::Stretch); 135 | header->setHighlightSections(false); 136 | resizeHeader(); 137 | header=danmV->verticalHeader(); 138 | header->setDefaultSectionSize(22.5*logicalDpiY()/72); 139 | connect(Danmaku::instance(),&Danmaku::layoutChanged,this,&Info::resizeHeader); 140 | connect(danmV,&QTableView::doubleClicked,[this](QModelIndex index){ 141 | APlayer::instance()->setTime(((Comment *)(index.data(Qt::UserRole).value()))->time); 142 | }); 143 | 144 | QAction *saveA=new QAction(tr("Save Danmaku to File"),this); 145 | saveA->setObjectName("Save"); 146 | saveA->setShortcut(Config::getValue("/Shortcut/Save",QString())); 147 | saveA->setEnabled(false); 148 | connect(saveA,&QAction::triggered,[](){ 149 | QFileDialog save(lApp->mainWidget(),tr("Save File")); 150 | save.setAcceptMode(QFileDialog::AcceptSave); 151 | QFileInfo info(APlayer::instance()->getMedia()); 152 | if(info.isFile()){ 153 | save.setDirectory(info.absolutePath()); 154 | save.selectFile(info.completeBaseName()); 155 | } 156 | else{ 157 | save.setDirectory(List::instance()->defaultPath(Utils::Danmaku)); 158 | } 159 | save.setDefaultSuffix("json"); 160 | QStringList type; 161 | type<saveToFile(file.first()); 170 | } 171 | } 172 | }); 173 | danmV->addAction(saveA); 174 | 175 | QAction *fullA=new QAction(tr("Full Danmaku"),this); 176 | fullA->setObjectName("Char"); 177 | fullA->setShortcut(Config::getValue("/Shortcut/Char",QString())); 178 | fullA->setEnabled(false); 179 | connect(fullA,&QAction::triggered,[](){ 180 | Load *load=Load::instance(); 181 | for(const Record &r:Danmaku::instance()->getPool()){ 182 | if (load->canFull(&r)){ 183 | load->fullDanmaku(&r); 184 | } 185 | } 186 | }); 187 | danmV->addAction(fullA); 188 | 189 | connect(Danmaku::instance(),&Danmaku::modelReset,[=](){ 190 | const QList &pool=Danmaku::instance()->getPool(); 191 | fullA->setEnabled(false); 192 | for(const Record &r:pool){ 193 | if (Load::instance()->canFull(&r)){ 194 | fullA->setEnabled(true); 195 | break; 196 | } 197 | } 198 | saveA->setEnabled(!pool.isEmpty()); 199 | }); 200 | 201 | connect(danmV,&QTableView::customContextMenuRequested,[=](QPoint p){ 202 | QMenu menu(this); 203 | QListselected; 204 | for(const QModelIndex &index:danmV->selectionModel()->selectedRows()){ 205 | selected.append((Comment *)index.data(Qt::UserRole).value()); 206 | } 207 | if(!selected.isEmpty()){ 208 | connect(menu.addAction(tr("Copy Danmaku")),&QAction::triggered,[&](){ 209 | QStringList list; 210 | for(const Comment *c:selected){ 211 | list.append(c->string); 212 | } 213 | qApp->clipboard()->setText(list.join('\n')); 214 | }); 215 | connect(menu.addAction(tr("Eliminate The Sender")),&QAction::triggered,[&](){ 216 | for(const Comment *c:selected){ 217 | QString sender=c->sender; 218 | if(!sender.isEmpty()){ 219 | Shield::shieldS.insert(sender); 220 | } 221 | } 222 | Danmaku::instance()->parse(0x2); 223 | }); 224 | bool flag=false; 225 | for(const Comment *c:selected){ 226 | if(Shield::shieldS.contains(c->sender)){ 227 | flag=true; 228 | break; 229 | } 230 | } 231 | if(flag){ 232 | connect(menu.addAction(tr("Recover The Sender")),&QAction::triggered,[&](){ 233 | for(const Comment *c:selected){ 234 | Shield::shieldS.remove(c->sender); 235 | } 236 | Danmaku::instance()->parse(0x2); 237 | }); 238 | } 239 | menu.addSeparator(); 240 | } 241 | menu.addAction(fullA); 242 | connect(menu.addAction(tr("Edit Blocking List")),&QAction::triggered,[this](){ 243 | Config::exec(lApp->mainWidget(),3); 244 | }); 245 | connect(menu.addAction(tr("Edit Danmaku Pool" )),&QAction::triggered,[this](){ 246 | Editor::exec(lApp->mainWidget()); 247 | }); 248 | connect(menu.addAction(tr("Clear Danmaku Pool")),&QAction::triggered,Danmaku::instance(),&Danmaku::clearPool); 249 | menu.addAction(saveA); 250 | isStay=1; 251 | menu.exec(danmV->viewport()->mapToGlobal(p)); 252 | isStay=0; 253 | }); 254 | 255 | animation=new QPropertyAnimation(this,"pos",this); 256 | animation->setDuration(200); 257 | animation->setEasingCurve(QEasingCurve::OutCubic); 258 | connect(animation,&QPropertyAnimation::finished,[this](){ 259 | if(!isPoped){ 260 | hide(); 261 | lApp->mainWidget()->setFocus(); 262 | } 263 | }); 264 | 265 | connect(APlayer::instance(),&APlayer::timeChanged,this,&Info::setTime); 266 | connect(APlayer::instance(),&APlayer::volumeChanged,[this](int volume){ 267 | updating=1; 268 | volmS->setValue(volume); 269 | updating=0; 270 | }); 271 | connect(APlayer::instance(),&APlayer::begin,[this](){ 272 | setDuration(APlayer::instance()->getDuration()); 273 | }); 274 | connect(APlayer::instance(),&APlayer::reach,[this](){ 275 | setDuration(-1); 276 | }); 277 | connect(APlayer::instance(),&APlayer::stateChanged,[this](int state){ 278 | bool playing=state==APlayer::Play; 279 | playB->setIcon(playing?pausI:playI); 280 | playA->setIcon(playing?pausI:playI); 281 | playA->setText(playing?tr("Pause"):tr("Play")); 282 | }); 283 | hide(); 284 | } 285 | 286 | void Info::pop() 287 | { 288 | if(!isPoped&&animation->state()==QAbstractAnimation::Stopped){ 289 | show(); 290 | animation->setStartValue(pos()); 291 | animation->setEndValue(pos()-QPoint(width(),0)); 292 | animation->start(); 293 | isPoped=true; 294 | } 295 | } 296 | 297 | void Info::push(bool force) 298 | { 299 | if(isPoped&&animation->state()==QAbstractAnimation::Stopped&&(!preferStay()||force)){ 300 | if(force){ 301 | isStay=false; 302 | } 303 | animation->setStartValue(pos()); 304 | animation->setEndValue(pos()+QPoint(width(),0)); 305 | animation->start(); 306 | isPoped=false; 307 | } 308 | } 309 | 310 | void Info::terminate() 311 | { 312 | if(animation->state()!=QAbstractAnimation::Stopped){ 313 | animation->setCurrentTime(animation->totalDuration()); 314 | } 315 | } 316 | 317 | void Info::resizeEvent(QResizeEvent *e) 318 | { 319 | int w=e->size().width(),h=e->size().height(); 320 | double f=font().pointSizeF(); 321 | int x=logicalDpiX()*f/72,y=logicalDpiY()*f/72; 322 | playB->setGeometry(QRect(0.83*x,1.25*y, 2.08*x, 2.08*y)); 323 | stopB->setGeometry(QRect(3.33*x,1.25*y, 2.08*x, 2.08*y)); 324 | duraT->setGeometry(QRect(5.83*x,1.25*y, w-6.67*x,2.08*y)); 325 | timeT->setGeometry(QRect(0.83*x,4.17*y, w-1.67*x,2.08*y)); 326 | timeS->setGeometry(QRect(0.83*x,6.25*y, w-1.67*x,1.25*y)); 327 | volmT->setGeometry(QRect(0.83*x,8.33*y, w-1.67*x,2.08*y)); 328 | volmS->setGeometry(QRect(0.83*x,10.42*y,w-1.67*x,1.25*y)); 329 | danmV->setGeometry(QRect(0.83*x,14.17*y,w-1.67*x,h-15.00*y)); 330 | QWidget::resizeEvent(e); 331 | } 332 | 333 | void Info::resizeHeader() 334 | { 335 | Danmaku *d=Danmaku::instance(); 336 | QStringList list; 337 | list.append(d->headerData(0,Qt::Horizontal,Qt::DisplayRole).toString()); 338 | int c=d->rowCount()-1,i; 339 | for(i=0;i<=c;++i){ 340 | if(d->data(d->index(i,0),Qt::ForegroundRole).value()!=Qt::red){ 341 | list.append(d->data(d->index(i,0),Qt::DisplayRole).toString()); 342 | break; 343 | } 344 | } 345 | for(i=c;i>=0;--i){ 346 | if(d->data(d->index(i,0),Qt::ForegroundRole).value()!=Qt::red){ 347 | list.append(d->data(d->index(i,0),Qt::DisplayRole).toString()); 348 | break; 349 | } 350 | } 351 | int m=0; 352 | for(QString item:list){ 353 | m=qMax(m,danmV->fontMetrics().width(item)+8); 354 | } 355 | danmV->horizontalHeader()->resizeSection(0,m); 356 | } 357 | 358 | void Info::setTime(qint64 _time) 359 | { 360 | if(!timeS->isSliderDown()){ 361 | updating=1; 362 | timeS->setValue(_time*400/duration); 363 | updating=0; 364 | } 365 | int c=_time/1000; 366 | int s=duration/1000; 367 | auto to=[](int num){ 368 | QString res=QString::number(num); 369 | res.prepend(QString(2-res.length(),'0')); 370 | return res; 371 | }; 372 | QString t; 373 | t+=to(c/60)+':'+to(c%60); 374 | t+='/'; 375 | t+=to(s/60)+':'+to(s%60); 376 | duraT->setText(t); 377 | } 378 | 379 | void Info::setDuration(qint64 _duration) 380 | { 381 | if(_duration>0){ 382 | duration=_duration; 383 | timeS->setRange(0,400); 384 | } 385 | else{ 386 | duration=-1; 387 | timeS->setValue(0); 388 | timeS->setRange(0,0); 389 | duraT->setText("00:00/00:00"); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/Graphic.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Graphic.cpp 6 | * Time: 2013/10/19 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Graphic.h" 28 | #include "Config.h" 29 | #include "Render.h" 30 | 31 | class Plain:public Graphic 32 | { 33 | public: 34 | virtual void draw(QPainter *painter); 35 | 36 | protected: 37 | explicit Plain(const Comment &comment); 38 | ~Plain(){delete cache;} 39 | Render::ICache *cache; 40 | }; 41 | 42 | class Mode1:public Plain 43 | { 44 | public: 45 | Mode1(const Comment &comment); 46 | bool move(qint64 time); 47 | uint intersects(Graphic *other); 48 | 49 | private: 50 | double speed; 51 | }; 52 | 53 | class Mode4:public Plain 54 | { 55 | public: 56 | Mode4(const Comment &comment); 57 | bool move(qint64 time); 58 | uint intersects(Graphic *other); 59 | 60 | private: 61 | double life; 62 | }; 63 | 64 | class Mode5:public Plain 65 | { 66 | public: 67 | Mode5(const Comment &comment); 68 | bool move(qint64 time); 69 | uint intersects(Graphic *other); 70 | 71 | private: 72 | double life; 73 | }; 74 | 75 | class Mode6:public Plain 76 | { 77 | public: 78 | Mode6(const Comment &comment); 79 | bool move(qint64 time); 80 | uint intersects(Graphic *other); 81 | 82 | private: 83 | double speed; 84 | }; 85 | 86 | class Mode7:public Graphic 87 | { 88 | public: 89 | Mode7(const Comment &comment); 90 | bool move(qint64 time); 91 | void draw(QPainter *painter); 92 | uint intersects(Graphic *){return 0;} 93 | 94 | private: 95 | QPointF bPos; 96 | QPointF ePos; 97 | double bAlpha; 98 | double eAlpha; 99 | double zRotate; 100 | double yRotate; 101 | QImage cache; 102 | double wait; 103 | double stay; 104 | double life; 105 | double time; 106 | }; 107 | 108 | namespace 109 | { 110 | QFont getFont(int pixelSize,QString family=Config::getValue("/Danmaku/Font",QFont().family())) 111 | { 112 | QFont font; 113 | font.setBold(Config::getValue("/Danmaku/Effect",5)%2); 114 | font.setFamily(family); 115 | font.setPixelSize(pixelSize); 116 | return font; 117 | } 118 | 119 | QSize getSize(QString string,QFont font) 120 | { 121 | QStringList lines=string.split('\n'); 122 | for(QString &line:lines){ 123 | QChar h=' ',f(0x3000); 124 | int hc=line.count(h),fc=line.count(f); 125 | line.remove(h).prepend(QString(hc,h)); 126 | line.remove(f).prepend(QString(fc,f)); 127 | } 128 | return QFontMetrics(font).size(0,lines.join('\n'))+QSize(4,4); 129 | } 130 | 131 | QSizeF getPlayer(qint64 date) 132 | { 133 | return date<=1384099200?QSizeF(545,388):QSizeF(862,568); 134 | } 135 | 136 | double getScale(int mode,qint64 date,QSize size) 137 | { 138 | int m=Config::getValue("/Danmaku/Scale/Fitted",0x1); 139 | if(mode==7&&(m&0x1)==0){ 140 | return 0; 141 | } 142 | if(mode<=6&&(m&0x2)==0){ 143 | return Config::getValue("/Danmaku/Scale/Factor",1.0); 144 | } 145 | QSizeF player=getPlayer(date); 146 | return qMin(size.width()/player.width(),size.height()/player.height()); 147 | } 148 | } 149 | 150 | void qt_blurImage(QPainter *p,QImage &blurImage,qreal radius, 151 | bool quality,bool alphaOnly,int transposed); 152 | 153 | namespace 154 | { 155 | QImage getCache(QString string, 156 | int color, 157 | QFont font, 158 | QSize size, 159 | bool frame, 160 | int effect=Config::getValue("/Danmaku/Effect",5)/2, 161 | int opacity=Config::getValue("/Danmaku/Alpha",100)) 162 | { 163 | QPainter painter; 164 | QColor base(color),edge=qGray(color)<30?Qt::white:Qt::black; 165 | QImage src(size,QImage::Format_ARGB32_Premultiplied); 166 | src.fill(Qt::transparent); 167 | painter.begin(&src); 168 | painter.setPen(base); 169 | painter.setFont(font); 170 | painter.drawText(src.rect().adjusted(2,2,-2,-2),string); 171 | painter.end(); 172 | QImage fst(size,QImage::Format_ARGB32_Premultiplied); 173 | fst.fill(Qt::transparent); 174 | if(effect==2){ 175 | QImage blr=src; 176 | painter.begin(&fst); 177 | painter.save(); 178 | qt_blurImage(&painter,blr,4,false,true,0); 179 | painter.restore(); 180 | painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 181 | painter.fillRect(src.rect(),edge); 182 | painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 183 | painter.drawImage(0,0,src); 184 | painter.end(); 185 | QImage sec(size,QImage::Format_ARGB32_Premultiplied); 186 | sec.fill(Qt::transparent); 187 | painter.begin(&sec); 188 | blr=fst; 189 | painter.save(); 190 | qt_blurImage(&painter,blr,4,false,true,0); 191 | painter.restore(); 192 | painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 193 | painter.fillRect(sec.rect(),edge); 194 | painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 195 | painter.drawImage(0,0,fst); 196 | painter.end(); 197 | fst=sec; 198 | } 199 | else{ 200 | QImage edg=src; 201 | painter.begin(&edg); 202 | painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 203 | painter.fillRect(edg.rect(),edge); 204 | painter.end(); 205 | painter.begin(&fst); 206 | switch(effect){ 207 | case 0: 208 | painter.drawImage(+1,0,edg); 209 | painter.drawImage(-1,0,edg); 210 | painter.drawImage(0,+1,edg); 211 | painter.drawImage(0,-1,edg); 212 | break; 213 | case 1: 214 | painter.drawImage(1,1,edg); 215 | break; 216 | } 217 | painter.drawImage(0,0,src); 218 | painter.end(); 219 | } 220 | if(frame){ 221 | painter.begin(&fst); 222 | painter.setPen(QColor(100,255,255)); 223 | painter.setBrush(Qt::NoBrush); 224 | painter.drawRect(fst.rect().adjusted(0,0,-1,-1)); 225 | painter.end(); 226 | } 227 | if(opacity!=100){ 228 | QImage sec(size,QImage::Format_ARGB32_Premultiplied); 229 | sec.fill(Qt::transparent); 230 | painter.begin(&sec); 231 | painter.setOpacity(opacity/100.0); 232 | painter.drawImage(QPoint(0,0),fst); 233 | painter.end(); 234 | fst=sec; 235 | } 236 | return fst; 237 | } 238 | 239 | double getOverlap(double ff,double fs,double sf,double ss) 240 | { 241 | if(sf<=ff&&ss>=fs){ 242 | return fs-ff; 243 | } 244 | if(sf>=ff&&sf<=fs){ 245 | return qMin(fs-sf,ss-sf); 246 | } 247 | if(ss<=fs&&ss>=ff){ 248 | return qMin(ss-ff,ss-sf); 249 | } 250 | return 0; 251 | } 252 | } 253 | 254 | Graphic *Graphic::create(const Comment &comment) 255 | { 256 | Graphic *graphic=nullptr; 257 | switch(comment.mode){ 258 | case 1: 259 | graphic=new Mode1(comment); 260 | break; 261 | case 4: 262 | graphic=new Mode4(comment); 263 | break; 264 | case 5: 265 | graphic=new Mode5(comment); 266 | break; 267 | case 6: 268 | graphic=new Mode6(comment); 269 | break; 270 | case 7: 271 | graphic=new Mode7(comment); 272 | break; 273 | } 274 | if (graphic&&!graphic->isEnabled()){ 275 | delete graphic; 276 | return nullptr; 277 | } 278 | else{ 279 | return graphic; 280 | } 281 | } 282 | 283 | void Graphic::setIndex() 284 | { 285 | static quint64 globalIndex; 286 | index=globalIndex++; 287 | } 288 | 289 | Plain::Plain(const Comment &comment) 290 | { 291 | QSize size=Render::instance()->getActualSize(); 292 | source=&comment; 293 | QFont font=getFont(comment.font*getScale(comment.mode,comment.date,size)); 294 | QSize need=getSize(comment.string,font); 295 | rect.setSize(need); 296 | cache=Render::instance()->getCache(getCache(comment.string,comment.color,font,need,comment.isLocal())); 297 | } 298 | 299 | void Plain::draw(QPainter *painter) 300 | { 301 | if(enabled){ 302 | cache->draw(painter,rect); 303 | } 304 | } 305 | 306 | Mode1::Mode1(const Comment &comment): 307 | Plain(comment) 308 | { 309 | if(comment.mode!=1){ 310 | return; 311 | } 312 | QSize size=Render::instance()->getActualSize(); 313 | QString expression=Config::getValue("/Danmaku/Speed","125+%{width}/5"); 314 | expression.replace("%{width}",QString::number(rect.width()),Qt::CaseInsensitive); 315 | if((speed=Utils::evaluate(expression))==0){ 316 | return; 317 | } 318 | rect.moveTopLeft(QPointF(size.width(),0)); 319 | enabled=true; 320 | } 321 | 322 | bool Mode1::move(qint64 time) 323 | { 324 | if(enabled){ 325 | rect.moveLeft(rect.left()-speed*time/1000.0); 326 | } 327 | return rect.right()>=0; 328 | } 329 | 330 | uint Mode1::intersects(Graphic *other) 331 | { 332 | if(other->getMode()!=1){ 333 | return 0; 334 | } 335 | const Mode1 &f=*dynamic_cast(other); 336 | const Mode1 &s=*this; 337 | int h; 338 | if((h=getOverlap(f.rect.top(),f.rect.bottom(),s.rect.top(),s.rect.bottom()))==0){ 339 | return 0; 340 | } 341 | int w=0; 342 | if(f.rect.intersects(s.rect)){ 343 | if(f.speed>s.speed){ 344 | w=getOverlap(f.rect.left(),f.rect.right(),s.rect.left(),s.rect.right()); 345 | } 346 | else{ 347 | w=qMin(f.rect.width(),s.rect.width()); 348 | } 349 | } 350 | else{ 351 | double o=f.rect.right()-f.speed*s.rect.left()/s.speed; 352 | w=o>0?qMin(qMin(f.rect.width(),s.rect.width()),o):0; 353 | } 354 | return h*w; 355 | } 356 | 357 | Mode4::Mode4(const Comment &comment): 358 | Plain(comment) 359 | { 360 | if(comment.mode!=4){ 361 | return; 362 | } 363 | QSize size=Render::instance()->getActualSize(); 364 | QString expression=Config::getValue("/Danmaku/Life","5"); 365 | expression.replace("%{width}",QString::number(rect.width()),Qt::CaseInsensitive); 366 | if((life=Utils::evaluate(expression))==0){ 367 | return; 368 | } 369 | rect.moveCenter(QPointF(size.width()/2.0,0)); 370 | rect.moveBottom(size.height()*(Config::getValue("/Danmaku/Protect",false)?0.85:1)); 371 | enabled=true; 372 | } 373 | 374 | bool Mode4::move(qint64 time) 375 | { 376 | if(enabled){ 377 | life-=time/1000.0; 378 | } 379 | return life>0; 380 | } 381 | 382 | uint Mode4::intersects(Graphic *other) 383 | { 384 | if(other->getMode()!=4){ 385 | return 0; 386 | } 387 | const Mode4 &f=*this; 388 | const Mode4 &s=*dynamic_cast(other); 389 | return getOverlap(f.rect.top(),f.rect.bottom(),s.rect.top(),s.rect.bottom())*qMin(f.rect.width(),s.rect.width()); 390 | } 391 | 392 | Mode5::Mode5(const Comment &comment): 393 | Plain(comment) 394 | { 395 | if(comment.mode!=5){ 396 | return; 397 | } 398 | QSize size=Render::instance()->getActualSize(); 399 | QSizeF bound=rect.size(); 400 | QString expression=Config::getValue("/Danmaku/Life","5"); 401 | expression.replace("%{width}",QString::number(bound.width()),Qt::CaseInsensitive); 402 | if((life=Utils::evaluate(expression))==0){ 403 | return; 404 | } 405 | rect.moveCenter(QPointF(size.width()/2.0,0)); 406 | rect.moveTop(0); 407 | enabled=true; 408 | } 409 | 410 | bool Mode5::move(qint64 time) 411 | { 412 | if(enabled){ 413 | life-=time/1000.0; 414 | } 415 | return life>0; 416 | } 417 | 418 | uint Mode5::intersects(Graphic *other) 419 | { 420 | if(other->getMode()!=5){ 421 | return 0; 422 | } 423 | const Mode5 &f=*this; 424 | const Mode5 &s=*dynamic_cast(other); 425 | return getOverlap(f.rect.top(),f.rect.bottom(),s.rect.top(),s.rect.bottom())*qMin(f.rect.width(),s.rect.width()); 426 | } 427 | 428 | Mode6::Mode6(const Comment &comment): 429 | Plain(comment) 430 | { 431 | if(comment.mode!=6){ 432 | return; 433 | } 434 | QString expression=Config::getValue("/Danmaku/Speed","125+%{width}/5"); 435 | expression.replace("%{width}",QString::number(rect.width()),Qt::CaseInsensitive); 436 | if((speed=Utils::evaluate(expression))==0){ 437 | return; 438 | } 439 | rect.moveTopLeft(QPointF(-rect.width(),0)); 440 | enabled=true; 441 | } 442 | 443 | bool Mode6::move(qint64 time) 444 | { 445 | QSize size=Render::instance()->getActualSize(); 446 | if(enabled){ 447 | rect.moveLeft(rect.left()+speed*time/1000.0); 448 | } 449 | return rect.left()<=size.width(); 450 | } 451 | 452 | uint Mode6::intersects(Graphic *other) 453 | { 454 | if(other->getMode()!=6){ 455 | return 0; 456 | } 457 | const Mode6 &f=*dynamic_cast(other); 458 | const Mode6 &s=*this; 459 | int h; 460 | if((h=getOverlap(f.rect.top(),f.rect.bottom(),s.rect.top(),s.rect.bottom()))==0){ 461 | return 0; 462 | } 463 | int w=0; 464 | if(f.rect.intersects(s.rect)){ 465 | if(f.speed>s.speed){ 466 | w=getOverlap(f.rect.left(),f.rect.right(),s.rect.left(),s.rect.right()); 467 | } 468 | else{ 469 | w=qMin(f.rect.width(),s.rect.width()); 470 | } 471 | } 472 | else{ 473 | double o=f.rect.left()-f.speed*s.rect.right()/s.speed; 474 | w=o>0?qMin(qMin(f.rect.width(),s.rect.width()),o):0; 475 | } 476 | return h*w; 477 | } 478 | 479 | Mode7::Mode7(const Comment &comment) 480 | { 481 | if(comment.mode!=7){ 482 | return; 483 | } 484 | QJsonArray data=QJsonDocument::fromJson(comment.string.toUtf8()).array(); 485 | int l; 486 | if((l=data.size())<5){ 487 | return; 488 | } 489 | QSize size=Render::instance()->getActualSize(); 490 | auto getDouble=[&data](int i){return data.at(i).toVariant().toDouble();}; 491 | double scale=getScale(comment.mode,comment.date,size); 492 | bPos=QPointF(getDouble(0),getDouble(1)); 493 | ePos=l<8?bPos:QPointF(getDouble(7),getDouble(8)); 494 | int w=size.width(),h=size.height(); 495 | if(bPos.x()<1&&bPos.y()<1&&ePos.x()<1&&ePos.y()<1){ 496 | bPos.rx()*=w; 497 | ePos.rx()*=w; 498 | bPos.ry()*=h; 499 | ePos.ry()*=h; 500 | scale=1; 501 | } 502 | else if(scale==0){ 503 | scale=1; 504 | } 505 | else{ 506 | QSizeF player=getPlayer(comment.date); 507 | QPoint offset=QPoint((w-player.width()*scale)/2,(h-player.height()*scale)/2); 508 | bPos=bPos*scale+offset; 509 | ePos=ePos*scale+offset; 510 | } 511 | QStringList alpha=data[2].toString().split('-'); 512 | bAlpha=alpha[0].toDouble(); 513 | eAlpha=alpha[1].toDouble(); 514 | life=getDouble(3); 515 | QJsonValue v=l<12?QJsonValue(true):data[11]; 516 | int effect=(v.isString()?v.toString()=="true":v.toVariant().toBool())?Config::getValue("/Danmaku/Effect",5)/2:-1; 517 | QFont font=getFont(scale?comment.font*scale:comment.font,l<13?Utils::defaultFont(true):data[12].toString()); 518 | QString string=data[4].toString(); 519 | cache=getCache(string,comment.color,font,getSize(string,font),comment.isLocal(),effect,100); 520 | zRotate=l<6?0:getDouble(5); 521 | yRotate=l<7?0:getDouble(6); 522 | wait=l<11?0:getDouble(10)/1000; 523 | stay=l<10?0:life-wait-getDouble(9)/1000; 524 | enabled=true; 525 | source=&comment; 526 | time=0; 527 | } 528 | 529 | bool Mode7::move(qint64 time) 530 | { 531 | if(enabled){ 532 | this->time+=time/1000.0; 533 | } 534 | return (this->time)<=life; 535 | } 536 | 537 | void Mode7::draw(QPainter *painter) 538 | { 539 | if(enabled){ 540 | QPointF cPos=bPos+(ePos-bPos)*qBound(0,(time-wait)/(life-stay),1); 541 | QTransform rotate; 542 | rotate.translate(+cPos.x(),+cPos.y()); 543 | rotate.rotate(yRotate,Qt::YAxis); 544 | rotate.rotate(zRotate,Qt::ZAxis); 545 | rotate.translate(-cPos.x(),-cPos.y()); 546 | painter->save(); 547 | painter->setTransform(rotate); 548 | painter->setOpacity(bAlpha+(eAlpha-bAlpha)*time/life); 549 | painter->drawImage(cPos,cache); 550 | painter->restore(); 551 | } 552 | } 553 | -------------------------------------------------------------------------------- /src/List.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: List.cpp 6 | * Time: 2014/11/19 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "List.h" 28 | #include "APlayer.h" 29 | #include "Danmaku.h" 30 | #include "Config.h" 31 | #include "Load.h" 32 | #include "Local.h" 33 | #include 34 | 35 | List *List::ins=nullptr; 36 | 37 | List *List::instance() 38 | { 39 | return ins?ins:new List(qApp); 40 | } 41 | 42 | namespace 43 | { 44 | class IconEngine:public QIconEngine 45 | { 46 | public: 47 | explicit IconEngine(int state): 48 | state(state) 49 | { 50 | } 51 | 52 | void paint(QPainter *p,const QRect &r,QIcon::Mode,QIcon::State) 53 | { 54 | switch(state){ 55 | case 0: 56 | p->drawPolyline(QPolygon({(r.topRight()+r.bottomRight())/2,r.center(),(r.bottomLeft()+r.bottomRight())/2})); 57 | break; 58 | case 1: 59 | p->drawLine((r.topRight()+r.topLeft())/2,(r.bottomLeft()+r.bottomRight())/2); 60 | break; 61 | case 2: 62 | p->drawPolyline(QPolygon({(r.topRight()+r.bottomRight())/2,r.center(),(r.topLeft() +r.topRight()) /2})); 63 | break; 64 | default: 65 | break; 66 | } 67 | } 68 | 69 | QIconEngine *clone() const 70 | { 71 | return new IconEngine(state); 72 | } 73 | 74 | private: 75 | int state; 76 | }; 77 | 78 | QModelIndex getGroupHead(QModelIndex i) 79 | { 80 | QModelIndex f; 81 | for(int o=0;;++o){ 82 | f=i.sibling(i.row()-o,0); 83 | if (f.data(List::CodeRole).toInt()==List::Records||f.row()==0){ 84 | return f; 85 | } 86 | } 87 | } 88 | 89 | QModelIndex getGroupTail(QModelIndex i) 90 | { 91 | for(;;){ 92 | QModelIndex j=i.sibling(i.row()+1,0); 93 | if (j.isValid()&&j.data(List::CodeRole).toInt()!=List::Records){ 94 | i=j; 95 | } 96 | else{ 97 | return i; 98 | } 99 | } 100 | } 101 | 102 | void clearItemGroup(int row) 103 | { 104 | QStandardItem *i=List::instance()->item(row); 105 | i->setIcon(QIcon()); 106 | i->setData(List::Records,List::CodeRole); 107 | } 108 | } 109 | 110 | List::List(QObject *parent): 111 | QStandardItemModel(parent) 112 | { 113 | ins=this; 114 | setObjectName("List"); 115 | cur=nullptr; 116 | time=0; 117 | for(int i=0;i<3;++i){ 118 | icons.append(QIcon(new IconEngine(i))); 119 | } 120 | QStandardItem *lastE=nullptr; 121 | QStandardItem *lastD=nullptr; 122 | int lastC=0; 123 | auto conbine=[&](){ 124 | if (lastE&&lastD){ 125 | QModelIndexList indexes; 126 | for(int i=lastE->row();i<=lastD->row();++i){ 127 | indexes.append(index(i,0)); 128 | } 129 | setRelated(indexes,lastC); 130 | } 131 | }; 132 | for(const QJsonValue &i:Config::getValue("/Playing/List")){ 133 | QStandardItem *item=new QStandardItem; 134 | QJsonObject data=i.toObject(); 135 | QFileInfo info(data["File"].toString()); 136 | item->setData(QColor(info.isFile()?Qt::black:Qt::gray),Qt::ForegroundRole); 137 | item->setText(info.completeBaseName()); 138 | item->setData(info.absoluteFilePath(),FileRole); 139 | item->setData(data["Time"].toDouble(),TimeRole); 140 | item->setData(data["Date"].toString(),DateRole); 141 | item->setEditable(false);item->setDropEnabled(false); 142 | QJsonValue danm=data["Danm"]; 143 | if (danm.isArray()){ 144 | for(const QJsonValue &j:danm.toArray()){ 145 | QStandardItem *c=new QStandardItem; 146 | QJsonObject d=j.toObject(); 147 | c->setData(d["Code"].toString(),CodeRole); 148 | c->setData(d["Time"].toDouble(),TimeRole); 149 | item->appendRow(c); 150 | } 151 | conbine(); 152 | lastE=item; 153 | } 154 | else{ 155 | lastC=data["Danm"].toString()=="Inherit"?Inherit:Surmise; 156 | lastD=item; 157 | } 158 | appendRow(item); 159 | } 160 | conbine(); 161 | 162 | connect(APlayer::instance(),&APlayer::timeChanged, [this](qint64 _time){ 163 | time=_time; 164 | }); 165 | connect(APlayer::instance(),&APlayer::mediaChanged,[this](QString file){ 166 | updateCurrent(); 167 | QStandardItem *old=cur; 168 | cur=itemFromFile(file,true); 169 | if (old){ 170 | old->setData(QColor(Qt::black),Qt::ForegroundRole); 171 | } 172 | cur->setData(QColor(90,115,210),Qt::ForegroundRole); 173 | cur->setData(QDateTime::currentDateTime(),DateRole); 174 | bool success=false; 175 | switch(cur->data(CodeRole).toInt()){ 176 | case Inherit: 177 | Danmaku::instance()->delayAll(-time); 178 | success=true; 179 | break;; 180 | case Surmise: 181 | for(int i=1;;i++){ 182 | QStandardItem *head=item(cur->row()-i); 183 | if (head->data(CodeRole).toInt()!=Records){ 184 | continue; 185 | } 186 | for(int j=0;jrowCount();++j){ 187 | QString code=head->child(j)->data(CodeRole).toString(); 188 | int sharp=code.indexOf(QRegularExpression("[#_]")); 189 | if (sharp!=-1&&!QFile::exists(code)){ 190 | QString id=code.mid(0,sharp); 191 | QString pt=code.mid(sharp+1); 192 | Load::instance()->loadDanmaku((id+"#%1").arg(pt.toInt()+i)); 193 | success=true; 194 | } 195 | } 196 | break; 197 | } 198 | Danmaku::instance()->clearPool(); 199 | break; 200 | case Records: 201 | { 202 | QFileInfo info(cur->data(FileRole).toString()); 203 | for(int i=0;irowCount();++i){ 204 | Load *load=Load::instance(); 205 | QStandardItem *d=cur->child(i); 206 | QString danmaku=d->data(CodeRole).toString(); 207 | danmaku.replace("%{File}",info.completeBaseName()).replace("%{Path}",info.absolutePath()); 208 | Load::Task task=load->codeToTask(danmaku); 209 | task.delay=d->data(List::TimeRole).value(); 210 | load->enqueue(task); 211 | success=true; 212 | } 213 | if (old||success){ 214 | Danmaku::instance()->clearPool(); 215 | } 216 | break; 217 | } 218 | } 219 | if(!success&&Danmaku::instance()->getPool().isEmpty()){ 220 | QFileInfo info(cur->data(FileRole).toString()); 221 | QStringList accept=Utils::getSuffix(Utils::Danmaku); 222 | for(const QFileInfo &iter:info.dir().entryInfoList(QDir::Files,QDir::Name)){ 223 | QString file=iter.absoluteFilePath(); 224 | if(!accept.contains(iter.suffix().toLower())|| 225 | info.baseName()!=iter.baseName()){ 226 | continue; 227 | } 228 | Load::instance()->loadDanmaku(file); 229 | break; 230 | } 231 | } 232 | }); 233 | connect(APlayer::instance(),&APlayer::reach,this,[this](bool m){ 234 | if(!m){ 235 | QModelIndex i=indexFromItem(cur); 236 | if (jumpToIndex(index(i.row()+1,0,i.parent()),false)&& 237 | APlayer::instance()->getState()!=APlayer::Play){ 238 | APlayer::instance()->play(); 239 | return; 240 | } 241 | } 242 | updateCurrent(); 243 | }); 244 | } 245 | 246 | List::~List() 247 | { 248 | QJsonArray list; 249 | updateCurrent(); 250 | for(int i=0;iitem(i); 252 | QJsonObject data; 253 | data["File"]=item->data(FileRole).toString(); 254 | data["Time"]=item->data(TimeRole).toDouble(); 255 | data["Date"]=item->data(DateRole).toString(); 256 | switch(item->data(CodeRole).toInt()){ 257 | case Records: 258 | { 259 | QJsonArray danm; 260 | for(int i=0;irowCount();++i){ 261 | QStandardItem *c=item->child(i); 262 | QJsonObject d; 263 | d["Code"]=c->data(CodeRole).toString(); 264 | d["Time"]=c->data(TimeRole).toDouble(); 265 | danm.append(d); 266 | } 267 | data["Danm"]=danm; 268 | break; 269 | } 270 | case Inherit: 271 | data["Danm"]="Inherit"; 272 | break; 273 | case Surmise: 274 | data["Danm"]="Surmise"; 275 | break; 276 | } 277 | list.append(data); 278 | } 279 | Config::setValue("/Playing/List",list); 280 | } 281 | 282 | void List::saveList() 283 | { 284 | QJsonArray list; 285 | updateCurrent(); 286 | for(int i=0;iitem(i); 288 | QJsonObject data; 289 | data["File"]=item->data(FileRole).toString(); 290 | data["Time"]=item->data(TimeRole).toDouble(); 291 | data["Date"]=item->data(DateRole).toString(); 292 | switch(item->data(CodeRole).toInt()){ 293 | case Records: 294 | { 295 | QJsonArray danm; 296 | for(int i=0;irowCount();++i){ 297 | QStandardItem *c=item->child(i); 298 | QJsonObject d; 299 | d["Code"]=c->data(CodeRole).toString(); 300 | d["Time"]=c->data(TimeRole).toDouble(); 301 | danm.append(d); 302 | } 303 | data["Danm"]=danm; 304 | break; 305 | } 306 | case Inherit: 307 | data["Danm"]="Inherit"; 308 | break; 309 | case Surmise: 310 | data["Danm"]="Surmise"; 311 | break; 312 | } 313 | list.append(data); 314 | } 315 | Config::setValue("/Playing/List",list); 316 | } 317 | 318 | QStringList List::mimeTypes() const 319 | { 320 | return {"application/x-bililocallistdata"}; 321 | } 322 | 323 | namespace 324 | { 325 | QList getItems(const QModelIndexList &indexes) 326 | { 327 | QList items; 328 | for(const QModelIndex&i:indexes){ 329 | items.append(List::instance()->itemFromIndex(i)); 330 | } 331 | std::sort(items.begin(),items.end(),[](QStandardItem *f,QStandardItem *s){return f->row()row();}); 332 | return items; 333 | } 334 | } 335 | 336 | QMimeData *List::mimeData(const QModelIndexList &indexes) const 337 | { 338 | QByteArray byte; 339 | QDataStream s(&byte,QIODevice::WriteOnly); 340 | for(QStandardItem *item:getItems(indexes)){ 341 | s<row(); 342 | } 343 | QMimeData *data=new QMimeData; 344 | data->setData("application/x-bililocallistdata",byte); 345 | return data; 346 | } 347 | 348 | bool List::dropMimeData(const QMimeData *data,Qt::DropAction action,int row,int column,const QModelIndex &parent) 349 | { 350 | if (action!=Qt::MoveAction||column!=0||parent.isValid()){ 351 | return false; 352 | } 353 | if (item(row)&&item(row)->data(CodeRole).toInt()!=Records){ 354 | return false; 355 | } 356 | QDataStream s(data->data("application/x-bililocallistdata")); 357 | QList items; 358 | while(!s.atEnd()){ 359 | int source; 360 | s>> source; 361 | items.append(item(source)); 362 | } 363 | if (items.isEmpty()){ 364 | return false; 365 | } 366 | std::sort(items.begin(),items.end(),[](QStandardItem *f,QStandardItem *s){return f->row()row();}); 367 | for(QStandardItem *item:items){ 368 | split(item->index()); 369 | if (item->row()row())); 373 | ++row; 374 | } 375 | return true; 376 | } 377 | 378 | void List::setRelated(const QModelIndexList &indexes,int reason) 379 | { 380 | if (indexes.size()<2){ 381 | return; 382 | } 383 | int i=-1; 384 | auto items=getItems(indexes); 385 | split(indexes); 386 | for(QStandardItem *item:items){ 387 | if (i<0){ 388 | i=item->row(); 389 | insertRow(i,takeRow(item->row())); 390 | item->setIcon(icons[0]); 391 | } 392 | else{ 393 | ++i; 394 | insertRow(i,takeRow(item->row())); 395 | item->setIcon(icons[1]); 396 | item->setData(reason,CodeRole); 397 | item->setRowCount(0); 398 | } 399 | } 400 | item(i)->setIcon(icons[2]); 401 | } 402 | 403 | QString List::defaultPath(int type) 404 | { 405 | QStandardItem *item=cur; 406 | if (hasChildren()&&!item){ 407 | item=this->item(0); 408 | for(int i=1;iitem(i); 410 | if (iter->data(DateRole).toDateTime()>item->data(DateRole).toDateTime()){ 411 | item=iter; 412 | } 413 | } 414 | } 415 | if (item){ 416 | if (type==Utils::Danmaku){ 417 | for(int i=0;irowCount();++i){ 418 | QString code=item->child(i)->data(CodeRole).toString(); 419 | if (QFile::exists(code)){ 420 | return code; 421 | } 422 | } 423 | } 424 | return item->data(FileRole).toString(); 425 | } 426 | QStringList paths=QStandardPaths::standardLocations(QStandardPaths::MoviesLocation); 427 | paths.append(QDir::homePath()); 428 | return paths.front(); 429 | } 430 | 431 | namespace 432 | { 433 | int diffAtNum(QString f,QString s) 434 | { 435 | for(int i=0;idata(FileRole).toString()==file){ 450 | return item(i); 451 | } 452 | } 453 | if(!create){ 454 | return nullptr; 455 | } 456 | else{ 457 | QFileInfo info(file); 458 | QString name=info.completeBaseName(); 459 | QString path=info.absoluteFilePath(); 460 | QStandardItem *item=new QStandardItem; 461 | item->setText(name); 462 | item->setData(path,FileRole); 463 | item->setEditable(false);item->setDropEnabled(false); 464 | appendRow(item); 465 | QModelIndexList indexes; 466 | indexes.append(item->index()); 467 | for(const QFileInfo &iter:info.dir().entryInfoList(QDir::Files,QDir::Name)){ 468 | QString p=iter.absoluteFilePath(); 469 | QString c=info.completeBaseName(); 470 | QString o=iter.completeBaseName(); 471 | if(!itemFromFile(p)&&info.suffix()==iter.suffix()&&diffAtNum(c,o)>0){ 472 | QStandardItem *i=new QStandardItem; 473 | i->setText(o); 474 | i->setData(p,FileRole); 475 | i->setEditable(false);i->setDropEnabled(false); 476 | appendRow(i); 477 | indexes.append(i->index()); 478 | } 479 | } 480 | group(indexes); 481 | return item; 482 | } 483 | } 484 | 485 | bool List::finished() 486 | { 487 | return !hasChildren()||cur==item(rowCount()-1); 488 | } 489 | 490 | void List::appendMedia(QString file) 491 | { 492 | itemFromFile(file,true); 493 | } 494 | 495 | void List::updateCurrent() 496 | { 497 | if(!cur){ 498 | return; 499 | } 500 | cur->setRowCount(0); 501 | cur->setData(time,TimeRole); 502 | if (cur->data(CodeRole).toInt()!=Records){ 503 | return; 504 | } 505 | QFileInfo info(cur->data(FileRole).toString()); 506 | for(const Record &r:Danmaku::instance()->getPool()){ 507 | QStandardItem *d=new QStandardItem; 508 | QString danmaku=r.access; 509 | danmaku.replace(info.completeBaseName(),"%{File}").replace(info.absolutePath(),"%{Path}"); 510 | d->setData(danmaku,CodeRole); 511 | d->setData(r.delay,TimeRole); 512 | cur->appendRow(d); 513 | } 514 | } 515 | 516 | void List::waste(const QModelIndex &index) 517 | { 518 | if (itemFromIndex(index)==cur){ 519 | return; 520 | } 521 | int self=index.row(); 522 | int head=getGroupHead(index).row(); 523 | int tail=getGroupTail(index).row(); 524 | removeRow(self); 525 | switch(tail-head){ 526 | case 0: 527 | return; 528 | case 1: 529 | clearItemGroup(head); 530 | return; 531 | default: 532 | --tail; 533 | item(tail)->setIcon(icons[2]); 534 | item(head)->setIcon(icons[0]); 535 | item(head)->setData(Records,CodeRole); 536 | return; 537 | } 538 | } 539 | 540 | void List::waste(const QModelIndexList &indexes) 541 | { 542 | for(QStandardItem *item:getItems(indexes)){ 543 | waste(item->index()); 544 | } 545 | } 546 | 547 | void List::split(const QModelIndex &index) 548 | { 549 | int self=index.row(); 550 | int head=getGroupHead(index).row(); 551 | int tail=getGroupTail(index).row(); 552 | switch(tail-head){ 553 | case 0: 554 | return; 555 | case 1: 556 | clearItemGroup(head); 557 | clearItemGroup(tail); 558 | return; 559 | default: 560 | insertRow(tail,takeRow(self)); 561 | clearItemGroup(tail); 562 | --tail; 563 | item(tail)->setIcon(icons[2]); 564 | item(head)->setIcon(icons[0]); 565 | item(head)->setData(Records,CodeRole); 566 | return; 567 | } 568 | } 569 | 570 | void List::split(const QModelIndexList &indexes) 571 | { 572 | QList items=getItems(indexes); 573 | while(!items.isEmpty()){ 574 | split(items.takeLast()->index()); 575 | } 576 | } 577 | 578 | void List::merge(const QModelIndexList &indexes) 579 | { 580 | setRelated(indexes,Inherit); 581 | } 582 | 583 | void List::group(const QModelIndexList &indexes) 584 | { 585 | setRelated(indexes,Surmise); 586 | } 587 | 588 | void List::jumpToLast() 589 | { 590 | int rc=rowCount(); 591 | int i=cur?cur->row():rc; 592 | jumpToIndex(index((i+rc-1)%rc,0)); 593 | } 594 | 595 | void List::jumpToNext() 596 | { 597 | int rc=rowCount(); 598 | int i=cur?cur->row():-1; 599 | jumpToIndex(index((i+rc+1)%rc,0)); 600 | } 601 | 602 | bool List::jumpToIndex(const QModelIndex &index,bool manually) 603 | { 604 | QStandardItem *head=itemFromIndex(index); 605 | if(!head||(manually&&head->data(CodeRole).toInt()==Inherit)){ 606 | return false; 607 | } 608 | APlayer::instance()->setMedia(head->data(FileRole).toString(),manually); 609 | return true; 610 | } 611 | -------------------------------------------------------------------------------- /src/Danmaku.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Danmaku.cpp 6 | * Time: 2013/03/18 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Danmaku.h" 28 | #include "APlayer.h" 29 | #include "Config.h" 30 | #include "Editor.h" 31 | #include "Graphic.h" 32 | #include "Load.h" 33 | #include "Local.h" 34 | #include "Render.h" 35 | #include "Shield.h" 36 | #include 37 | #include 38 | #include 39 | 40 | #define qThreadPool QThreadPool::globalInstance() 41 | 42 | Danmaku *Danmaku::ins=nullptr; 43 | 44 | Danmaku *Danmaku::instance() 45 | { 46 | return ins?ins:new Danmaku(qApp); 47 | } 48 | 49 | Danmaku::Danmaku(QObject *parent): 50 | QAbstractItemModel(parent) 51 | { 52 | ins=this; 53 | setObjectName("Danmaku"); 54 | cur=time=0; 55 | qThreadPool->setMaxThreadCount(Config::getValue("/Danmaku/Thread",QThread::idealThreadCount())); 56 | connect(APlayer::instance(),&APlayer::jumped, this,&Danmaku::jumpToTime); 57 | connect(APlayer::instance(),&APlayer::timeChanged,this,&Danmaku::setTime ); 58 | connect(this,SIGNAL(layoutChanged()),Render::instance(),SLOT(draw())); 59 | QMetaObject::invokeMethod(this,"alphaChanged",Qt::QueuedConnection,Q_ARG(int,Config::getValue("/Danmaku/Alpha",100))); 60 | } 61 | 62 | Danmaku::~Danmaku() 63 | { 64 | qThreadPool->clear(); 65 | qThreadPool->waitForDone(); 66 | qDeleteAll(current); 67 | } 68 | 69 | void Danmaku::draw(QPainter *painter,qint64 move) 70 | { 71 | QVarLengthArray dirty; 72 | lock.lockForWrite(); 73 | dirty.reserve(current.size()); 74 | for(auto iter=current.begin();iter!=current.end();){ 75 | Graphic *g=*iter; 76 | if(g->move(move)){ 77 | dirty.append(g); 78 | ++iter; 79 | } 80 | else{ 81 | delete g; 82 | iter=current.erase(iter); 83 | } 84 | } 85 | lock.unlock(); 86 | for(Graphic *g:dirty){ 87 | g->draw(painter); 88 | } 89 | } 90 | 91 | QVariant Danmaku::data(const QModelIndex &index,int role) const 92 | { 93 | if(index.isValid()){ 94 | const Comment &comment=*danmaku[index.row()]; 95 | switch(role){ 96 | case Qt::DisplayRole: 97 | if(index.column()==0){ 98 | if(comment.blocked){ 99 | return tr("Blocked"); 100 | } 101 | else{ 102 | QString time("%1:%2"); 103 | qint64 sec=comment.time/1000; 104 | if(sec<0){ 105 | time.prepend("-"); 106 | sec=-sec; 107 | } 108 | time=time.arg(sec/60,2,10,QChar('0')); 109 | time=time.arg(sec%60,2,10,QChar('0')); 110 | return time; 111 | } 112 | } 113 | else{ 114 | if(comment.mode==7){ 115 | QJsonArray data=QJsonDocument::fromJson(comment.string.toUtf8()).array(); 116 | return data.size()>=5?data.at(4).toString():QString(); 117 | } 118 | else{ 119 | return comment.string.left(50).remove('\n'); 120 | } 121 | } 122 | case Qt::ForegroundRole: 123 | if(index.column()==0){ 124 | if(comment.blocked||comment.time>=60000000){ 125 | return QColor(Qt::red); 126 | } 127 | } 128 | else{ 129 | if(comment.blocked){ 130 | return QColor(Qt::gray); 131 | } 132 | } 133 | break; 134 | case Qt::ToolTipRole: 135 | if(index.column()==1){ 136 | return Qt::convertFromPlainText(comment.string); 137 | } 138 | break; 139 | case Qt::TextAlignmentRole: 140 | if(index.column()==0){ 141 | return Qt::AlignCenter; 142 | } 143 | break; 144 | case Qt::BackgroundRole: 145 | switch(comment.mode){ 146 | case 7: 147 | return QColor(200,255,200); 148 | case 8: 149 | return QColor(255,255,160); 150 | default: 151 | break; 152 | } 153 | case Qt::UserRole: 154 | return (quintptr)&comment; 155 | } 156 | } 157 | return QVariant(); 158 | } 159 | 160 | int Danmaku::rowCount(const QModelIndex &parent) const 161 | { 162 | return parent.isValid()?0:danmaku.size(); 163 | } 164 | 165 | int Danmaku::columnCount(const QModelIndex &parent) const 166 | { 167 | return parent.isValid()?0:2; 168 | } 169 | 170 | QModelIndex Danmaku::parent(const QModelIndex &) const 171 | { 172 | return QModelIndex(); 173 | } 174 | 175 | QModelIndex Danmaku::index(int row,int colum,const QModelIndex &parent) const 176 | { 177 | if(!parent.isValid()&&colum<2){ 178 | return createIndex(row,colum); 179 | } 180 | return QModelIndex(); 181 | } 182 | 183 | QVariant Danmaku::headerData(int section,Qt::Orientation orientation,int role) const 184 | { 185 | if(role==Qt::DisplayRole&&orientation==Qt::Horizontal){ 186 | if(section==0){ 187 | return tr("Time"); 188 | } 189 | if(section==1){ 190 | return tr("Comment"); 191 | } 192 | } 193 | return QVariant(); 194 | } 195 | 196 | const Comment *Danmaku::commentAt(QPoint point) const 197 | { 198 | lock.lockForRead(); 199 | for(Graphic *g:current){ 200 | if(g->currentRect().contains(point)){ 201 | lock.unlock(); 202 | return g->getSource(); 203 | } 204 | } 205 | lock.unlock(); 206 | return nullptr; 207 | } 208 | 209 | void Danmaku::setAlpha(int _alpha) 210 | { 211 | Config::setValue("/Danmaku/Alpha", _alpha); 212 | emit alphaChanged(_alpha); 213 | } 214 | 215 | void Danmaku::resetTime() 216 | { 217 | cur=0; 218 | time=0; 219 | } 220 | 221 | void Danmaku::clearPool() 222 | { 223 | if(!pool.isEmpty()){ 224 | clearCurrent(); 225 | pool.clear(); 226 | danmaku.clear(); 227 | parse(0x1|0x2); 228 | } 229 | } 230 | 231 | namespace 232 | { 233 | class CommentPointer 234 | { 235 | public: 236 | const Comment *comment; 237 | 238 | CommentPointer(const Comment *comment): 239 | comment(comment) 240 | { 241 | } 242 | 243 | inline bool operator == (const CommentPointer &o) const 244 | { 245 | return *comment==*o.comment; 246 | } 247 | }; 248 | 249 | inline uint qHash(const CommentPointer &p, uint seed = 0) 250 | { 251 | return ::qHash(*p.comment,seed); 252 | } 253 | } 254 | 255 | void Danmaku::appendToPool(const Record *record) 256 | { 257 | Record *append=0; 258 | for(Record &r:pool){ 259 | if (r.source==record->source){ 260 | append=&r; 261 | break; 262 | } 263 | } 264 | if(!append){ 265 | pool.append(*record); 266 | QSet s; 267 | auto &d=pool.last().danmaku; 268 | for(auto iter=d.begin();iter!=d.end();){ 269 | CommentPointer p(&(*iter)); 270 | if(!s.contains(p)){ 271 | ++iter; 272 | s.insert(p); 273 | } 274 | else{ 275 | iter=d.erase(iter); 276 | } 277 | } 278 | } 279 | else{ 280 | auto &d=append->danmaku; 281 | QSet s; 282 | for(const Comment &c:d){ 283 | s.insert(&c); 284 | } 285 | for(Comment c:record->danmaku){ 286 | c.time+=append->delay-record->delay; 287 | if(!s.contains(&c)){ 288 | d.append(c); 289 | s.insert(&d.last()); 290 | } 291 | } 292 | if (record->full){ 293 | append->full=true; 294 | } 295 | } 296 | parse(0x1|0x2); 297 | if(!append&&Load::instance()->size()<2&&pool.size()>=2){ 298 | Editor::exec(lApp->mainWidget()); 299 | } 300 | } 301 | 302 | namespace 303 | { 304 | class Compare 305 | { 306 | public: 307 | inline bool operator ()(const Comment *c,qint64 time) 308 | { 309 | return c->timetime; 314 | } 315 | inline bool operator ()(const Comment *f,const Comment *s) 316 | { 317 | return f->timetime; 318 | } 319 | }; 320 | } 321 | 322 | void Danmaku::appendToPool(QString source,const Comment *comment) 323 | { 324 | Record *append=nullptr; 325 | for(Record &r:pool){ 326 | if (r.source==source){ 327 | append=&r; 328 | break; 329 | } 330 | } 331 | if(!append){ 332 | Record r; 333 | r.source=source; 334 | pool.append(r); 335 | append=&pool.last(); 336 | } 337 | append->danmaku.append(*comment); 338 | auto ptr=&append->danmaku.last(); 339 | danmaku.insert(std::upper_bound(danmaku.begin(),danmaku.end(),ptr,Compare()),ptr); 340 | parse(0x2); 341 | } 342 | 343 | void Danmaku::clearCurrent(bool soft) 344 | { 345 | qThreadPool->clear(); 346 | qThreadPool->waitForDone(); 347 | lock.lockForWrite(); 348 | if(soft){ 349 | for(auto iter=current.begin();iter!=current.end();){ 350 | Graphic *g=*iter; 351 | if(g->getMode()==8){ 352 | ++iter; 353 | } 354 | else{ 355 | delete g; 356 | iter=current.erase(iter); 357 | } 358 | } 359 | } 360 | else{ 361 | qDeleteAll(current); 362 | current.clear(); 363 | } 364 | lock.unlock(); 365 | Render::instance()->draw(); 366 | } 367 | 368 | void Danmaku::insertToCurrent(Graphic *graphic,int index) 369 | { 370 | lock.lockForWrite(); 371 | Graphic *g=(Graphic *)graphic; 372 | g->setIndex(); 373 | int size=current.size(),next; 374 | if (size==0||index==0){ 375 | next=0; 376 | } 377 | else{ 378 | int ring=size+1; 379 | next=index>0?(index%ring):(ring+index%ring); 380 | if (next==0){ 381 | next=size; 382 | } 383 | } 384 | current.insert(next,g); 385 | lock.unlock(); 386 | } 387 | 388 | void Danmaku::parse(int flag) 389 | { 390 | if((flag&0x1)>0){ 391 | beginResetModel(); 392 | danmaku.clear(); 393 | for(Record &record:pool){ 394 | for(Comment &comment:record.danmaku){ 395 | danmaku.append(&comment); 396 | } 397 | } 398 | std::stable_sort(danmaku.begin(),danmaku.end(),Compare()); 399 | jumpToTime(time); 400 | endResetModel(); 401 | } 402 | if((flag&0x2)>0){ 403 | for(Record &r:pool){ 404 | for(Comment &c:r.danmaku){ 405 | c.blocked=r.limit!=0&&c.date>r.limit; 406 | } 407 | } 408 | QSet set; 409 | int l=Config::getValue("/Shield/Limit",5); 410 | QVector clean; 411 | clean.reserve(danmaku.size()); 412 | if(l!=0){ 413 | for(const Comment *c:danmaku){ 414 | QString r; 415 | r.reserve(c->string.length()); 416 | for(const QChar &i:c->string){ 417 | if(i.isLetterOrNumber()||i.isMark()||i=='_'){ 418 | r.append(i); 419 | } 420 | } 421 | clean.append(r); 422 | } 423 | QHash count; 424 | int sta=0,end=sta; 425 | while(end!=danmaku.size()){ 426 | while(danmaku[sta]->time+10000time){ 427 | if(--count[clean[sta]]==0){ 428 | count.remove(clean[sta]); 429 | } 430 | ++sta; 431 | } 432 | if(++count[clean[end]]>l&&danmaku[end]->mode<=6){ 433 | set.insert(clean[end]); 434 | } 435 | ++end; 436 | } 437 | } 438 | for(int i=0;iclear(); 443 | qThreadPool->waitForDone(); 444 | lock.lockForWrite(); 445 | for(auto iter=current.begin();iter!=current.end();){ 446 | const Comment *cur=(*iter)->getSource(); 447 | if(cur&&cur->blocked){ 448 | delete *iter; 449 | iter=current.erase(iter); 450 | } 451 | else{ 452 | ++iter; 453 | } 454 | } 455 | lock.unlock(); 456 | emit layoutChanged(); 457 | } 458 | } 459 | 460 | namespace 461 | { 462 | class Process:public QRunnable 463 | { 464 | public: 465 | Process(QReadWriteLock *l,QList &c,const QList &w): 466 | current(c),lock(l),wait(w) 467 | { 468 | createTime=QDateTime::currentMSecsSinceEpoch(); 469 | } 470 | 471 | void run() 472 | { 473 | if(wait.isEmpty()||createTimesetPriority(QThread::NormalPriority); 477 | QSize size=Render::instance()->getActualSize(); 478 | QList ready; 479 | while(!wait.isEmpty()){ 480 | const Comment *c=wait.takeFirst(); 481 | Graphic *g=Graphic::create(*c); 482 | if(g){ 483 | if(c->mode<=6&&c->font*(c->string.count("\n")+1)<360){ 484 | QRectF &r=g->currentRect(); 485 | int b=r.top(),e=0,s=10; 486 | std::function f; 487 | switch(c->mode){ 488 | case 1: 489 | case 5: 490 | case 6: 491 | e=size.height()*(Config::getValue("/Danmaku/Protect",false)?0.85:1)-r.height(); 492 | f=std::less_equal (); 493 | break; 494 | case 4: 495 | s=-s; 496 | f=std::greater_equal(); 497 | break; 498 | } 499 | QVarLengthArray result(qMax((e-b)/s+1,0)); 500 | memset(result.data(),0,sizeof(int)*result.size()); 501 | auto calculate=[&](const QList &data){ 502 | QRectF t=r; 503 | int i=0,h=b; 504 | for(;f(h,e);h+=s,++i){ 505 | r.moveTop(h); 506 | for(Graphic *iter:data){ 507 | result[i]+=g->intersects(iter); 508 | } 509 | } 510 | r=t; 511 | }; 512 | lock->lockForRead(); 513 | quint64 last=current.isEmpty()?0:current.last()->getIndex(); 514 | calculate(current); 515 | lock->unlock(); 516 | g->setEnabled(false); 517 | ready.append(g); 518 | lock->lockForWrite(); 519 | QList addtion; 520 | QListIterator iter(current); 521 | iter.toBack(); 522 | while(iter.hasPrevious()){ 523 | Graphic *p=iter.previous(); 524 | if(p->getIndex()>last){ 525 | addtion.prepend(p); 526 | } 527 | else break; 528 | } 529 | calculate(addtion); 530 | int h=b,m=std::numeric_limits::max(); 531 | for(int i=0;f(h,e)&&m!=0;h+=s,++i){ 532 | if (m>result[i]){ 533 | m=result[i]; 534 | r.moveTop(h); 535 | } 536 | } 537 | } 538 | else{ 539 | g->setEnabled(false); 540 | ready.append(g); 541 | lock->lockForWrite(); 542 | } 543 | g->setIndex(); 544 | current.append(g); 545 | lock->unlock(); 546 | } 547 | else{ 548 | Danmaku::instance()->unrecognizedComment(c); 549 | } 550 | } 551 | lock->lockForWrite(); 552 | for(Graphic *g:ready){ 553 | g->setEnabled(true); 554 | } 555 | lock->unlock(); 556 | } 557 | 558 | Process &operator=(const Process &)=delete; 559 | 560 | private: 561 | QList ¤t; 562 | qint64 createTime; 563 | QReadWriteLock *lock; 564 | QList wait; 565 | }; 566 | } 567 | 568 | void Danmaku::setTime(qint64 _time) 569 | { 570 | time=_time; 571 | int l=Config::getValue("/Shield/Density",100),n=0; 572 | QMap>> buffer; 573 | for(;curtimeblocked&&(c->mode>6||l==0||current.size()+ntime][c->string].append(c); 578 | } 579 | } 580 | for(const auto &sameTime:buffer){ 581 | for(const auto &sameText:sameTime){ 582 | qThreadPool->start(new Process(&lock,current,sameText)); 583 | } 584 | } 585 | } 586 | 587 | void Danmaku::delayAll(qint64 _time) 588 | { 589 | for(Record &r:pool){ 590 | r.delay+=_time; 591 | for(Comment &c:r.danmaku){ 592 | c.time+=_time; 593 | } 594 | } 595 | jumpToTime(time); 596 | emit layoutChanged(); 597 | } 598 | 599 | void Danmaku::jumpToTime(qint64 _time) 600 | { 601 | clearCurrent(true); 602 | time=_time; 603 | cur=std::lower_bound(danmaku.begin(),danmaku.end(),time,Compare())-danmaku.begin(); 604 | } 605 | 606 | void Danmaku::saveToFile(QString file) 607 | { 608 | QFile f(file); 609 | f.open(QIODevice::WriteOnly|QIODevice::Text); 610 | bool skip=Config::getValue("/Interface/Save/Skip",false); 611 | if (file.endsWith("xml",Qt::CaseInsensitive)){ 612 | QXmlStreamWriter w(&f); 613 | w.setAutoFormatting(true); 614 | w.writeStartDocument(); 615 | w.writeStartElement("i"); 616 | w.writeStartElement("chatserver"); 617 | w.writeCharacters("chat."+Utils::customUrl(Utils::Bilibili)); 618 | w.writeEndElement(); 619 | w.writeStartElement("mission"); 620 | w.writeCharacters("0"); 621 | w.writeEndElement(); 622 | w.writeStartElement("source"); 623 | w.writeCharacters("k-v"); 624 | w.writeEndElement(); 625 | for(const Comment *c:danmaku){ 626 | if(c->blocked&&skip){ 627 | continue; 628 | } 629 | w.writeStartElement("d"); 630 | QStringList l; 631 | l<time/1000.0)<< 632 | QString::number(c->mode)<< 633 | QString::number(c->font)<< 634 | QString::number(c->color)<< 635 | QString::number(c->date)<< 636 | "0"<< 637 | c->sender<< 638 | "0"; 639 | w.writeAttribute("p",l.join(',')); 640 | w.writeCharacters(c->string); 641 | w.writeEndElement(); 642 | } 643 | w.writeEndElement(); 644 | w.writeEndDocument(); 645 | } 646 | else{ 647 | QJsonArray a; 648 | for(const Comment *c:danmaku){ 649 | if(c->blocked&&skip){ 650 | continue; 651 | } 652 | QJsonObject o; 653 | QStringList l; 654 | l<time/1000.0)<< 655 | QString::number(c->color)<< 656 | QString::number(c->mode)<< 657 | QString::number(c->font)<< 658 | c->sender<< 659 | QString::number(c->date); 660 | o["c"]=l.join(','); 661 | o["m"]=c->string; 662 | a.append(o); 663 | } 664 | f.write(QJsonDocument(a).toJson(QJsonDocument::Compact)); 665 | } 666 | f.close(); 667 | } 668 | -------------------------------------------------------------------------------- /src/Menu.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Menu.cpp 6 | * Time: 2013/04/05 7 | * Author: Lysine 8 | * Contributor: Chaserhkj 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #include "Menu.h" 29 | #include "APlayer.h" 30 | #include "Config.h" 31 | #include "Danmaku.h" 32 | #include "List.h" 33 | #include "Load.h" 34 | #include "Local.h" 35 | #include "Render.h" 36 | #include "Search.h" 37 | #include "Utils.h" 38 | 39 | namespace{ 40 | class LoadProxyModel:public QAbstractProxyModel 41 | { 42 | public: 43 | explicit LoadProxyModel(QObject *parent=0): 44 | QAbstractProxyModel(parent) 45 | { 46 | setSourceModel(Load::instance()->getModel()); 47 | connect(sourceModel(),&QAbstractItemModel::rowsInserted,this,&LoadProxyModel::endInsertRows); 48 | connect(sourceModel(),&QAbstractItemModel::rowsAboutToBeInserted,[this](const QModelIndex &parent,int sta,int end){ 49 | beginInsertRows(mapFromSource(parent),sta,end); 50 | }); 51 | connect(sourceModel(),&QAbstractItemModel::layoutAboutToBeChanged,this,&LoadProxyModel::layoutAboutToBeChanged); 52 | connect(sourceModel(),&QAbstractItemModel::layoutChanged,this,&LoadProxyModel::layoutChanged); 53 | } 54 | 55 | int columnCount(const QModelIndex &) const 56 | { 57 | return 1; 58 | } 59 | 60 | QVariant data(const QModelIndex &index,int role) const 61 | { 62 | if(isFakeItem(index)){ 63 | switch(role){ 64 | case Qt::TextAlignmentRole: 65 | return Qt::AlignCenter; 66 | case Qt::FontRole: 67 | { 68 | QFont f; 69 | f.setBold(true); 70 | return f; 71 | } 72 | case Qt::DisplayRole: 73 | case Qt::EditRole: 74 | return Menu::tr("Load All"); 75 | case Qt::BackgroundRole: 76 | return QColor(0xA0A0A4); 77 | case Qt::ForegroundRole: 78 | return QColor(0xFFFFFF); 79 | case Qt::SizeHintRole: 80 | return QSize(0,20); 81 | case Load::UrlRole: 82 | return QUrl(); 83 | case Load::StrRole: 84 | return ""; 85 | default: 86 | return QVariant(); 87 | } 88 | } 89 | else{ 90 | return sourceModel()->data(mapToSource(index),role); 91 | } 92 | } 93 | 94 | Qt::ItemFlags flags(const QModelIndex &) const 95 | { 96 | return Qt::ItemIsSelectable|Qt::ItemIsEnabled; 97 | } 98 | 99 | QModelIndex index(int r,int c,const QModelIndex &p) const 100 | { 101 | if(!p.isValid()&&r==0&&c==0){ 102 | return createIndex(0,0,1); 103 | } 104 | else{ 105 | return createIndex(r,c); 106 | } 107 | } 108 | 109 | QModelIndex parent(const QModelIndex &) const 110 | { 111 | return QModelIndex(); 112 | } 113 | 114 | int rowCount(const QModelIndex &parent) const 115 | { 116 | return sourceModel()->rowCount(mapToSource(parent))+(parent.isValid()?0:1); 117 | } 118 | 119 | bool isFakeItem(const QModelIndex &index) const 120 | { 121 | return index.internalId(); 122 | } 123 | 124 | QModelIndex mapToSource (const QModelIndex &i) const 125 | { 126 | return (isFakeItem(i)||!i.isValid())?QModelIndex():sourceModel()->index(i.row()-1,i.column(),mapToSource(i.parent())); 127 | } 128 | 129 | QModelIndex mapFromSource(const QModelIndex &i) const 130 | { 131 | return i.isValid()?index(i.row()+1,i.column(),mapFromSource(i.parent())):QModelIndex(); 132 | } 133 | }; 134 | 135 | class ListProxyModel:public QSortFilterProxyModel 136 | { 137 | public: 138 | explicit ListProxyModel(QObject *parent=0): 139 | QSortFilterProxyModel(parent) 140 | { 141 | setSourceModel(List::instance()); 142 | setSortRole(List::DateRole); 143 | sort(0,Qt::DescendingOrder); 144 | } 145 | 146 | QVariant data(const QModelIndex &index,int role) const 147 | { 148 | return role==Qt::DecorationRole?QVariant():QSortFilterProxyModel::data(index,role); 149 | } 150 | 151 | private: 152 | bool filterAcceptsRow(int row,const QModelIndex &parent) const 153 | { 154 | QModelIndex i=sourceModel()->index(row,0,parent); 155 | if (!i.data(List::DateRole).toDateTime().isValid()||i.data(List::CodeRole).toInt()==List::Inherit){ 156 | return false; 157 | } 158 | QStandardItem *c=List::instance()->getCurrent(); 159 | return !c||c->index()!=i; 160 | } 161 | }; 162 | 163 | class FileEdit:public QLineEdit 164 | { 165 | public: 166 | explicit FileEdit(QCompleter *completer,QWidget *parent=0): 167 | QLineEdit(parent),completer(completer) 168 | { 169 | historyFlag=0; 170 | connect(this,&QLineEdit::selectionChanged,[this](){historyFlag=0;}); 171 | } 172 | 173 | void mousePressEvent(QMouseEvent *e) 174 | { 175 | if (e->button()==Qt::LeftButton){ 176 | historyFlag=1; 177 | } 178 | QLineEdit::mousePressEvent(e); 179 | } 180 | 181 | void mouseReleaseEvent(QMouseEvent *e) 182 | { 183 | if (e->button()==Qt::LeftButton){ 184 | if (historyFlag){ 185 | completer->complete(); 186 | completer->popup()->setCurrentIndex(completer->model()->index(0,0)); 187 | } 188 | historyFlag=0; 189 | } 190 | QLineEdit::mouseReleaseEvent(e); 191 | } 192 | 193 | private: 194 | bool historyFlag; 195 | QCompleter *completer; 196 | }; 197 | 198 | class DanmEdit:public QLineEdit 199 | { 200 | public: 201 | 202 | explicit DanmEdit(QCompleter *completer,QWidget *parent=0): 203 | QLineEdit(parent) 204 | { 205 | completer->popup()->installEventFilter(this); 206 | connect(this,&DanmEdit::textEdited,this,&DanmEdit::fixCode); 207 | } 208 | 209 | bool eventFilter(QObject *,QEvent *e) override 210 | { 211 | if (e->type()==QEvent::Hide){ 212 | Load::instance()->dequeue(); 213 | } 214 | return false; 215 | } 216 | 217 | void setCode(QString text) 218 | { 219 | code=text; 220 | text=QFileInfo(text).fileName(); 221 | if (text!=this->text()){ 222 | setText(text); 223 | } 224 | } 225 | 226 | void fixCode(QString text) 227 | { 228 | Load::instance()->fixCode(text); 229 | setCode(text); 230 | } 231 | 232 | QString getCode() 233 | { 234 | return code.isEmpty()?text():code; 235 | } 236 | 237 | private: 238 | QString code; 239 | }; 240 | } 241 | 242 | Menu::Menu(QWidget *parent): 243 | QWidget(parent) 244 | { 245 | setObjectName("Menu"); 246 | isStay=isPoped=false; 247 | Utils::setGround(this,Qt::white); 248 | ListProxyModel *fileM=new ListProxyModel(this); 249 | fileC=new QCompleter(fileM,this); 250 | LoadProxyModel *danmM=new LoadProxyModel(this); 251 | danmC=new QCompleter(danmM,this); 252 | fileL=new FileEdit(fileC,this); 253 | danmL=new DanmEdit(danmC,this); 254 | sechL=new QLineEdit(this); 255 | fileL->installEventFilter(this); 256 | danmL->installEventFilter(this); 257 | sechL->installEventFilter(this); 258 | fileL->setReadOnly(true); 259 | fileL->setPlaceholderText(tr("choose a local media")); 260 | danmL->setPlaceholderText(tr("input av/ac number")); 261 | sechL->setPlaceholderText(tr("search danmaku online")); 262 | QAbstractItemView *popup; 263 | fileC->setCompletionMode(QCompleter::UnfilteredPopupCompletion); 264 | fileC->setWidget(fileL); 265 | popup=fileC->popup(); 266 | popup->setMouseTracking(true); 267 | QAction *hdelA=new QAction(popup); 268 | hdelA->setShortcut(QKeySequence(Qt::Key_Delete)); 269 | connect(hdelA,&QAction::triggered,[=](){ 270 | QModelIndex index=popup->currentIndex(); 271 | index=dynamic_cast(fileC->completionModel())->mapToSource(index); 272 | index=fileM->mapToSource(index); 273 | List::instance()->itemFromIndex(index)->setData(QVariant(),List::DateRole); 274 | }); 275 | popup->addAction(hdelA); 276 | connect(popup,SIGNAL(entered(QModelIndex)),popup,SLOT(setCurrentIndex(QModelIndex))); 277 | connect(fileC,&QCompleter::activated,[=](const QModelIndex &index){ 278 | setFocus(); 279 | List::instance()->jumpToIndex(fileM->mapToSource(dynamic_cast(fileC->completionModel())->mapToSource(index))); 280 | }); 281 | danmC->setCompletionMode(QCompleter::UnfilteredPopupCompletion); 282 | danmC->setWidget(danmL); 283 | popup=danmC->popup(); 284 | popup->setMouseTracking(true); 285 | connect(popup,SIGNAL(entered(QModelIndex)),popup,SLOT(setCurrentIndex(QModelIndex))); 286 | connect(danmC,&QCompleter::activated,[=](const QModelIndex &index){ 287 | setFocus(); 288 | Load::instance()->loadDanmaku(danmM->mapToSource(dynamic_cast(danmC->completionModel())->mapToSource(index))); 289 | }); 290 | fileB=new QPushButton(this); 291 | sechB=new QPushButton(this); 292 | danmB=new QPushButton(this); 293 | fileB->setText(tr("Open")); 294 | danmB->setText(tr("Load")); 295 | sechB->setText(tr("Search")); 296 | fileA=new QAction(tr("Open File"),this); 297 | fileA->setObjectName("File"); 298 | fileA->setShortcut(Config::getValue("/Shortcut/File",QString())); 299 | danmA=new QAction(tr("Load Danmaku"),this); 300 | danmA->setObjectName("Danm"); 301 | danmA->setShortcut(Config::getValue("/Shortcut/Danm",QString())); 302 | sechA=new QAction(tr("Search Danmaku"),this); 303 | sechA->setObjectName("Sech"); 304 | sechA->setShortcut(Config::getValue("/Shortcut/Sech",QString())); 305 | connect(fileA,&QAction::triggered,[this](){ 306 | QString _file=QFileDialog::getOpenFileName(lApp->mainWidget(), 307 | tr("Open File"), 308 | List::instance()->defaultPath(Utils::Video|Utils::Audio), 309 | tr("Media files (%1);;All files (*.*)").arg(Utils::getSuffix(Utils::Video|Utils::Audio,"*.%1").join(' '))); 310 | if(!_file.isEmpty()){ 311 | APlayer::instance()->setMedia(_file); 312 | } 313 | }); 314 | connect(danmA,&QAction::triggered,[this](){ 315 | if(Config::getValue("/Danmaku/Local",false)){ 316 | QString _file=QFileDialog::getOpenFileName(lApp->mainWidget(), 317 | tr("Open File"), 318 | List::instance()->defaultPath(Utils::Danmaku), 319 | tr("Danmaku files (%1);;All files (*.*)").arg(Utils::getSuffix(Utils::Danmaku,"*.%1").join(' '))); 320 | if(!_file.isEmpty()){ 321 | Load::instance()->loadDanmaku(_file); 322 | } 323 | } 324 | else{ 325 | if(danmL->text().isEmpty()){ 326 | pop(); 327 | isStay=true; 328 | danmL->setFocus(); 329 | } 330 | else{ 331 | Load::instance()->loadDanmaku(dynamic_cast(danmL)->getCode()); 332 | } 333 | } 334 | }); 335 | connect(sechA,&QAction::triggered,[this](){ 336 | Search searchBox(lApp->mainWidget()); 337 | sechL->setText(sechL->text().simplified()); 338 | if(!sechL->text().isEmpty()){ 339 | searchBox.setText(sechL->text()); 340 | } 341 | if(searchBox.exec()) { 342 | Load::instance()->loadDanmaku(searchBox.getAid()); 343 | } 344 | sechL->setText(searchBox.getKey()); 345 | }); 346 | addAction(fileA); 347 | addAction(danmA); 348 | addAction(sechA); 349 | connect(fileB,&QPushButton::clicked,fileA,&QAction::trigger); 350 | connect(danmB,&QPushButton::clicked,danmA,&QAction::trigger); 351 | connect(sechB,&QPushButton::clicked,sechA,&QAction::trigger); 352 | connect(danmL,&QLineEdit::returnPressed,danmA,&QAction::trigger); 353 | connect(sechL,&QLineEdit::returnPressed,sechA,&QAction::trigger); 354 | alphaT=new QLabel(this); 355 | alphaT->setText(tr("Danmaku Alpha")); 356 | alphaS=new QSlider(this); 357 | alphaS->setOrientation(Qt::Horizontal); 358 | alphaS->setRange(0,100); 359 | connect(alphaS,&QSlider::valueChanged,[this](int _alpha){ 360 | Danmaku::instance()->setAlpha(_alpha); 361 | if (alphaS->isVisible()){ 362 | QPoint p; 363 | p.setX(QCursor::pos().x()); 364 | p.setY(alphaS->mapToGlobal(alphaS->rect().center()).y()); 365 | QToolTip::showText(p,QString::number(_alpha)); 366 | } 367 | }); 368 | connect(Danmaku::instance(),&Danmaku::alphaChanged,alphaS,&QSlider::setValue); 369 | powerT=new QLabel(this); 370 | powerT->setText(tr("Danmaku Power")); 371 | powerL=new QLineEdit(this); 372 | powerL->setValidator(new QRegularExpressionValidator(QRegularExpression("^\\w*$"),powerL)); 373 | connect(powerL,&QLineEdit::editingFinished,[this](){ 374 | Render::instance()->setRefreshRate(powerL->text().toInt()); 375 | }); 376 | connect(Render::instance(),&Render::refreshRateChanged,[this](int fps){ 377 | if(fps==0){ 378 | powerL->clear(); 379 | } 380 | else{ 381 | powerL->setText(QString::number(fps)); 382 | } 383 | }); 384 | localT=new QLabel(this); 385 | localT->setText(tr("Local Danmaku")); 386 | localC=new QCheckBox(this); 387 | connect(localC,&QCheckBox::stateChanged,[this](int state){ 388 | bool local=state==Qt::Checked; 389 | danmL->clear(); 390 | danmL->setReadOnly(local); 391 | danmB->setText(local?tr("Open"):tr("Load")); 392 | danmL->setPlaceholderText(local?tr("choose a local danmaku"):tr("input av/ac number")); 393 | for(const Record &r:Danmaku::instance()->getPool()){ 394 | if(QUrl(r.source).isLocalFile()==local){ 395 | danmL->setText(r.string); 396 | } 397 | } 398 | danmL->setCursorPosition(0); 399 | Config::setValue("/Danmaku/Local",local); 400 | }); 401 | localC->setChecked(Config::getValue("/Danmaku/Local",false)); 402 | subT=new QLabel(this); 403 | subT->setText(tr("Protect Sub")); 404 | subC=new QCheckBox(this); 405 | subC->setChecked(Config::getValue("/Danmaku/Protect",false)); 406 | connect(subC,&QCheckBox::stateChanged,[this](int state){ 407 | Config::setValue("/Danmaku/Protect",state==Qt::Checked); 408 | }); 409 | loopT=new QLabel(this); 410 | loopT->setText(tr("Loop Playback")); 411 | loopC=new QCheckBox(this); 412 | loopC->setChecked(Config::getValue("/Playing/Loop",false)); 413 | connect(loopC,&QCheckBox::stateChanged,[this](int state){ 414 | Config::setValue("/Playing/Loop",state==Qt::Checked); 415 | }); 416 | 417 | animation=new QPropertyAnimation(this,"pos",this); 418 | animation->setDuration(200); 419 | animation->setEasingCurve(QEasingCurve::OutCubic); 420 | connect(animation,&QPropertyAnimation::finished,[this](){ 421 | if(!isPoped){ 422 | hide(); 423 | lApp->mainWidget()->setFocus(); 424 | } 425 | }); 426 | connect(Load::instance(),&Load::stateChanged,[this](int state){ 427 | Load::Task *task=Load::instance()->getHead(); 428 | auto syncDanmL=[&](){ 429 | QString fix(task->code); 430 | if(!task->code.isEmpty()&&task->processer->regular(fix)){ 431 | DanmEdit *danmE=dynamic_cast(danmL); 432 | danmE->setCode(fix); 433 | danmE->setCursorPosition(0); 434 | danmE->clearFocus(); 435 | } 436 | }; 437 | switch(state){ 438 | case Load::Page: 439 | isStay=1; 440 | syncDanmL(); 441 | break; 442 | case Load::Part: 443 | if(isPoped&&animation->state()==QAbstractAnimation::Stopped){ 444 | danmC->complete(); 445 | danmC->popup()->setCurrentIndex(QModelIndex()); 446 | } 447 | case Load::File: 448 | localC->setChecked(task->request.url().isLocalFile()); 449 | syncDanmL(); 450 | case Load::Code: 451 | isStay=1; 452 | break; 453 | case Load::None: 454 | isStay=0; 455 | default: 456 | break; 457 | } 458 | }); 459 | connect(Load::instance(),&Load::errorOccured,[this](int state){ 460 | switch(state){ 461 | case 301: 462 | pop(); 463 | isStay=1; 464 | danmL->setFocus(); 465 | break; 466 | default: 467 | isStay=0; 468 | break; 469 | } 470 | }); 471 | connect(APlayer::instance(),&APlayer::mediaChanged,[this](QString _file){ 472 | fileL->setText(QFileInfo(_file).fileName()); 473 | fileL->setCursorPosition(0); 474 | }); 475 | hide(); 476 | } 477 | 478 | void Menu::resizeEvent(QResizeEvent *e) 479 | { 480 | double f=font().pointSizeF(); 481 | int x=logicalDpiX()*f/72,y=logicalDpiY()*f/72,w=e->size().width(); 482 | fileL->setGeometry(QRect(0.83*x,2.08*y,w-6.67*x,2.08*y)); 483 | danmL->setGeometry(QRect(0.83*x,5.42*y,w-6.67*x,2.08*y)); 484 | sechL->setGeometry(QRect(0.83*x,8.75*y,w-6.67*x,2.08*y)); 485 | fileB->setGeometry(QRect(w-5.42*x,2.08*y,4.58*x,2.08*y)); 486 | danmB->setGeometry(QRect(w-5.42*x,5.42*y,4.58*x,2.08*y)); 487 | sechB->setGeometry(QRect(w-5.42*x,8.75*y,4.58*x,2.08*y)); 488 | alphaT->setGeometry(QRect(0.83*x,12.08*y,w-1.67*x,2.08*y)); 489 | alphaS->setGeometry(QRect(0.83*x,14.17*y,w-1.67*x,1.25*y)); 490 | powerT->setGeometry(QRect(0.83*x,17.08*y,w-1.67*x,1.67*y)); 491 | powerL->setGeometry(QRect(w-3.33*x,17.08*y,2.50*x,1.67*y)); 492 | localT->setGeometry(QRect(0.83*x,20.00*y,w-1.67*x,2.08*y)); 493 | subT ->setGeometry(QRect(0.83*x,22.92*y,w-1.67*x,2.08*y)); 494 | loopT ->setGeometry(QRect(0.83*x,25.83*y,w-1.67*x,2.08*y)); 495 | int l=15*logicalDpiX()/96; 496 | localC->setGeometry(QRect(w-l/2-2.08*x,20.00*y,l,2.08*y)); 497 | subC ->setGeometry(QRect(w-l/2-2.08*x,22.92*y,l,2.08*y)); 498 | loopC ->setGeometry(QRect(w-l/2-2.08*x,25.83*y,l,2.08*y)); 499 | QWidget::resizeEvent(e); 500 | } 501 | 502 | bool Menu::eventFilter(QObject *o,QEvent *e) 503 | { 504 | switch(e->type()){ 505 | case QEvent::ContextMenu: 506 | { 507 | isStay=1; 508 | QMenu *m=dynamic_cast(o)->createStandardContextMenu(); 509 | m->exec(dynamic_cast(e)->globalPos()); 510 | delete m; 511 | isStay=0; 512 | return 1; 513 | } 514 | case QEvent::FocusIn: 515 | isStay=1; 516 | return 0; 517 | case QEvent::FocusOut: 518 | isStay=0; 519 | return 0; 520 | default: 521 | return 0; 522 | } 523 | } 524 | 525 | void Menu::pop() 526 | { 527 | if(!isPoped&&animation->state()==QAbstractAnimation::Stopped){ 528 | show(); 529 | animation->setStartValue(pos()); 530 | animation->setEndValue(pos()+QPoint(width(),0)); 531 | animation->start(); 532 | isPoped=true; 533 | } 534 | } 535 | 536 | void Menu::push(bool force) 537 | { 538 | if(isPoped&&animation->state()==QAbstractAnimation::Stopped&&(!preferStay()||force)){ 539 | if(force){ 540 | isStay=false; 541 | } 542 | animation->setStartValue(pos()); 543 | animation->setEndValue(pos()-QPoint(width(),0)); 544 | animation->start(); 545 | isPoped=false; 546 | } 547 | } 548 | 549 | void Menu::terminate() 550 | { 551 | if(animation->state()!=QAbstractAnimation::Stopped){ 552 | animation->setCurrentTime(animation->totalDuration()); 553 | } 554 | } 555 | -------------------------------------------------------------------------------- /src/Search.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Search.cpp 6 | * Time: 2013/04/18 7 | * Author: Chaserhkj 8 | * Contributor: Lysine 9 | * 10 | * Lysine is a student majoring in Software Engineering 11 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 12 | * 13 | * This program is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | 23 | * You should have received a copy of the GNU General Public License 24 | * along with this program. If not, see . 25 | * 26 | =========================================================================*/ 27 | 28 | #include "Search.h" 29 | #include "APlayer.h" 30 | #include "Config.h" 31 | #include "Local.h" 32 | #include "Utils.h" 33 | 34 | namespace 35 | { 36 | QHash getChannel(QString name) 37 | { 38 | static QHash> m; 39 | if(!m.contains(name)){ 40 | QFile file(":/Text/DATA"); 41 | file.open(QIODevice::ReadOnly|QIODevice::Text); 42 | QJsonObject data=QJsonDocument::fromJson(file.readAll()).object()[name+"Channel"].toObject(); 43 | for(auto iter=data.begin();iter!=data.end();++iter){ 44 | m[name][iter.key().toInt()]=iter.value().toString(); 45 | } 46 | } 47 | return m[name]; 48 | } 49 | } 50 | 51 | Search::Search(QWidget *parent):QDialog(parent) 52 | { 53 | double x=logicalDpiX()/72.0,y=logicalDpiY()/72.0; 54 | pageNum=pageCur=-1; 55 | isWaiting=false; 56 | auto outerLayout=new QVBoxLayout; 57 | 58 | //Head 59 | auto keywdLayout=new QHBoxLayout; 60 | 61 | keywE=new QLineEdit(this); 62 | keywdLayout->addWidget(keywE); 63 | 64 | orderC=new QComboBox(this); 65 | orderC->setEditable(false); 66 | sitesC=new QComboBox(this); 67 | QStringList sites; 68 | sites<<"Bilibili"<<"AcFun"<<"AcPlay"; 69 | sitesC->addItems(sites); 70 | sitesC->setEditable(false); 71 | orderC->setFixedWidth(60*x); 72 | sitesC->setFixedWidth(60*x); 73 | keywdLayout->addWidget(orderC); 74 | keywdLayout->addWidget(sitesC); 75 | 76 | searchB=new QPushButton(this); 77 | searchB->setText(tr("Search")); 78 | keywdLayout->addWidget(searchB); 79 | 80 | outerLayout->addLayout(keywdLayout); 81 | 82 | //Body 83 | resultW=new QTreeWidget(this); 84 | resultW->setIconSize(QSize(90*x,67.5*y)); 85 | resultW->setIndentation(0); 86 | outerLayout->addWidget(resultW); 87 | setSite(); 88 | 89 | //Tail 90 | auto pageLayout=new QHBoxLayout; 91 | statusL=new QLabel(tr("Ready"),this); 92 | pageLayout->addWidget(statusL); 93 | pageLayout->addStretch(); 94 | 95 | pageT=new QLabel(tr("Page"),this); 96 | pageLayout->addWidget(pageT); 97 | 98 | pageE=new QLineEdit(this); 99 | pageE->setFixedWidth(30*x); 100 | pageLayout->addWidget(pageE); 101 | 102 | pageL=new QLabel(this); 103 | pageL->setFixedWidth(30*x); 104 | pageLayout->addWidget(pageL); 105 | 106 | pageGoB=new QPushButton(tr("Goto"),this); 107 | pageUpB=new QPushButton(tr("PgUp"),this); 108 | pageDnB=new QPushButton(tr("PgDn"),this); 109 | pageLayout->addWidget(pageGoB); 110 | pageLayout->addWidget(pageUpB); 111 | pageLayout->addWidget(pageDnB); 112 | outerLayout->addLayout(pageLayout); 113 | 114 | auto responseLayout=new QHBoxLayout; 115 | okB=new QPushButton(tr("Confirm"),this); 116 | ccB=new QPushButton(tr("Cancel"), this); 117 | responseLayout->addWidget(okB); 118 | responseLayout->addStretch(); 119 | responseLayout->addWidget(ccB); 120 | outerLayout->addLayout(responseLayout); 121 | 122 | setLayout(outerLayout); 123 | setWindowTitle(tr("Search")); 124 | setMinimumSize(450*x,300*y); 125 | resize(675*x,390*y); 126 | Utils::setCenter(this); 127 | 128 | resultW->setSelectionMode(QAbstractItemView::SingleSelection); 129 | resultW->setColumnWidth(0,90*x+6); 130 | resultW->setColumnWidth(1,45*x); 131 | resultW->setColumnWidth(2,45*x); 132 | resultW->setColumnWidth(4,75*x); 133 | resultW->header()->setStretchLastSection(false); 134 | resultW->header()->setSectionResizeMode(3,QHeaderView::Stretch); 135 | 136 | connect(orderC,&QComboBox::currentTextChanged,[this](QString){ 137 | if(!isWaiting&&resultW->topLevelItemCount()>0){ 138 | searchB->click(); 139 | } 140 | }); 141 | 142 | connect(sitesC,&QComboBox::currentTextChanged,[this](QString){ 143 | setSite(); 144 | if(!isWaiting&&resultW->topLevelItemCount()>0){ 145 | searchB->click(); 146 | } 147 | }); 148 | 149 | connect(searchB,&QPushButton::clicked,[this](){ 150 | if(isWaiting){ 151 | QMessageBox::warning(this,tr("Warning"),tr("A request is pending.")); 152 | return; 153 | } 154 | clearSearch(); 155 | startSearch(); 156 | }); 157 | 158 | auto jump=[this](int page){ 159 | if(pageNum==-1) { 160 | QMessageBox::warning(this,tr("Warning"),tr("No search in progress.")); 161 | } 162 | else if(isWaiting) { 163 | QMessageBox::warning(this,tr("Warning"),tr("A request is pending.")); 164 | } 165 | else if(page<1||page>pageNum) { 166 | QMessageBox::warning(this,tr("Warning"),tr("Page num out of range.")); 167 | } 168 | else{ 169 | getData(page); 170 | } 171 | }; 172 | 173 | connect(pageGoB,&QPushButton::clicked,[jump,this](){jump(pageE->text().toInt());}); 174 | connect(pageUpB,&QPushButton::clicked,[jump,this](){jump(pageCur-1);}); 175 | connect(pageDnB,&QPushButton::clicked,[jump,this](){jump(pageCur+1);}); 176 | 177 | connect(okB,&QPushButton::clicked,this,&Search::accept); 178 | connect(ccB,&QPushButton::clicked,this,&Search::reject); 179 | connect(resultW,&QTreeWidget::itemActivated,this,&Search::accept); 180 | 181 | manager=new QNetworkAccessManager(this); 182 | Config::setManager(manager); 183 | connect(manager,&QNetworkAccessManager::finished,[this](QNetworkReply *reply){ 184 | reply->deleteLater(); 185 | remain.remove(reply); 186 | QNetworkRequest redirect(reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl()); 187 | if (redirect.url().isValid()){ 188 | remain+=reply->manager()->get(redirect); 189 | return; 190 | } 191 | if (reply->error()==QNetworkReply::OperationCanceledError){ 192 | return; 193 | } 194 | QVariant image=reply->request().attribute(QNetworkRequest::User); 195 | if (image.isValid()){ 196 | QTreeWidgetItem *line=resultW->topLevelItem(image.toInt()); 197 | if (line&&line->icon(0).isNull()){ 198 | QPixmap pixmap; 199 | pixmap.loadFromData(reply->readAll()); 200 | if(!pixmap.isNull()){ 201 | pixmap=pixmap.scaled(resultW->iconSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); 202 | } 203 | line->setIcon(0,QIcon(pixmap)); 204 | } 205 | return; 206 | } 207 | if (reply->error()!=QNetworkReply::NoError){ 208 | QString info=tr("Network error occurred, error code: %1").arg(reply->error()); 209 | QString sugg=Local::instance()->suggestion(reply->error()); 210 | QMessageBox::warning(this,tr("Network Error"),sugg.isEmpty()?info:(info+'\n'+sugg)); 211 | clearSearch(); 212 | isWaiting=false; 213 | } 214 | switch(Utils::parseSite(reply->url().url())){ 215 | case Utils::Bilibili: 216 | { 217 | QString data(reply->readAll()); 218 | QRegularExpression r; 219 | QRegularExpressionMatch m; 220 | if (pageNum==-1){ 221 | r.setPattern("(?<=page=)\\d+"); 222 | m=r.match(data,data.indexOf("endPage")); 223 | pageNum=m.hasMatch()?m.captured().toInt():1; 224 | pageL->setText(QString("/%1").arg(pageNum)); 225 | } 226 | QStringList ary=data.split("
  • =2){ 228 | QString &last=ary.last(); 229 | last.truncate(last.lastIndexOf("
  • ")+5); 230 | ary.removeFirst(); 231 | for(const QString &item:ary){ 232 | QTreeWidgetItem *row=new QTreeWidgetItem(resultW); 233 | r.setPattern("av\\d+"); 234 | m=r.match(item); 235 | row->setData(0,Qt::UserRole,m.captured()); 236 | row->setSizeHint(0,QSize(0,resultW->iconSize().height()+3)); 237 | r.setPattern("(?<=src=\")[^\"']+"); 238 | m=r.match(item,m.capturedEnd()); 239 | QNetworkRequest request(QUrl(m.captured())); 240 | request.setAttribute(QNetworkRequest::User,resultW->invisibleRootItem()->childCount()-1); 241 | remain+=manager->get(request); 242 | r.setPattern("(?<=)[^<]+"); 243 | m=r.match(item,m.capturedEnd()); 244 | row->setText(4,Utils::decodeXml(m.captured())); 245 | r.setPattern("(?<=\\s).+"); 246 | m=r.match(item,m.capturedEnd()); 247 | row->setText(3,Utils::decodeXml(m.captured())); 248 | r.setPattern("class=\"upper\""); 249 | m=r.match(item,m.capturedEnd()); 250 | r.setPattern("(?<=>)[^<]+"); 251 | m=r.match(item,m.capturedEnd()); 252 | row->setText(5,Utils::decodeXml(m.captured())); 253 | r.setPattern(")[\\s\\d\\-]+(?=)"); 256 | auto i=r.globalMatch(item,m.capturedEnd()); 257 | row->setText(1,i.next().captured().simplified()); 258 | i.next(); 259 | row->setText(2,i.next().captured().simplified()); 260 | r.setPattern("(?<=class=\"intro\">).*(?=)"); 261 | m=r.match(item,i.next().capturedEnd()); 262 | row->setToolTip(3,Utils::decodeXml(m.captured())); 263 | } 264 | } 265 | statusL->setText(tr("Finished")); 266 | isWaiting=false; 267 | break; 268 | } 269 | case Utils::AcFun: 270 | { 271 | QJsonObject page=QJsonDocument::fromJson(reply->readAll()).object()["data"].toObject()["page"].toObject(); 272 | if (pageNum==-1){ 273 | pageNum=page["totalCount"].toDouble()/page["pageSize"].toDouble()+0.5; 274 | pageL->setText(QString("/%1").arg(pageNum)); 275 | } 276 | QJsonArray list=page["list"].toArray(); 277 | for(int i=0;i 72 && channelId < 77)) { 281 | continue; 282 | } 283 | QStringList content; 284 | content+=""; 285 | content+=QString::number((int)item["views"].toDouble()); 286 | content+=QString::number((int)item["comments"].toDouble()); 287 | content+=Utils::decodeXml(item["title"].toString()); 288 | content+=getChannel("AcFun")[channelId]; 289 | content+=Utils::decodeXml(item["username"].toString()); 290 | QTreeWidgetItem *row=new QTreeWidgetItem(resultW,content); 291 | row->setData(0,Qt::UserRole,item["contentId"].toString()); 292 | row->setSizeHint(0,QSize(0,resultW->iconSize().height()+3)); 293 | row->setToolTip(3,item["description"].toString()); 294 | QNetworkRequest request(QUrl(item["titleImg"].toString())); 295 | request.setAttribute(QNetworkRequest::User,resultW->invisibleRootItem()->childCount()-1); 296 | remain+=manager->get(request); 297 | } 298 | statusL->setText(tr("Finished")); 299 | isWaiting=false; 300 | break; 301 | } 302 | case Utils::AcPlay: 303 | { 304 | QJsonObject json=QJsonDocument::fromJson(reply->readAll()).object(); 305 | for(QJsonValue iter:json["Matches"].toArray()){ 306 | QJsonObject item=iter.toObject(); 307 | QStringList content; 308 | content+=item["AnimeTitle"].toString(); 309 | content+=""; 310 | content+=""; 311 | content+=item["EpisodeTitle"].toString(); 312 | content+=getChannel("AcPlay")[item["Type"].toInt()]; 313 | content+=""; 314 | QTreeWidgetItem *row=new QTreeWidgetItem(resultW,content); 315 | row->setData(0,Qt::UserRole,QString("dd%1").arg(item["EpisodeId"].toInt())); 316 | row->setSizeHint(0,QSize(0,resultW->iconSize().height()+3)); 317 | } 318 | for(QJsonValue iter:json["Animes"].toArray()){ 319 | QJsonObject item=iter.toObject(); 320 | QString title=item["Title"].toString(),type=getChannel("AcPlay")[item["Type"].toInt()]; 321 | for(QJsonValue epsd:item["Episodes"].toArray()){ 322 | item=epsd.toObject(); 323 | QStringList content; 324 | content+=title; 325 | content+=""; 326 | content+=""; 327 | content+=item["Title"].toString(); 328 | content+=type; 329 | content+=""; 330 | QTreeWidgetItem *row=new QTreeWidgetItem(resultW,content); 331 | row->setData(0,Qt::UserRole,QString("dd%1").arg(item["Id"].toInt())); 332 | row->setSizeHint(0,QSize(0,resultW->iconSize().height()+3)); 333 | } 334 | } 335 | pageNum=1; 336 | pageL->setText("/1"); 337 | statusL->setText(tr("Finished")); 338 | isWaiting=false; 339 | break; 340 | } 341 | default: 342 | break; 343 | } 344 | }); 345 | } 346 | 347 | void Search::getData(int pageNum) 348 | { 349 | for(QNetworkReply *r:QSet(remain)){ 350 | r->abort(); 351 | } 352 | key=keywE->text(); 353 | QUrl url; 354 | switch(sitesC->currentIndex()){ 355 | case 0: 356 | { 357 | if (key.isEmpty()){ 358 | return; 359 | } 360 | url=QUrl("http://www."+ 361 | Utils::customUrl(Utils::Bilibili)+ 362 | "/search"); 363 | auto order=getOrder(Utils::Bilibili); 364 | QUrlQuery query; 365 | query.addQueryItem("keyword",key); 366 | query.addQueryItem("page",QString::number(pageNum)); 367 | query.addQueryItem("orderby",order[orderC->currentIndex()]); 368 | query.addQueryItem("pagesize","20"); 369 | url.setQuery(query); 370 | break; 371 | } 372 | case 1: 373 | { 374 | if (key.isEmpty()){ 375 | return; 376 | } 377 | url=QUrl("http://search."+ 378 | Utils::customUrl(Utils::AcFun)+ 379 | "/search"); 380 | auto order=getOrder(Utils::AcFun); 381 | QUrlQuery query; 382 | query.addQueryItem("q",key); 383 | query.addQueryItem("sortType","-1"); 384 | query.addQueryItem("field","title"); 385 | query.addQueryItem("sortField",order[orderC->currentIndex()]); 386 | query.addQueryItem("pageNo",QString::number(pageNum)); 387 | query.addQueryItem("pageSize","20"); 388 | url.setQuery(query); 389 | break; 390 | } 391 | case 2: 392 | { 393 | QUrlQuery query; 394 | switch(orderC->currentIndex()){ 395 | case 0: 396 | { 397 | if (key.isEmpty()){ 398 | return; 399 | } 400 | QStringList args=key.split("#"); 401 | url=QUrl("http://api."+ 402 | Utils::customUrl(Utils::AcPlay)+ 403 | "/api/v1/searchall/"+ 404 | (args.size()==2?args.join("/"):key)); 405 | break; 406 | } 407 | case 1: 408 | { 409 | if (key.isEmpty()){ 410 | return; 411 | } 412 | if (QRegularExpression("^[^#]*#\\d+$").match(key).hasMatch()){ 413 | QMessageBox::warning(this,tr("Match Error"),tr("Format {anime}#{episode} needed.")); 414 | return; 415 | } 416 | QStringList args=key.split("#"); 417 | url=QUrl("http://api."+ 418 | Utils::customUrl(Utils::AcPlay)+ 419 | "/api/v1/search/TVAnime"); 420 | query.addQueryItem("anime" ,args[0]); 421 | query.addQueryItem("episode",args[1]); 422 | break; 423 | } 424 | case 2: 425 | { 426 | if (key.isEmpty()){ 427 | return; 428 | } 429 | QStringList args=key.split("#"); 430 | url=QUrl("http://api."+ 431 | Utils::customUrl(Utils::AcPlay)+ 432 | "/api/v1/search/Other"); 433 | query.addQueryItem("anime" ,args.size()==2?args[0]:key); 434 | query.addQueryItem("episode",args.size()==2?args[1]:QString("")); 435 | break; 436 | } 437 | case 3: 438 | { 439 | QFile file(key); 440 | if(!file.exists()){ 441 | file.setFileName(APlayer::instance()->getMedia()); 442 | } 443 | if (file.exists()){ 444 | file.open(QIODevice::ReadOnly); 445 | url=QUrl("http://api."+ 446 | Utils::customUrl(Utils::AcPlay)+ 447 | "/api/v1/match"); 448 | query.addQueryItem("fileName",QFileInfo(file).baseName()); 449 | query.addQueryItem("hash",QCryptographicHash::hash(file.read(0x1000000),QCryptographicHash::Md5).toHex()); 450 | query.addQueryItem("length",QString::number(file.size())); 451 | } 452 | else{ 453 | QMessageBox::warning(this,tr("Match Error"),tr("Please open a video or type in the file path.")); 454 | return; 455 | } 456 | } 457 | } 458 | url.setQuery(query); 459 | } 460 | } 461 | resultW->clear(); 462 | isWaiting=true; 463 | pageCur=pageNum; 464 | pageE->setText(QString::number(pageCur)); 465 | statusL->setText(tr("Requesting")); 466 | remain+=manager->get(QNetworkRequest(url)); 467 | } 468 | 469 | #define tr 470 | QList Search::getOrder(int site) 471 | { 472 | QList od; 473 | switch(site){ 474 | case Utils::AcFun: 475 | od<setText(key); 506 | searchB->click(); 507 | } 508 | 509 | void Search::setSite() 510 | { 511 | QStringList header,options; 512 | switch(sitesC->currentIndex()){ 513 | case 0: 514 | header<clear(); 534 | orderC->addItems(options); 535 | orderC->setCurrentIndex(0); 536 | resultW->setHeaderLabels(header); 537 | isWaiting=false; 538 | } 539 | 540 | void Search::accept() 541 | { 542 | QTreeWidgetItem *item=resultW->currentItem(); 543 | if (item){ 544 | aid=item->data(0,Qt::UserRole).toString(); 545 | QDialog::accept(); 546 | } 547 | else{ 548 | QMessageBox::warning(this,tr("Warning"),tr("No video has been chosen.")); 549 | } 550 | } 551 | 552 | void Search::startSearch() 553 | { 554 | getData(1); 555 | } 556 | 557 | void Search::clearSearch() 558 | { 559 | resultW->clear(); 560 | pageNum=-1; 561 | pageCur=-1; 562 | pageE->clear(); 563 | pageL->clear(); 564 | statusL->setText(tr("Ready")); 565 | } 566 | -------------------------------------------------------------------------------- /src/Editor.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: Editor.cpp 6 | * Time: 2013/06/30 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "Editor.h" 28 | #include "APlayer.h" 29 | #include "Config.h" 30 | #include "Danmaku.h" 31 | #include "List.h" 32 | #include "Load.h" 33 | #include "Utils.h" 34 | 35 | namespace{ 36 | class ListEditor:public QListView 37 | { 38 | public: 39 | explicit ListEditor(QWidget *parent=0): 40 | QListView(parent) 41 | { 42 | setModel(List::instance()); 43 | setMinimumWidth(200*logicalDpiX()/96); 44 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 45 | setSelectionMode(ExtendedSelection); 46 | setDragDropMode(InternalMove); 47 | setContextMenuPolicy(Qt::ActionsContextMenu); 48 | setIconSize(QSize(15*logicalDpiX()/96,15*logicalDpiY()/96)); 49 | 50 | megA=new QAction(Editor::tr("Merge"),this); 51 | connect(megA,&QAction::triggered,[this](){ 52 | List::instance()->merge(selectionModel()->selectedRows()); 53 | setCurrentIndex(QModelIndex()); 54 | }); 55 | addAction(megA); 56 | 57 | grpA=new QAction(Editor::tr("Group"),this); 58 | connect(grpA,&QAction::triggered,[this](){ 59 | List::instance()->group(selectionModel()->selectedRows()); 60 | setCurrentIndex(QModelIndex()); 61 | }); 62 | addAction(grpA); 63 | 64 | splA=new QAction(Editor::tr("Split"),this); 65 | splA->setShortcut(QKeySequence("S")); 66 | connect(splA,&QAction::triggered,[this](){ 67 | List::instance()->split(selectionModel()->selectedRows()); 68 | setCurrentIndex(QModelIndex()); 69 | }); 70 | addAction(splA); 71 | 72 | delA=new QAction(Editor::tr("Delete"),this); 73 | delA->setShortcut(QKeySequence("Del")); 74 | connect(delA,&QAction::triggered,[this](){ 75 | List::instance()->waste(selectionModel()->selectedRows()); 76 | setCurrentIndex(QModelIndex()); 77 | }); 78 | addAction(delA); 79 | 80 | connect(this,SIGNAL(doubleClicked(QModelIndex)),List::instance(),SLOT(jumpToIndex(QModelIndex))); 81 | } 82 | 83 | private: 84 | QAction *megA; 85 | QAction *grpA; 86 | QAction *splA; 87 | QAction *delA; 88 | 89 | void dragEnterEvent(QDragEnterEvent *e) 90 | { 91 | if (e->mimeData()->hasFormat("text/uri-list")){ 92 | e->acceptProposedAction(); 93 | } 94 | QListView::dragEnterEvent(e); 95 | } 96 | 97 | void dropEvent(QDropEvent *e) 98 | { 99 | if (e->mimeData()->hasFormat("text/uri-list")){ 100 | for(const QString &item:QString(e->mimeData()->data("text/uri-list")).split('\n',QString::SkipEmptyParts)){ 101 | List::instance()->appendMedia(QUrl(item).toLocalFile().trimmed()); 102 | } 103 | } 104 | QListView::dropEvent(e); 105 | } 106 | 107 | void currentChanged(const QModelIndex &c, 108 | const QModelIndex &p) 109 | { 110 | QListView::currentChanged(c,p); 111 | selectionModel()->setCurrentIndex(QModelIndex(),QItemSelectionModel::NoUpdate); 112 | } 113 | 114 | QSize sizeHint() const 115 | { 116 | return QSize(150*logicalDpiX()/96,QListView::sizeHint().height()); 117 | } 118 | 119 | }; 120 | 121 | class Calendar:public QDialog 122 | { 123 | public: 124 | explicit Calendar(QWidget *parent=0): 125 | QDialog(parent,Qt::Popup) 126 | { 127 | auto layout=new QGridLayout(this); 128 | date=new QLabel(this); 129 | date->setAlignment(Qt::AlignCenter); 130 | layout->addWidget(date,0,1); 131 | prev=new QToolButton(this); 132 | next=new QToolButton(this); 133 | prev->setIcon(QIcon(":/Picture/previous.png")); 134 | next->setIcon(QIcon(":/Picture/next.png")); 135 | connect(prev,&QToolButton::clicked,[this](){ 136 | setCurrentPage(page.addMonths(-1)); 137 | }); 138 | connect(next,&QToolButton::clicked,[this](){ 139 | setCurrentPage(page.addMonths(+1)); 140 | }); 141 | layout->addWidget(prev,0,0); 142 | layout->addWidget(next,0,2); 143 | table=new QTableWidget(7,7,this); 144 | table->setShowGrid(false); 145 | table->setSelectionMode(QAbstractItemView::SingleSelection); 146 | table->verticalHeader() ->hide(); 147 | table->verticalHeader() ->setSectionResizeMode(QHeaderView::Stretch); 148 | table->horizontalHeader()->hide(); 149 | table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); 150 | table->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff); 151 | table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 152 | connect(table,&QTableWidget::itemDoubleClicked,this,&QDialog::accept); 153 | layout->addWidget(table,1,0,1,3); 154 | resize(280*logicalDpiX()/96,250*logicalDpiY()/96); 155 | Utils::setCenter(this); 156 | } 157 | 158 | QDate selectedDate() 159 | { 160 | QTableWidgetItem *item=table->currentItem(); 161 | if(item!=NULL){ 162 | QDate selected=item->data(Qt::UserRole).toDate(); 163 | if(selected<=QDate::currentDate()){ 164 | return selected; 165 | } 166 | } 167 | return QDate(); 168 | } 169 | 170 | void setCurrentDate(QDate _d) 171 | { 172 | curr=_d; 173 | } 174 | 175 | void setCurrentPage(QDate _d) 176 | { 177 | page.setDate(_d.year(),_d.month(),1); 178 | table->clear(); 179 | for(int day=1;day<=7;++day){ 180 | QTableWidgetItem *item=new QTableWidgetItem(QDate::shortDayName(day)); 181 | item->setFlags(0); 182 | QFont f=item->font(); 183 | f.setBold(true); 184 | item->setFont(f); 185 | item->setData(Qt::TextColorRole,QColor(Qt::black)); 186 | table->setItem(0,day-1,item); 187 | } 188 | int row=1; 189 | for(QDate iter=page;iter.month()==page.month();iter=iter.addDays(1)){ 190 | QTableWidgetItem *item=new QTableWidgetItem(QString::number(iter.day())); 191 | item->setData(Qt::UserRole,iter); 192 | item->setData(Qt::TextAlignmentRole,Qt::AlignCenter); 193 | if(!count.contains(iter)){ 194 | item->setFlags(0); 195 | } 196 | else{ 197 | item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); 198 | } 199 | if(iter==curr){ 200 | item->setData(Qt::BackgroundRole,QColor(Qt::gray)); 201 | } 202 | table->setItem(row,iter.dayOfWeek()-1,item); 203 | if(item->column()==6){ 204 | ++row; 205 | } 206 | } 207 | for(int r=0;r<7;++r){ 208 | for(int c=0;c<7;++c){ 209 | if(table->item(r,c)==0){ 210 | QTableWidgetItem *item=new QTableWidgetItem; 211 | item->setFlags(0); 212 | table->setItem(r,c,item); 213 | } 214 | } 215 | } 216 | QDate last(page.year(),page.month(),page.daysInMonth()); 217 | prev->setEnabled(page>count.firstKey()); 218 | next->setEnabled(lastsetText(page.toString("MMM yyyy")); 220 | } 221 | 222 | void setCount(const QMap &_c) 223 | { 224 | count=_c; 225 | } 226 | 227 | private: 228 | QToolButton *prev; 229 | QToolButton *next; 230 | QTableWidget *table; 231 | QDate page; 232 | QDate curr; 233 | QLabel *date; 234 | QMap count; 235 | QDate currentLimit(); 236 | }; 237 | 238 | class Track:public QWidget 239 | { 240 | public: 241 | explicit Track(QWidget *parent=0): 242 | QWidget(parent) 243 | { 244 | int x=100*logicalDpiX()/96,y=100*logicalDpiY()/96; 245 | resize(parent->width(),y); 246 | m_record=nullptr; 247 | m_wheel=0; 248 | m_magnet<<0<<0<<0; 249 | m_label=new QLabel(this); 250 | m_label->setGeometry(0, 0, x-2,y*0.8-2); 251 | m_label->setFixedWidth(m_label->width()); 252 | m_label->setWordWrap(true); 253 | m_delay=new QLineEdit(this); 254 | m_delay->setGeometry(0,y*0.8-2,x-2,y/5); 255 | m_delay->setFrame(false); 256 | m_delay->setAlignment(Qt::AlignCenter); 257 | connect(m_delay,&QLineEdit::editingFinished,[this](){ 258 | QString expression=QRegularExpression("[\\s\\d\\.\\+\\-\\*\\/\\(\\)]+").match(m_delay->text()).captured(); 259 | if(!expression.isEmpty()){ 260 | delayRecord(Utils::evaluate(expression)*1000-m_record->delay); 261 | } 262 | else{ 263 | m_delay->setText(m_prefix.arg(m_record->delay/1000)); 264 | } 265 | }); 266 | } 267 | 268 | Record &getRecord() 269 | { 270 | return *m_record; 271 | } 272 | 273 | void setRecord(Record *r) 274 | { 275 | m_record=r; 276 | m_label->setText(r->string); 277 | m_label->setAlignment((m_label->sizeHint().height()>m_label->height()?Qt::AlignTop:Qt::AlignVCenter)|Qt::AlignHCenter); 278 | m_delay->setText(m_prefix.arg(r->delay/1000)); 279 | } 280 | 281 | void setPrefix(QString p){ 282 | m_prefix=p; 283 | } 284 | 285 | void setCurrent(qint64 c) 286 | { 287 | m_current=c; 288 | m_magnet[1]=c; 289 | } 290 | 291 | void setDuration(qint64 d) 292 | { 293 | m_duration=d; 294 | m_magnet[2]=d; 295 | } 296 | 297 | private: 298 | QLabel *m_label; 299 | QLineEdit *m_delay; 300 | 301 | Record *m_record; 302 | QString m_prefix; 303 | qint64 m_current; 304 | qint64 m_duration; 305 | QPoint m_point; 306 | QList m_magnet; 307 | int m_wheel; 308 | 309 | void paintEvent(QPaintEvent *e) 310 | { 311 | QPainter painter(this); 312 | double x=100*logicalDpiX()/96,y=100*logicalDpiY()/96; 313 | painter.fillRect(e->rect(),Qt::gray); 314 | painter.fillRect(0,0,x-2,y-2,Qt::white); 315 | if(x>=width()-5){ 316 | return; 317 | } 318 | int w=width()-x,m=0,d=m_duration/(w/5)+1,t=0; 319 | QHash c; 320 | for(const Comment &com:m_record->danmaku){ 321 | if(com.blocked){ 322 | continue; 323 | } 324 | if(com.time>=0&&com.time<=m_duration){ 325 | ++t; 326 | } 327 | int k=(com.time-m_record->delay)/d,v=c.value(k,0)+1; 328 | c.insert(k,v); 329 | m=v>m?v:m; 330 | } 331 | if(m!=0){ 332 | int o=w*m_record->delay/m_duration; 333 | if(!m_point.isNull()){ 334 | o+=mapFromGlobal(QCursor::pos()).x()-m_point.x(); 335 | for(qint64 p:m_magnet){ 336 | p=p*w/m_duration; 337 | if(qAbs(o-p)<5){ 338 | o=p; 339 | break; 340 | } 341 | } 342 | } 343 | painter.setClipRect(x,0,w,y); 344 | for(int j=0;jdanmaku.size()); 350 | QRect rect=painter.fontMetrics().boundingRect(x,0,w,y-2,Qt::AlignRight,count); 351 | rect.adjust(-5,0,0,0); 352 | painter.fillRect(rect,QColor(160,160,164,100)); 353 | painter.drawText(rect,Qt::AlignCenter,count); 354 | if(m_current>0){ 355 | painter.fillRect(m_current*w/m_duration+x,0,1,y,Qt::red); 356 | } 357 | } 358 | } 359 | 360 | void wheelEvent(QWheelEvent *e) 361 | { 362 | m_wheel+=e->angleDelta().y(); 363 | if(qAbs(m_wheel)>=120){ 364 | qint64 d=(m_record->delay/1000)*1000; 365 | d+=m_wheel>0?-1000:1000; 366 | d-=m_record->delay; 367 | delayRecord(d); 368 | m_wheel=0; 369 | } 370 | e->accept(); 371 | } 372 | 373 | void mouseMoveEvent(QMouseEvent *e) 374 | { 375 | if(!m_point.isNull()){ 376 | update(); 377 | } 378 | else{ 379 | m_point=e->pos(); 380 | } 381 | } 382 | 383 | void mouseReleaseEvent(QMouseEvent *e) 384 | { 385 | if(!m_point.isNull()){ 386 | int w=width()-100*logicalDpiX()/96; 387 | qint64 d=(e->x()-m_point.x())*m_duration/w; 388 | for(qint64 p:m_magnet){ 389 | if(qAbs(d+m_record->delay-p)delay; 391 | break; 392 | } 393 | } 394 | delayRecord(d); 395 | } 396 | m_point=QPoint(); 397 | } 398 | 399 | void delayRecord(qint64 delay) 400 | { 401 | m_record->delay+=delay; 402 | for(Comment &c:m_record->danmaku){ 403 | c.time+=delay; 404 | } 405 | QMetaObject::invokeMethod(Danmaku::instance(),"parse",Qt::QueuedConnection,Q_ARG(int,0x1|0x2)); 406 | } 407 | }; 408 | 409 | class MScroll:public QScrollBar 410 | { 411 | public: 412 | explicit MScroll(QWidget *parent=0): 413 | QScrollBar(parent) 414 | { 415 | } 416 | 417 | private: 418 | void paintEvent(QPaintEvent *e) 419 | { 420 | QScrollBar::paintEvent(e); 421 | QPainter painter(this); 422 | painter.setPen(Qt::gray); 423 | QPoint points[4]={rect().topLeft(),rect().topRight(),rect().bottomRight(),rect().bottomLeft()}; 424 | painter.drawPolyline(points,4); 425 | } 426 | }; 427 | 428 | class PoolEditor:public QWidget 429 | { 430 | public: 431 | explicit PoolEditor(QWidget *parent=0): 432 | QWidget(parent) 433 | { 434 | setMinimumWidth(100*logicalDpiX()/96); 435 | manager=new QNetworkAccessManager(this); 436 | Config::setManager(manager); 437 | 438 | scroll=new MScroll(this); 439 | scroll->setSingleStep(20); 440 | connect(scroll,&QScrollBar::valueChanged,[this](int value){ 441 | value=-value; 442 | for(QObject *c:widget->children()){ 443 | qobject_cast(c)->move(0,value); 444 | value+=100*logicalDpiY()/96; 445 | } 446 | }); 447 | 448 | widget=new QWidget(this); 449 | widget->setContextMenuPolicy(Qt::CustomContextMenu); 450 | connect(widget,&QWidget::customContextMenuRequested,[this](QPoint point){ 451 | Track *c=dynamic_cast(childAt(point)); 452 | if(!c){ 453 | return; 454 | } 455 | QMenu menu(this); 456 | Load *load=Load::instance(); 457 | auto &p=Danmaku::instance()->getPool(); 458 | auto &r=c->getRecord(); 459 | QAction *fullA=menu.addAction(Editor::tr("Full")); 460 | fullA->setEnabled(load->canFull(&r)); 461 | connect(fullA,&QAction::triggered,[=,&r](){ 462 | load->fullDanmaku(&r); 463 | }); 464 | menu.addSeparator(); 465 | connect(menu.addAction(Editor::tr("History")),&QAction::triggered,[=,&r](){ 466 | Calendar history(window()); 467 | QMap count; 468 | QDate c=r.limit==0?QDate::currentDate().addDays(1):QDateTime::fromTime_t(r.limit).date(); 469 | history.setCurrentDate(c); 470 | if(!load->canHist(&r)){ 471 | for(const Comment &c:r.danmaku){ 472 | ++count[QDateTime::fromTime_t(c.date).date()]; 473 | } 474 | count[QDate::currentDate().addDays(1)]=0; 475 | count.remove(count.firstKey()); 476 | history.setCount(count); 477 | history.setCurrentPage(c); 478 | } 479 | else{ 480 | QString cid=QFileInfo(r.source).baseName(); 481 | QString api("http://comment.%1/rolldate,%2"); 482 | api=api.arg(Utils::customUrl(Utils::Bilibili)); 483 | QNetworkReply *reply=manager->get(QNetworkRequest(api.arg(cid))); 484 | connect(reply,&QNetworkReply::finished,[&](){ 485 | QMap count; 486 | for(QJsonValue iter:QJsonDocument::fromJson(reply->readAll()).array()){ 487 | QJsonObject obj=iter.toObject(); 488 | QJsonValue time=obj["timestamp"],size=obj["new"]; 489 | count[QDateTime::fromTime_t(time.toVariant().toInt()).date()]+=size.toVariant().toInt(); 490 | } 491 | count[QDate::currentDate().addDays(1)]=0; 492 | history.setCount(count); 493 | history.setCurrentPage(c); 494 | }); 495 | } 496 | if(history.exec()==QDialog::Accepted){ 497 | QDate selected=history.selectedDate(); 498 | if(!load->canHist(&r)){ 499 | r.limit=selected.isValid()?QDateTime(selected).toTime_t():0; 500 | Danmaku::instance()->parse(0x2); 501 | widget->update(); 502 | } 503 | else{ 504 | load->loadHistory(&r,history.selectedDate()); 505 | } 506 | } 507 | }); 508 | connect(menu.addAction(Editor::tr("Delete")),&QAction::triggered,[&p,&r](){ 509 | for(auto i=p.begin();i!=p.end();++i){ 510 | if(&r==&(*i)){ 511 | p.erase(i); 512 | Danmaku::instance()->parse(0x1|0x2); 513 | break; 514 | } 515 | } 516 | }); 517 | menu.exec(mapToGlobal(point)); 518 | }); 519 | 520 | parseRecords(); 521 | connect(Danmaku::instance(),&Danmaku::modelReset,this,&PoolEditor::parseRecords); 522 | } 523 | 524 | private: 525 | QWidget *widget; 526 | QScrollBar *scroll; 527 | QNetworkAccessManager *manager; 528 | 529 | void paintEvent(QPaintEvent *) 530 | { 531 | if(!Danmaku::instance()->getPool().isEmpty()){ 532 | return; 533 | } 534 | QPainter p(this); 535 | p.setPen(Qt::gray); 536 | QString t=QStringLiteral("_(:з」∠)_"); 537 | QFont f=p.font(); 538 | f.setPointSize(55); 539 | f.setBold(true); 540 | p.setFont(f); 541 | QSize s=p.fontMetrics().size(0,t); 542 | if (width()children()); 557 | QList &pool=Danmaku::instance()->getPool(); 558 | if(!pool.isEmpty()){ 559 | qint64 duration=APlayer::instance()->getDuration(); 560 | for(Record &r:pool){ 561 | if(APlayer::instance()->getDuration()<=0){ 562 | for(const Comment &c:r.danmaku){ 563 | duration=qMax(c.time-r.delay,duration); 564 | } 565 | } 566 | } 567 | int height=0; 568 | for(Record &r:pool){ 569 | Track *t=new Track(widget); 570 | t->setPrefix(Editor::tr("Delay: %1s")); 571 | t->move(0,height); 572 | height+=100*logicalDpiY()/96; 573 | t->setCurrent(APlayer::instance()->getTime()); 574 | t->setDuration(duration); 575 | t->setRecord(&r); 576 | t->show(); 577 | } 578 | } 579 | parseLayouts(); 580 | update(); 581 | } 582 | 583 | void parseLayouts() 584 | { 585 | double y=100*logicalDpiY()/96; 586 | QRect r=rect(); 587 | if (widget->children().size()*y-2>r.height()){ 588 | scroll->show(); 589 | scroll->setGeometry(r.adjusted(r.width()-scroll->width(),0,0,0)); 590 | widget->setGeometry(r.adjusted(0,0,-scroll->width(),0)); 591 | } 592 | else{ 593 | scroll->hide(); 594 | widget->setGeometry(r); 595 | } 596 | QObjectList c=widget->children(); 597 | scroll->setRange(0,c.size()*y-r.height()-2); 598 | scroll->setValue(c.size()?-qobject_cast(c.first())->y():0); 599 | scroll->setPageStep(r.height()); 600 | for(QObject *o:c){ 601 | QWidget *w=qobject_cast(o); 602 | if (w){ 603 | w->resize(widget->width(),y); 604 | } 605 | } 606 | } 607 | 608 | void resizeEvent(QResizeEvent *) 609 | { 610 | parseLayouts(); 611 | } 612 | }; 613 | } 614 | 615 | void Editor::exec(QWidget *parent) 616 | { 617 | static Editor *executing; 618 | if(!executing){ 619 | Editor editor(parent); 620 | executing=&editor; 621 | editor.QDialog::exec(); 622 | executing=nullptr; 623 | } 624 | else{ 625 | executing->activateWindow(); 626 | } 627 | } 628 | 629 | Editor::Editor(QWidget *parent): 630 | QDialog(parent) 631 | { 632 | setMinimumSize(650*logicalDpiX()/96,450*logicalDpiY()/96); 633 | setWindowTitle(tr("Editor")); 634 | 635 | QSplitter *splitter=new QSplitter(this); 636 | splitter->addWidget(list=new ListEditor(this)); 637 | splitter->setStretchFactor(0,0); 638 | splitter->addWidget(pool=new PoolEditor(this)); 639 | splitter->setStretchFactor(1,1); 640 | (new QGridLayout(this))->addWidget(splitter); 641 | setFocus(); 642 | Utils::setCenter(this); 643 | } 644 | -------------------------------------------------------------------------------- /src/APlayer.cpp: -------------------------------------------------------------------------------- 1 | /*======================================================================= 2 | * 3 | * Copyright (C) 2013 Lysine. 4 | * 5 | * Filename: VPlayer.cpp 6 | * Time: 2013/03/18 7 | * Author: Lysine 8 | * 9 | * Lysine is a student majoring in Software Engineering 10 | * from the School of Software, SUN YAT-SEN UNIVERSITY. 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | =========================================================================*/ 26 | 27 | #include "APlayer.h" 28 | #include "Config.h" 29 | #include "List.h" 30 | #include "Local.h" 31 | #include "Render.h" 32 | #include "Utils.h" 33 | 34 | APlayer *APlayer::ins=nullptr; 35 | 36 | #ifdef BACKEND_VLC 37 | extern "C" 38 | { 39 | #include 40 | } 41 | 42 | class VPlayer:public APlayer 43 | { 44 | public: 45 | enum Event 46 | { 47 | Init, 48 | Wait, 49 | Free, 50 | Fail 51 | }; 52 | 53 | explicit VPlayer(QObject *parent=0); 54 | ~VPlayer(); 55 | static QMutex time; 56 | QList getTracks(int type); 57 | 58 | private: 59 | int state; 60 | QActionGroup *tracks[3]; 61 | libvlc_instance_t *vlc; 62 | libvlc_media_player_t *mp; 63 | 64 | void init(); 65 | void wait(); 66 | void free(); 67 | 68 | public slots: 69 | void play(); 70 | void stop(bool manually=true); 71 | int getState(){return state;} 72 | 73 | void setTime(qint64 _time); 74 | qint64 getTime(); 75 | 76 | void setMedia(QString _file,bool manually=true); 77 | QString getMedia(); 78 | 79 | qint64 getDuration(); 80 | void addSubtitle(QString _file); 81 | 82 | void setVolume(int _volume); 83 | int getVolume(); 84 | 85 | void event(int type); 86 | 87 | }; 88 | 89 | QMutex VPlayer::time; 90 | 91 | namespace 92 | { 93 | class Buffer 94 | { 95 | public: 96 | explicit Buffer(const QList &planeSize) 97 | { 98 | size=0; 99 | QList planeLength; 100 | for(const QSize &s:planeSize){ 101 | int length=s.width()*s.height(); 102 | planeLength.append(length); 103 | size+=length; 104 | } 105 | quint8 *alloc=new quint8[size]; 106 | for(int length:planeLength){ 107 | data.append(alloc); 108 | alloc+=length; 109 | } 110 | } 111 | 112 | ~Buffer() 113 | { 114 | delete []data[0]; 115 | } 116 | 117 | void flush() 118 | { 119 | memcpy(Render::instance()->getBuffer()[0],data[0],size); 120 | Render::instance()->releaseBuffer(); 121 | } 122 | 123 | QList getBuffer() 124 | { 125 | return data; 126 | } 127 | 128 | private: 129 | QList data; 130 | int size; 131 | }; 132 | 133 | unsigned fmt(void **opaque,char *chroma, 134 | unsigned *width,unsigned *height, 135 | unsigned *p,unsigned *l) 136 | { 137 | QString c(chroma); 138 | QList b; 139 | Render::instance()->setBuffer(c,QSize(*width,*height),&b); 140 | if (b.isEmpty()){ 141 | return 0; 142 | } 143 | memcpy(chroma,c.toUtf8(),4); 144 | for(int i=0;igetBuffer()){ 156 | planes[i++]=(void *)p; 157 | } 158 | return nullptr; 159 | } 160 | 161 | void dsp(void *opaque,void *) 162 | { 163 | ((Buffer *)opaque)->flush(); 164 | emit APlayer::instance()->decode(); 165 | } 166 | 167 | void clr(void *opaque) 168 | { 169 | delete (Buffer *)opaque; 170 | } 171 | 172 | void sta(const libvlc_event_t *,void *) 173 | { 174 | QMetaObject::invokeMethod(APlayer::instance(),"event",Q_ARG(int,VPlayer::Init)); 175 | } 176 | 177 | void mid(const libvlc_event_t *,void *) 178 | { 179 | if (VPlayer::time.tryLock()) { 180 | QMetaObject::invokeMethod(APlayer::instance(), 181 | "timeChanged", 182 | Q_ARG(qint64,APlayer::instance()->getTime())); 183 | VPlayer::time.unlock(); 184 | } 185 | } 186 | 187 | void hal(const libvlc_event_t *,void *) 188 | { 189 | QMetaObject::invokeMethod(APlayer::instance(),"event",Q_ARG(int,VPlayer::Wait)); 190 | } 191 | 192 | void end(const libvlc_event_t *,void *) 193 | { 194 | QMetaObject::invokeMethod(APlayer::instance(),"event",Q_ARG(int,VPlayer::Free)); 195 | } 196 | 197 | void err(const libvlc_event_t *,void *) 198 | { 199 | QMetaObject::invokeMethod(APlayer::instance(),"event",Q_ARG(int,VPlayer::Fail)); 200 | } 201 | } 202 | 203 | VPlayer::VPlayer(QObject *parent): 204 | APlayer(parent) 205 | { 206 | ins=this; 207 | setObjectName("VPlayer"); 208 | QList args; 209 | for(QJsonValue arg:Config::getValue("/Playing/Arguments")){ 210 | args.append(arg.toString().toUtf8()); 211 | } 212 | const char **argv=args.isEmpty()?nullptr:new const char *[args.size()]; 213 | for(int i=0;isetExclusive(true); 225 | } 226 | } 227 | 228 | VPlayer::~VPlayer() 229 | { 230 | if(mp){ 231 | libvlc_media_player_release(mp); 232 | } 233 | libvlc_release(vlc); 234 | } 235 | 236 | QList VPlayer::getTracks(int type) 237 | { 238 | QList track; 239 | if(type&Utils::Video){ 240 | track+=tracks[0]->actions(); 241 | } 242 | if(type&Utils::Audio){ 243 | track+=tracks[1]->actions(); 244 | } 245 | if(type&Utils::Subtitle){ 246 | track+=tracks[2]->actions(); 247 | } 248 | return track; 249 | } 250 | 251 | namespace 252 | { 253 | void copyTracks(libvlc_track_description_t *head,QActionGroup *group) 254 | { 255 | qDeleteAll(group->actions()); 256 | libvlc_track_description_t *iter=head; 257 | while(iter){ 258 | QAction *action=group->addAction(iter->psz_name); 259 | action->setCheckable(true); 260 | action->setData(iter->i_id); 261 | iter=iter->p_next; 262 | } 263 | libvlc_track_description_list_release(head); 264 | } 265 | } 266 | 267 | void VPlayer::init() 268 | { 269 | if(mp){ 270 | auto *connection=new QMetaObject::Connection; 271 | *connection=connect(this,&VPlayer::timeChanged,this,[=](){ 272 | int last=state; 273 | emit stateChanged(state=Play); 274 | disconnect(*connection); 275 | delete connection; 276 | switch(last){ 277 | case Stop: 278 | { 279 | bool music=true; 280 | libvlc_media_track_t **info; 281 | int n=libvlc_media_tracks_get(libvlc_media_player_get_media(mp),&info); 282 | for(int i=0;ii_type==libvlc_track_video){ 284 | libvlc_video_track_t *v=info[i]->video; 285 | double r=v->i_sar_den==0?1:(double)v->i_sar_num/v->i_sar_den; 286 | Render::instance()->setPixelAspectRatio(r); 287 | music=false; 288 | break; 289 | } 290 | } 291 | libvlc_media_tracks_release(info,n); 292 | Render::instance()->setMusic(music); 293 | if(!Config::getValue("/Playing/Subtitle",true)){ 294 | libvlc_video_set_spu(mp,-1); 295 | } 296 | copyTracks(libvlc_video_get_spu_description(mp),tracks[2]); 297 | copyTracks(libvlc_video_get_track_description(mp),tracks[0]); 298 | copyTracks(libvlc_audio_get_track_description(mp),tracks[1]); 299 | for(QAction *i:tracks[0]->actions()){ 300 | int t=i->data().toInt(); 301 | connect(i,&QAction::triggered,[=](){ 302 | libvlc_video_set_track(mp,t); 303 | Render::instance()->setMusic(t==-1); 304 | }); 305 | i->setChecked(t==libvlc_video_get_track(mp)); 306 | } 307 | for(QAction *i:tracks[1]->actions()){ 308 | connect(i,&QAction::triggered,[=](){libvlc_audio_set_track(mp,i->data().toInt());}); 309 | i->setChecked(i->data().toInt()==libvlc_audio_get_track(mp)); 310 | } 311 | for(QAction *i:tracks[2]->actions()){ 312 | connect(i,&QAction::triggered,[=](){libvlc_video_set_spu(mp,i->data().toInt());}); 313 | i->setChecked(i->data().toInt()==libvlc_video_get_spu(mp)); 314 | } 315 | emit begin(); 316 | break; 317 | } 318 | case Loop: 319 | { 320 | for(auto *g:tracks){ 321 | for(QAction *i:g->actions()){ 322 | if(i->isChecked()){ 323 | i->trigger(); 324 | } 325 | } 326 | } 327 | break; 328 | } 329 | default: 330 | return; 331 | } 332 | setVolume(Config::getValue("/Playing/Volume",50)); 333 | }); 334 | } 335 | } 336 | 337 | void VPlayer::wait() 338 | { 339 | emit stateChanged(state=Pause); 340 | } 341 | 342 | void VPlayer::free() 343 | { 344 | if(state==Play&&Config::getValue("/Playing/Loop",false)){ 345 | libvlc_media_player_stop(mp); 346 | emit stateChanged(state=Loop); 347 | libvlc_media_player_play(mp); 348 | emit jumped(0); 349 | } 350 | else{ 351 | stop(false); 352 | } 353 | } 354 | 355 | void VPlayer::play() 356 | { 357 | if(mp){ 358 | if(state==Stop){ 359 | libvlc_video_set_format_callbacks(mp,fmt,clr); 360 | libvlc_video_set_callbacks(mp,lck,nullptr,dsp,nullptr); 361 | libvlc_media_player_play(mp); 362 | } 363 | else{ 364 | libvlc_media_player_pause(mp); 365 | } 366 | } 367 | } 368 | 369 | void VPlayer::stop(bool manually) 370 | { 371 | if(mp&&state!=Stop){ 372 | libvlc_media_player_stop(mp); 373 | emit stateChanged(state=Stop); 374 | for(auto g:tracks){ 375 | qDeleteAll(g->actions()); 376 | } 377 | emit reach(manually); 378 | } 379 | } 380 | 381 | void VPlayer::setTime(qint64 _time) 382 | { 383 | if(mp&&state!=Stop){ 384 | if(getDuration()==_time){ 385 | if(Config::getValue("/Playing/Loop",false)){ 386 | setTime(0); 387 | } 388 | else{ 389 | stop(); 390 | } 391 | } 392 | else{ 393 | time.lock(); 394 | qApp->processEvents(); 395 | emit jumped(_time); 396 | libvlc_media_player_set_time(mp,qBound(0,_time,getDuration())); 397 | time.unlock(); 398 | } 399 | } 400 | } 401 | 402 | qint64 VPlayer::getTime() 403 | { 404 | return state==Stop?-1:libvlc_media_player_get_time(mp); 405 | } 406 | 407 | void VPlayer::setMedia(QString _file,bool manually) 408 | { 409 | stop(manually); 410 | libvlc_media_t *m=libvlc_media_new_path(vlc,QDir::toNativeSeparators(_file).toUtf8()); 411 | if(!m){ 412 | return; 413 | } 414 | if(mp){ 415 | libvlc_media_player_release(mp); 416 | } 417 | mp=libvlc_media_player_new_from_media(m); 418 | libvlc_media_release(m); 419 | if(!mp){ 420 | return; 421 | } 422 | libvlc_event_manager_t *man=libvlc_media_player_event_manager(mp); 423 | libvlc_event_attach(man, 424 | libvlc_MediaPlayerPlaying, 425 | sta,nullptr); 426 | libvlc_event_attach(man, 427 | libvlc_MediaPlayerTimeChanged, 428 | mid,nullptr); 429 | libvlc_event_attach(man, 430 | libvlc_MediaPlayerPaused, 431 | hal,nullptr); 432 | libvlc_event_attach(man, 433 | libvlc_MediaPlayerEndReached, 434 | end,nullptr); 435 | libvlc_event_attach(man, 436 | libvlc_MediaPlayerEncounteredError, 437 | err,nullptr); 438 | emit mediaChanged(getMedia()); 439 | if (Config::getValue("/Playing/Immediate",false)){ 440 | play(); 441 | } 442 | } 443 | 444 | QString VPlayer::getMedia() 445 | { 446 | if(mp){ 447 | libvlc_media_t *m=libvlc_media_player_get_media(mp); 448 | char *s=libvlc_media_get_mrl(m); 449 | QUrl u(s); 450 | libvlc_free(s); 451 | libvlc_media_release(m); 452 | return u.isLocalFile()?u.toLocalFile():u.url(); 453 | } 454 | return QString(); 455 | } 456 | 457 | qint64 VPlayer::getDuration() 458 | { 459 | return mp?libvlc_media_player_get_length(mp):-1; 460 | } 461 | 462 | void VPlayer::addSubtitle(QString _file) 463 | { 464 | if(mp){ 465 | if(tracks[2]->actions().isEmpty()){ 466 | QAction *action=tracks[2]->addAction(APlayer::tr("Disable")); 467 | action->setCheckable(true); 468 | connect(action,&QAction::triggered,[this](){ 469 | libvlc_video_set_spu(mp,-1); 470 | }); 471 | } 472 | QAction *outside=new QAction(tracks[2]); 473 | QFileInfo info(_file); 474 | outside->setCheckable(true); 475 | outside->setText(qApp->fontMetrics().elidedText(info.fileName(),Qt::ElideMiddle,200)); 476 | outside->setData(QDir::toNativeSeparators(info.absoluteFilePath())); 477 | connect(outside,&QAction::triggered,[=](){ 478 | libvlc_video_set_subtitle_file(mp,outside->data().toString().toUtf8()); 479 | }); 480 | outside->trigger(); 481 | } 482 | } 483 | 484 | void VPlayer::setVolume(int _volume) 485 | { 486 | _volume=qBound(0,_volume,100); 487 | Config::setValue("/Playing/Volume",_volume); 488 | if(mp){ 489 | libvlc_audio_set_volume(mp,_volume); 490 | } 491 | emit volumeChanged(_volume); 492 | } 493 | 494 | int VPlayer::getVolume() 495 | { 496 | return mp?libvlc_audio_get_volume(mp):0; 497 | } 498 | 499 | void VPlayer::event(int type) 500 | { 501 | switch(type){ 502 | case Init: 503 | init(); 504 | break; 505 | case Wait: 506 | wait(); 507 | break; 508 | case Free: 509 | free(); 510 | break; 511 | case Fail: 512 | emit errorOccurred(UnknownError); 513 | break; 514 | } 515 | } 516 | #endif 517 | 518 | #ifdef BACKEND_QMM 519 | #include 520 | 521 | namespace 522 | { 523 | QString getFormat(QVideoFrame::PixelFormat format) 524 | { 525 | switch(format){ 526 | case QVideoFrame::Format_YUV420P: 527 | return "I420"; 528 | case QVideoFrame::Format_YV12: 529 | return "YV12"; 530 | case QVideoFrame::Format_NV12: 531 | return "NV12"; 532 | case QVideoFrame::Format_NV21: 533 | return "NV21"; 534 | default: 535 | return QString(); 536 | } 537 | } 538 | 539 | class RenderAdapter:public QAbstractVideoSurface 540 | { 541 | public: 542 | RenderAdapter(QObject *parent=0): 543 | QAbstractVideoSurface(parent) 544 | { 545 | } 546 | 547 | bool start(const QVideoSurfaceFormat &format) 548 | { 549 | QString chroma=getFormat(format.pixelFormat()); 550 | if (chroma.isEmpty()) 551 | return false; 552 | QString buffer(chroma); 553 | Render::instance()->setBuffer(buffer,format.frameSize()); 554 | if (buffer!=chroma) 555 | return false; 556 | QSize pixel(format.pixelAspectRatio()); 557 | Render::instance()->setPixelAspectRatio(pixel.width()/(double)pixel.height()); 558 | return true; 559 | } 560 | 561 | bool present(const QVideoFrame &frame) 562 | { 563 | QVideoFrame f(frame); 564 | if (f.map(QAbstractVideoBuffer::ReadOnly)){ 565 | int len=f.mappedBytes(); 566 | const quint8 *dat=f.bits(); 567 | QList buffer=Render::instance()->getBuffer(); 568 | memcpy(buffer[0],dat,len); 569 | Render::instance()->releaseBuffer(); 570 | f.unmap(); 571 | } 572 | else{ 573 | return false; 574 | } 575 | emit APlayer::instance()->decode(); 576 | return true; 577 | } 578 | 579 | QList supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const 580 | { 581 | QList f; 582 | if (QAbstractVideoBuffer::NoHandle==handleType){ 583 | f<setNotifyInterval(300); 629 | mp->setVideoOutput(new RenderAdapter(mp)); 630 | m.unlock(); 631 | w.wakeAll(); 632 | exec(); 633 | delete mp; 634 | } 635 | }; 636 | } 637 | 638 | class QPlayer:public APlayer 639 | { 640 | public: 641 | explicit QPlayer(QObject *parent=0); 642 | QList getTracks(int type); 643 | 644 | private: 645 | QMediaPlayer *mp; 646 | int state; 647 | bool manuallyStopped; 648 | bool waitingForBegin; 649 | bool skipTimeChanged; 650 | 651 | public slots: 652 | void play(); 653 | void stop(bool manually=true); 654 | int getState(){return state;} 655 | 656 | void setTime(qint64 _time); 657 | qint64 getTime(); 658 | 659 | void setMedia(QString _file,bool manually=true); 660 | QString getMedia(); 661 | 662 | qint64 getDuration(); 663 | void addSubtitle(QString _file); 664 | 665 | void setVolume(int _volume); 666 | int getVolume(); 667 | 668 | void event(int type); 669 | 670 | }; 671 | 672 | QPlayer::QPlayer(QObject *parent): 673 | APlayer(parent) 674 | { 675 | ins=this; 676 | setObjectName("QPlayer"); 677 | state=Stop; 678 | manuallyStopped=false; 679 | waitingForBegin=false; 680 | skipTimeChanged=false; 681 | 682 | mp=(new QPlayerThread(this))->getMediaPlayer(); 683 | mp->setVolume(Config::getValue("/Playing/Volume",50)); 684 | 685 | connect(mp,&QMediaPlayer::error,this,[this](int error){ 686 | if ((State)mp->state()==Play){ 687 | manuallyStopped=true; 688 | } 689 | emit errorOccurred(error); 690 | }); 691 | 692 | connect(mp,&QMediaPlayer::volumeChanged,this,&QPlayer::volumeChanged); 693 | 694 | connect(mp,&QMediaPlayer::stateChanged,this,[this](int _state){ 695 | if(_state==Stop){ 696 | if(!manuallyStopped&&Config::getValue("/Playing/Loop",false)){ 697 | stateChanged(state=Loop); 698 | play(); 699 | emit jumped(0); 700 | } 701 | else{ 702 | stateChanged(state=Stop); 703 | emit reach(manuallyStopped); 704 | } 705 | manuallyStopped=false; 706 | } 707 | else{ 708 | manuallyStopped=false; 709 | if(_state==Play&&state==Stop){ 710 | waitingForBegin=true; 711 | } 712 | else{ 713 | emit stateChanged(state=_state); 714 | } 715 | } 716 | }); 717 | 718 | connect(mp,&QMediaPlayer::positionChanged,this,[this](qint64 time){ 719 | if (waitingForBegin&&time>0){ 720 | waitingForBegin=false; 721 | Render::instance()->setMusic(!mp->isVideoAvailable()); 722 | emit stateChanged(state=Play); 723 | emit begin(); 724 | } 725 | if(!skipTimeChanged){ 726 | emit timeChanged(time); 727 | } 728 | else{ 729 | skipTimeChanged=false; 730 | } 731 | }); 732 | 733 | connect(mp,&QMediaPlayer::mediaChanged,this,[this](){ 734 | emit mediaChanged(getMedia()); 735 | }); 736 | } 737 | 738 | QList QPlayer::getTracks(int) 739 | { 740 | return QList(); 741 | } 742 | 743 | void QPlayer::play() 744 | { 745 | if(getState()==Play){ 746 | List::instance()->saveList(); 747 | Config::save(); 748 | } 749 | QMetaObject::invokeMethod(mp,getState()==Play?"pause":"play",Qt::BlockingQueuedConnection); 750 | } 751 | 752 | void QPlayer::stop(bool manually) 753 | { 754 | manuallyStopped=manually; 755 | QMetaObject::invokeMethod(mp,"stop",Qt::BlockingQueuedConnection); 756 | } 757 | 758 | void QPlayer::setTime(qint64 _time) 759 | { 760 | QMetaObject::invokeMethod(mp,"setPosition",Qt::BlockingQueuedConnection,Q_ARG(qint64,_time)); 761 | skipTimeChanged=true; 762 | emit jumped(_time); 763 | } 764 | 765 | qint64 QPlayer::getTime() 766 | { 767 | return mp->position(); 768 | } 769 | 770 | void QPlayer::setMedia(QString _file,bool manually) 771 | { 772 | stop(manually); 773 | QMetaObject::invokeMethod(mp,"setMedia",Qt::BlockingQueuedConnection,Q_ARG(QMediaContent,QUrl::fromLocalFile(_file))); 774 | if(Config::getValue("/Playing/Immediate",false)){ 775 | play(); 776 | } 777 | } 778 | 779 | QString QPlayer::getMedia() 780 | { 781 | QUrl u=mp->media().canonicalUrl(); 782 | return u.isLocalFile()?u.toLocalFile():QString(); 783 | } 784 | 785 | qint64 QPlayer::getDuration() 786 | { 787 | return mp->duration(); 788 | } 789 | 790 | void QPlayer::addSubtitle(QString) 791 | { 792 | } 793 | 794 | void QPlayer::setVolume(int _volume) 795 | { 796 | _volume=qBound(0,_volume,100); 797 | mp->setVolume(_volume); 798 | Config::setValue("/Playing/Volume",_volume); 799 | } 800 | 801 | int QPlayer::getVolume() 802 | { 803 | return mp->volume(); 804 | } 805 | 806 | void QPlayer::event(int) 807 | { 808 | } 809 | #endif 810 | 811 | #ifdef BACKEND_NIL 812 | class NPlayer:public APlayer 813 | { 814 | public: 815 | explicit NPlayer(QObject *parent=0); 816 | QList getTracks(int type); 817 | 818 | private: 819 | qint64 start; 820 | int state; 821 | 822 | void timerEvent(QTimerEvent * e); 823 | 824 | public slots: 825 | void play(); 826 | void stop(bool manually=true); 827 | int getState(){return state;} 828 | 829 | void setTime(qint64 _time); 830 | qint64 getTime(); 831 | 832 | void setMedia(QString _file,bool manually=true); 833 | QString getMedia(); 834 | 835 | qint64 getDuration(); 836 | void addSubtitle(QString _file); 837 | 838 | void setVolume(int _volume); 839 | int getVolume(); 840 | 841 | void event(int type); 842 | 843 | }; 844 | 845 | NPlayer::NPlayer(QObject *parent): 846 | APlayer(parent) 847 | { 848 | ins=this; 849 | setObjectName("NPlayer"); 850 | 851 | state=Stop; 852 | startTimer(100); 853 | } 854 | 855 | void NPlayer::timerEvent(QTimerEvent *) 856 | { 857 | if(state==Play){ 858 | emit timeChanged(getTime()); 859 | } 860 | } 861 | 862 | QList NPlayer::getTracks(int) 863 | { 864 | return QList(); 865 | } 866 | 867 | void NPlayer::play() 868 | { 869 | if(state!=Stop){ 870 | return; 871 | } 872 | Render::instance()->setMusic(true); 873 | emit stateChanged(state=Play); 874 | emit begin(); 875 | start=QDateTime::currentMSecsSinceEpoch(); 876 | } 877 | 878 | void NPlayer::stop(bool) 879 | { 880 | emit reach(true); 881 | emit stateChanged(state=Stop); 882 | } 883 | 884 | void NPlayer::setTime(qint64) 885 | { 886 | } 887 | 888 | qint64 NPlayer::getTime() 889 | { 890 | return state==Stop?-1:(QDateTime::currentMSecsSinceEpoch()-start); 891 | } 892 | 893 | void NPlayer::setMedia(QString,bool) 894 | { 895 | } 896 | 897 | QString NPlayer::getMedia() 898 | { 899 | return QString(); 900 | } 901 | 902 | qint64 NPlayer::getDuration() 903 | { 904 | return -1; 905 | } 906 | 907 | void NPlayer::addSubtitle(QString) 908 | { 909 | } 910 | 911 | void NPlayer::setVolume(int) 912 | { 913 | } 914 | 915 | int NPlayer::getVolume() 916 | { 917 | return 0; 918 | } 919 | 920 | void NPlayer::event(int) 921 | { 922 | } 923 | #endif 924 | 925 | APlayer *APlayer::instance() 926 | { 927 | if (ins){ 928 | return ins; 929 | } 930 | QString d; 931 | QStringList l=Utils::getDecodeModules(); 932 | switch(l.size()){ 933 | case 0: 934 | break; 935 | case 1: 936 | d=l[0]; 937 | break; 938 | default: 939 | d=Config::getValue("/Performance/Decode",l[0]); 940 | d=l.contains(d)?d:l[0]; 941 | break; 942 | } 943 | #ifdef BACKEND_VLC 944 | if (d=="VLC"){ 945 | return new VPlayer(qApp); 946 | } 947 | #endif 948 | #ifdef BACKEND_QMM 949 | if (d=="QMM"){ 950 | return new QPlayer(qApp); 951 | } 952 | #endif 953 | #ifdef BACKEND_NIL 954 | if (d=="NIL"){ 955 | return new NPlayer(qApp); 956 | } 957 | #endif 958 | return 0; 959 | } 960 | --------------------------------------------------------------------------------