├── Global.py ├── Mycomponent ├── Card.qml ├── CollectView.qml ├── DownLoad.qml ├── HistoryView.qml ├── HomeView.qml ├── IconButton.qml ├── InputCom.qml ├── Link.qml ├── MsgTip.qml ├── MyButton.qml ├── NetDown.qml ├── PreView.qml ├── RadiuImg.qml ├── SettingView.qml ├── SureButton.qml ├── TagButton.qml ├── TagView.qml ├── TagdescView.qml ├── TaskView.qml ├── TaskView.qml.HQWFWg ├── TaskView.qml.autosave.OLIAbr ├── TransForm.qml ├── VideoView.qml ├── mainwindow.ui ├── qmldir ├── transfer.qml └── util.qml ├── README.md ├── chromedriver.exe ├── config.toml ├── db.py ├── db.sqlite3 ├── en_US.toml ├── ffmpeg.exe ├── icon ├── Edit.svg ├── add.svg ├── addimg.svg ├── back.svg ├── bg-backward.svg ├── bg-forward.svg ├── bg.jpg ├── box2-heart-fill.svg ├── card-image.svg ├── change.svg ├── close.svg ├── delete.svg ├── detail.svg ├── download.svg ├── favicon.ico ├── github.svg ├── heart-fill.svg ├── heart.svg ├── history.svg ├── home.svg ├── image.svg ├── img.png ├── import.svg ├── larrow.svg ├── load.svg ├── loading.gif ├── love.svg ├── mute.svg ├── next.svg ├── notfound.png ├── play.svg ├── previous.svg ├── qes.svg ├── rarrow.svg ├── search.svg ├── setting.svg ├── skip.svg ├── stop.svg ├── sure.svg ├── tag.svg ├── tagadd.svg ├── telegram.svg ├── type.svg ├── unlove.svg ├── unsure.svg ├── video.svg ├── vol.svg ├── 同步.svg ├── 图片预览.svg ├── 播放.svg ├── 时间.svg ├── 时间1.svg └── 网站.svg ├── img ├── H-box.gif ├── collect.png ├── hbox.png ├── history.jpg ├── home.png ├── img.txt ├── local.jpg ├── net.jpg ├── netdown.jpg ├── rule.jpg ├── set.jpg ├── setting.jpg ├── tag.jpg └── video.jpg ├── logfile.log ├── main.py ├── main.qml ├── rule.toml ├── spider.py ├── user.md ├── util.py └── zh_CN.toml /Global.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | class Global: 3 | now_page = 1 4 | collect_page = 0 5 | search_page = 1 6 | tag_page = 1 7 | search = None 8 | curtagid = 0 9 | process = {"total":0,"now":0,"end":True} 10 | download = {"sync":False} 11 | genimg = {} 12 | hentai = {} 13 | ruleset = {} 14 | headless = True 15 | sock = None -------------------------------------------------------------------------------- /Mycomponent/Card.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import Qt5Compat.GraphicalEffects 4 | Rectangle { 5 | id: card 6 | // width: 160 7 | // height: 200 8 | property string imgpath: "" 9 | property string titletext: "" 10 | property string videopath: "" 11 | property string icopath: "" 12 | property int vid: 0 13 | property int col: 0 14 | property bool edit: false 15 | color:"transparent" 16 | // layer.effect: DropShadow { 17 | // transparentBorder: true 18 | // } 19 | onEditChanged: { 20 | if(edit){ 21 | love.source="../icon/Edit.svg" 22 | }else{ 23 | love.source="../icon/播放.svg" 24 | 25 | } 26 | } 27 | Column{ 28 | Image{ 29 | id: image 30 | width: card.width // 160 31 | height: card.height-30 // 185 32 | source: imgpath 33 | asynchronous: true 34 | // fillMode: Image.PreserveAspectFit 35 | cache:false 36 | layer.enabled: true 37 | layer.effect: OpacityMask { 38 | maskSource: Rectangle { 39 | width: card.width 40 | height: card.height-30 41 | radius: 10 42 | } 43 | } 44 | onStatusChanged: { 45 | if(image.status === Image.Error ){ 46 | source = "../icon/notfound.png" 47 | } 48 | } 49 | Image{ 50 | id:love 51 | visible: false 52 | anchors.centerIn: parent 53 | source:"../icon/播放.svg" 54 | width:50 55 | height: 50 56 | } 57 | MouseArea{ 58 | id:mousea 59 | anchors.fill: parent 60 | hoverEnabled: true 61 | onClicked:{ 62 | if (!edit){ 63 | bridge.history(vid) 64 | videoviews.videopath = videopath 65 | videoviews.vid = id 66 | videoviews.main = 1 67 | videoviews.collect = col 68 | videoviews.show() 69 | }else{ 70 | editcard.openme(vid,titletext,imgpath) 71 | } 72 | } 73 | onEntered:{ 74 | love.visible = true 75 | } 76 | onExited:{ 77 | love.visible = false 78 | } 79 | } 80 | } 81 | Rectangle{ 82 | width: card.width 83 | height: 30 84 | clip: true 85 | radius: 4 86 | // color:"yellow" 87 | Row{ 88 | spacing:5 89 | anchors.verticalCenter: parent.verticalCenter 90 | Image{ 91 | width: 18 92 | height:18 93 | source: "../img/"+icopath 94 | layer.enabled: true 95 | layer.effect: OpacityMask { 96 | maskSource: Rectangle { 97 | width: 18 98 | height: 18 99 | radius: 4 100 | } 101 | } 102 | } 103 | Text{ 104 | text: titletext.trim().substring(0,30) 105 | } 106 | } 107 | ToolTip{ 108 | background: Rectangle{ 109 | radius: 5 110 | color:"white" 111 | } 112 | id: tt 113 | text: titletext 114 | } 115 | MouseArea{ 116 | anchors.fill: parent 117 | hoverEnabled: true 118 | onEntered: tt.visible = true 119 | onExited:tt.visible = false 120 | } 121 | } 122 | 123 | } 124 | 125 | } 126 | 127 | //Rectangle { 128 | // id: mask 129 | // radius: 10 130 | // width: card.width // 160 131 | // height: card.height-30 // 185 132 | // smooth: true 133 | // visible: false 134 | // } 135 | 136 | // OpacityMask { 137 | // anchors.fill: image 138 | // source: image 139 | // maskSource: mask 140 | // } 141 | -------------------------------------------------------------------------------- /Mycomponent/CollectView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import Qt5Compat.GraphicalEffects 4 | Rectangle { 5 | id: love 6 | ListModel { 7 | id: data 8 | } 9 | function pop(index){ 10 | data.remove(index) 11 | } 12 | Component.onCompleted: { 13 | data.clear() 14 | var listdata = bridge.love(0) 15 | if (listdata.length===0){ 16 | nolove.visible=true 17 | }else{ 18 | data.append(bridge.love(0)) 19 | } 20 | 21 | } 22 | 23 | Text{ 24 | id:nolove 25 | anchors.centerIn: love 26 | text:config.collect.tip 27 | visible: false 28 | } 29 | ListView{ 30 | id:list 31 | anchors.fill: parent 32 | model: data 33 | spacing: 10 34 | delegate: rollcard 35 | onMovementEnded:{ 36 | if (list.contentY===data.count*200-list.height+(data.count-1)*10){ 37 | data.append(bridge.love(1)) 38 | } 39 | } 40 | Component { 41 | id: rollcard 42 | Rectangle{ 43 | id: card 44 | width: love.width 45 | height: 200 46 | // color:"gray" 47 | clip: true 48 | MouseArea{ 49 | anchors.fill: parent 50 | cursorShape: Qt.PointingHandCursor 51 | onClicked:{ 52 | bridge.history(id) 53 | videoviews.videopath = video 54 | videoviews.vid = id 55 | videoviews.collect=1 56 | videoviews.show() 57 | } 58 | } 59 | Image{ 60 | id:bg 61 | height:300 62 | width: 520 63 | source:"../img/"+colpic 64 | asynchronous: true 65 | cache:false 66 | x:card.width-bg.width 67 | LinearGradient { 68 | id: mask 69 | anchors.fill: bg 70 | start: Qt.point(300, 0) 71 | end: Qt.point(0, 0) 72 | gradient: Gradient { 73 | GradientStop { position: 0; color: "transparent"} 74 | GradientStop { position: 0.9; color: "white" } 75 | } 76 | } 77 | } 78 | Row{ 79 | anchors.verticalCenter: card.verticalCenter 80 | spacing:20 81 | Rectangle{ 82 | width: 60 83 | height: 100 84 | color:"transparent" 85 | } 86 | 87 | Rectangle{ 88 | id:aaa 89 | width: 128 90 | height: 160 91 | color:"transparent" 92 | Image{ 93 | id:images 94 | width: 128 95 | height: 170 96 | source: "file:///"+datadir+"/"+feng 97 | // visible: false 98 | layer.enabled: true 99 | layer.effect: OpacityMask { 100 | maskSource: Rectangle { 101 | width: 128 102 | height:170 103 | radius: 10 104 | } 105 | } 106 | } 107 | // Rectangle{ 108 | // id: rmask 109 | // anchors.fill: aaa 110 | // radius: 10 111 | // visible: false 112 | // } 113 | // OpacityMask { 114 | // anchors.fill: images 115 | // source: images 116 | // maskSource: rmask 117 | // visible: true 118 | // } 119 | } 120 | 121 | Rectangle{ 122 | width: 160 123 | height: 160 124 | color:"transparent" 125 | Column{ 126 | spacing: 20 127 | 128 | Text{ 129 | text:config.collect.title+title 130 | } 131 | Text{ 132 | text:config.collect.addtime+collecttime 133 | } 134 | Text{ 135 | text:config.collect.views+views 136 | } 137 | IconButton{ 138 | icon{ 139 | source:"../icon/unlove.svg" 140 | } 141 | Popup { 142 | id:popup 143 | x:60 144 | width: 100 145 | height: 30 146 | closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent 147 | Row{ 148 | anchors.centerIn: parent 149 | spacing:10 150 | SureButton{ 151 | c:1 152 | onClicked: { 153 | bridge.collect(id,0,0,"") 154 | pop(index) 155 | } 156 | } 157 | SureButton{ 158 | c:0 159 | onClicked: {popup.close()} 160 | } 161 | } 162 | } 163 | onClicked: { 164 | popup.open() 165 | } 166 | 167 | } 168 | } 169 | } 170 | } 171 | 172 | } 173 | } 174 | } 175 | 176 | 177 | } 178 | -------------------------------------------------------------------------------- /Mycomponent/DownLoad.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import QtQuick.Dialogs 5 | 6 | Rectangle{ 7 | FileDialog { 8 | id: fileDialog 9 | nameFilters: ["Image (*.mp4)"] 10 | onAccepted: { 11 | var path = selectedFile 12 | videourl.text = path 13 | let urltext = path.split("\n")[0] 14 | let l =urltext.split("/") 15 | let urlpath = l[l.length-1].slice(0,-4) 16 | titleedit.text = urlpath 17 | visible = false 18 | detail.visible = true 19 | } 20 | } 21 | GridLayout{ 22 | id:detail 23 | anchors.centerIn: parent 24 | columns: 2 25 | visible:false 26 | Label{text:"文件标题"} 27 | TextField{id:titleedit} 28 | Label{ 29 | id:prefeng 30 | text:"封面" 31 | } 32 | IconButton{ 33 | icon{source:"../icon/qes.svg"} 34 | } 35 | Label{text:"视频路径"} 36 | Text{ 37 | width:50 38 | id:videourl 39 | wrapMode: Text.Wrap 40 | } 41 | Button{ 42 | text:"确认" 43 | onClicked: { 44 | bridge.localimport(titleedit.text,prefeng.text,videourl.text) 45 | drop.visible = true 46 | detail.visible = false 47 | msgtip.msg="导入成功" 48 | msgtip.open() 49 | } 50 | } 51 | Button{ 52 | text:"取消" 53 | onClicked: { 54 | drop.visible = true 55 | detail.visible = false 56 | } 57 | } 58 | } 59 | 60 | DropArea{ 61 | id:drop 62 | anchors.fill: parent 63 | Text{ 64 | text: config.imp.tip 65 | anchors.horizontalCenter: parent.horizontalCenter 66 | anchors.verticalCenterOffset: -90 67 | anchors.verticalCenter: parent.verticalCenter 68 | } 69 | onDropped: (drop)=>{ 70 | var path = drop.urls[0] 71 | videourl.text = path 72 | let urltext = drop.text.split("\n")[0] 73 | let l =urltext.split("/") 74 | let urlpath = l[l.length-1].slice(0,-4) 75 | titleedit.text = urlpath 76 | visible = false 77 | detail.visible = true 78 | } 79 | Rectangle{ 80 | width:90 81 | height:120 82 | border.color: "gray" 83 | border.width: 3 84 | radius:10 85 | anchors.centerIn: parent 86 | Image{ 87 | id:preimg 88 | sourceSize.width: parent.width 89 | sourceSize.height: parent.height 90 | source: "../icon/addimg.svg" 91 | } 92 | MouseArea{ 93 | anchors.fill: parent 94 | onClicked: { 95 | fileDialog.open() 96 | } 97 | } 98 | } 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /Mycomponent/HistoryView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import Qt5Compat.GraphicalEffects 4 | Rectangle { 5 | id:history 6 | color: "white" 7 | function setHis(times = 0){ 8 | if(typeof times ==='number'){ 9 | if(times <= 0){ 10 | return '00:00:00'; 11 | }else{ 12 | let hh = parseInt(times / 3600); //小时 13 | let shh = times - hh * 3600; 14 | let ii = parseInt(shh/ 60); 15 | let ss = parseInt(shh - ii * 60); 16 | return (hh < 10 ? '0'+hh : hh) + ':' + (ii < 10 ? '0'+ii : ii) +':'+(ss < 10 ? '0'+ss : ss); 17 | } 18 | }else{ 19 | return '00:00:00'; 20 | } 21 | } 22 | ListModel { 23 | id: data 24 | } 25 | Text{ 26 | id:noting 27 | visible: false 28 | anchors.centerIn: parent 29 | text:config.history.tip 30 | } 31 | Component.onCompleted: { 32 | data.append(bridge.getallhistory()) 33 | if (data.count===0){ 34 | noting.visible=true 35 | } 36 | } 37 | ListView{ 38 | anchors.fill: parent 39 | model:data 40 | delegate: comp 41 | spacing:9 42 | anchors.margins: 10 43 | Component{ 44 | id: comp 45 | Rectangle{ 46 | id:his 47 | width: history.width-20 48 | height: 50 49 | // color: "gray" 50 | radius: 10 51 | layer.enabled: true 52 | layer.effect: DropShadow { 53 | transparentBorder: true 54 | // horizontalOffset: 2 55 | // verticalOffset: 2 56 | } 57 | Row{ 58 | spacing:10 59 | anchors.verticalCenter: his.verticalCenter 60 | Rectangle{ 61 | width: 30 62 | height: 30 63 | color:"transparent" 64 | } 65 | Image{ 66 | source:"../img/"+company 67 | width: 30 68 | height: 30 69 | layer.enabled: true 70 | layer.effect: OpacityMask { 71 | maskSource: Rectangle { 72 | width: 30 73 | height: 30 74 | radius: 4 75 | } 76 | } 77 | } 78 | Label{ 79 | anchors.verticalCenter: parent.verticalCenter 80 | text:title.substring(0,50) 81 | } 82 | Button{ 83 | flat: true 84 | icon{ 85 | source:"../icon/时间.svg" 86 | } 87 | text:setHis(viewtime/1000) 88 | } 89 | Button{ 90 | flat: true 91 | icon{ 92 | source:"../icon/时间1.svg" 93 | } 94 | text:time 95 | } 96 | } 97 | MouseArea{ 98 | anchors.fill: parent 99 | hoverEnabled: true 100 | onEntered: { 101 | his.color= "gray" 102 | } 103 | onExited: { 104 | his.color = "white" 105 | } 106 | onClicked: { 107 | bridge.history(vid) 108 | videoviews.videopath = videopath 109 | videoviews.vid = vid 110 | videoviews.main = 0 111 | videoviews.viewpro = viewtime 112 | videoviews.show() 113 | 114 | } 115 | } 116 | } 117 | 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Mycomponent/HomeView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import QtQuick.Dialogs 5 | import Qt5Compat.GraphicalEffects 6 | Rectangle { 7 | id: home 8 | property var tpg 9 | property int nowpage 10 | property bool ed :false 11 | property string seatext 12 | property int updateid 13 | function enablebutton(now){ 14 | if(now===1){ 15 | pre.enabled = false 16 | }else{ 17 | if(pre.enabled===false){ 18 | pre.enabled = true 19 | } 20 | } 21 | if(now===tpg || now===0){ 22 | next.enabled = false 23 | }else{ 24 | if(next.enabled===false){ 25 | next.enabled = true 26 | } 27 | } 28 | } 29 | // 30 | Popup{ 31 | id:editcard 32 | anchors.centerIn: parent 33 | width: home.width 34 | height: home.height 35 | closePolicy: Popup.NoAutoClose 36 | background:Rectangle{opacity: 0.5} 37 | function openme(vid,name,imgurl){ 38 | updateid=vid 39 | img.source=imgurl 40 | title.text = name 41 | neturl.clear() 42 | open() 43 | } 44 | FileDialog { 45 | id: fileDialog 46 | nameFilters: ["Image (*.jpg *.jpeg *.png)"] 47 | onAccepted: { 48 | img.source=selectedFile 49 | } 50 | } 51 | Rectangle{ 52 | width: 600 53 | height: 400 54 | radius: 10 55 | anchors.centerIn: parent 56 | IconButton{ 57 | x:540 58 | y:40 59 | icon.source: "../icon/delete.svg" 60 | onClicked: { 61 | bridge.delvideo(updateid) 62 | editcard.close() 63 | data.clear() 64 | data.append(bridge.getdata(0)) 65 | } 66 | } 67 | GridLayout { 68 | anchors.centerIn: parent 69 | columns: 2 70 | Label{text:config.home.title} 71 | InputCom{ 72 | id:title 73 | } 74 | Label{text:config.home.img} 75 | Image{ 76 | id:img 77 | sourceSize.width: 100 78 | sourceSize.height:150 79 | MouseArea{ 80 | anchors.fill: parent 81 | onClicked: { 82 | fileDialog.open() 83 | } 84 | } 85 | } 86 | Label{text:config.home.net} 87 | TextField{id:neturl} 88 | SureButton{ 89 | c:1 90 | onClicked: { 91 | let imgpath = neturl.text.length>0?neturl.text:img.source 92 | bridge.update(updateid,title.text,imgpath) 93 | editcard.close() 94 | } 95 | } 96 | SureButton{ 97 | c:0 98 | onClicked: { 99 | editcard.close() 100 | } 101 | } 102 | } 103 | } 104 | } 105 | // 106 | Column{ 107 | Rectangle { 108 | id: daohang 109 | width: home.width 110 | height: 50 111 | color: "#FFB473" 112 | Row{ 113 | spacing: 15 114 | anchors.centerIn: daohang 115 | Button{ 116 | id:pre 117 | icon{ 118 | source:"../icon/previous.svg" 119 | } 120 | onClicked: { 121 | if(seatext.length===0){ 122 | data.clear() 123 | data.append(bridge.getdata(-1)) 124 | var now =bridge.getnowpage() 125 | totalpage.text = now+"/"+tpg 126 | enablebutton(now) 127 | }else{ 128 | nowpage -= 1 129 | var r = bridge.search(seatext,nowpage) 130 | data.clear() 131 | data.append(r[0]) 132 | tpg=r[1] 133 | totalpage.text =nowpage+"/"+tpg 134 | enablebutton(nowpage) 135 | } 136 | } 137 | } 138 | Label{ 139 | id:totalpage 140 | height: parent.height 141 | font.pixelSize: 16 142 | verticalAlignment: Text.AlignVCenter 143 | } 144 | Button{ 145 | id:next 146 | icon{ 147 | source:"../icon/next.svg" 148 | } 149 | onClicked: { 150 | if (seatext.length===0){ 151 | data.clear() 152 | data.append(bridge.getdata(1)) 153 | var now =bridge.getnowpage() 154 | totalpage.text = now+"/"+tpg 155 | enablebutton(now) 156 | }else{ 157 | nowpage += 1 158 | var r = bridge.search(seatext,nowpage) 159 | data.clear() 160 | data.append(r[0]) 161 | tpg=r[1] 162 | totalpage.text =nowpage+"/"+tpg 163 | enablebutton(nowpage) 164 | } 165 | } 166 | } 167 | Rectangle{ 168 | width: 50 169 | height: 30 170 | radius: 15 171 | border.color:jump.activeFocus?"#3914B0":"grey" 172 | Image{ 173 | width: 20 174 | height:20 175 | anchors.centerIn: parent 176 | source: "../icon/skip.svg" 177 | opacity: 0.4 178 | } 179 | TextInput { 180 | id: jump 181 | height: parent.height/2 182 | width: parent.width/2 183 | anchors.centerIn: parent 184 | validator: IntValidator{ bottom: 1; top: tpg; } 185 | onAccepted:{ 186 | data.clear() 187 | data.append(bridge.jump(jump.text)) 188 | totalpage.text = bridge.getnowpage()+"/"+tpg 189 | enablebutton(parseInt(jump.text)) 190 | } 191 | } 192 | } 193 | Rectangle{ 194 | id:serachinput 195 | width: 180 196 | height: 30 197 | radius: 10 198 | border.color:sea.activeFocus?"#3914B0":"grey" 199 | Image{ 200 | width: 20 201 | height:20 202 | anchors.centerIn: parent 203 | source: "../icon/search.svg" 204 | opacity: 0.4 205 | } 206 | TextInput { 207 | id: sea 208 | selectedTextColor: "blue" 209 | height: parent.height/2 210 | width: parent.width-20 211 | anchors.centerIn: parent 212 | clip:true 213 | onAccepted:{ 214 | if(sea.text.length>0){ 215 | nowpage = 1 216 | seatext = sea.text 217 | var r = bridge.search(seatext,nowpage) 218 | if (r[0].length===0){ 219 | data.clear() 220 | totalpage.text ="1/1" 221 | tpg=1 222 | enablebutton(1) 223 | }else{ 224 | data.clear() 225 | data.append(r[0]) 226 | tpg=r[1] 227 | totalpage.text ="1/"+tpg 228 | enablebutton(1) 229 | } 230 | } 231 | } 232 | } 233 | } 234 | IconButton{ 235 | icon{ 236 | source:"../icon/Edit.svg" 237 | } 238 | Rectangle{ 239 | radius: 5 240 | color:"yellow" 241 | id: tip 242 | Text{ 243 | text:config.home.edit 244 | anchors.centerIn: parent 245 | } 246 | x: 40 247 | y: 00 248 | height: 28 249 | width: 108 250 | visible:false 251 | } 252 | onClicked: { 253 | ed=!ed 254 | if(ed){ 255 | icon.color="yellow" 256 | tip.visible=true 257 | }else{ 258 | icon.color="black" 259 | tip.visible=false 260 | } 261 | } 262 | } 263 | } 264 | } 265 | ScrollView { 266 | id: scroll 267 | width: home.width 268 | height: home.height-50 269 | ScrollBar.vertical.policy: ScrollBar.AlwaysOff 270 | ScrollBar.horizontal.policy: ScrollBar.AlwaysOff 271 | clip: true 272 | Image{ 273 | x:home.width/2 274 | y:10 275 | width:home.width/2 276 | height:home.height-50 277 | source: "../icon/bg.jpg" 278 | } 279 | ListModel { 280 | id: data 281 | } 282 | Component.onCompleted:{ 283 | data.append(bridge.getdata(0)) 284 | tpg =bridge.gettotal() 285 | var now = bridge.getnowpage() 286 | totalpage.text=now+"/"+tpg 287 | enablebutton(now) 288 | } 289 | GridView { 290 | id: grid 291 | anchors.fill: parent 292 | anchors.centerIn: parent 293 | cellHeight: 255 294 | cellWidth: 170 295 | anchors.margins: 20 296 | model: data 297 | delegate: deleg 298 | Component { 299 | id: deleg 300 | Card{ 301 | edit:ed 302 | vid: id 303 | col:collect 304 | videopath: video 305 | titletext: title 306 | icopath:company 307 | width: grid.cellWidth-10 308 | height: grid.cellHeight-10 309 | imgpath:"file:///"+datadir+"/"+feng 310 | } 311 | } 312 | } 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /Mycomponent/IconButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | Button { 4 | flat: true 5 | icon{ 6 | width: 20 7 | height:20 8 | color:hovered?"#F00078":"black" 9 | } 10 | MouseArea{ 11 | anchors.fill: parent 12 | cursorShape: Qt.PointingHandCursor 13 | acceptedButtons: Qt.NoButton 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Mycomponent/InputCom.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.0 3 | import Qt5Compat.GraphicalEffects 4 | 5 | TextField { 6 | id: root 7 | 8 | property color checkedColor: "#D5DBDB" 9 | 10 | signal doubleClicked(var/*MouseEvent*/ event) 11 | 12 | placeholderText: qsTr("请输入内容") 13 | font.family: "Arial" 14 | font.pixelSize: 15 15 | font.weight: Font.Thin 16 | antialiasing: true 17 | 18 | background: Rectangle { 19 | implicitWidth: 213 20 | implicitHeight: 24 21 | radius: 8 22 | color: root.enabled ? "transparent" : "#F4F6F6" 23 | border.color: root.enabled ? root.checkedColor : "#D5DBDB" 24 | border.width: 2 25 | opacity: root.enabled ? 1 : 0.7 26 | 27 | layer.enabled: root.hovered 28 | layer.effect: DropShadow { 29 | id: dropShadow 30 | transparentBorder: true 31 | color: root.checkedColor 32 | samples: 10 /*20*/ 33 | } 34 | } 35 | 36 | cursorDelegate: Rectangle { 37 | width: 1 38 | height: parent.height*0.8 39 | color: root.checkedColor 40 | visible: root.focus 41 | 42 | Timer { 43 | interval: 600 44 | repeat: true 45 | running: root.focus 46 | onRunningChanged: parent.visible = running 47 | onTriggered: parent.visible = !parent.visible 48 | } 49 | } 50 | 51 | onDoubleClicked: selectAll() 52 | 53 | /* note: This signal was introduced in QtQuick.Controls 2.1 (Qt 5.8). */ 54 | onPressed: { 55 | _private.mouseEvent = event 56 | _private.isCheckDoubleClickedEvent++ 57 | 58 | if (! _checkDoubleClickedEventTimer.running) 59 | _checkDoubleClickedEventTimer.restart() 60 | } 61 | 62 | /* Private */ 63 | Item { 64 | id: _private 65 | property int isCheckDoubleClickedEvent: 0 66 | property var/*MouseEvent*/ mouseEvent 67 | 68 | Timer { 69 | id: _checkDoubleClickedEventTimer 70 | running: false 71 | repeat: false 72 | interval: 180 73 | onTriggered: { 74 | if (_private.isCheckDoubleClickedEvent >= 2) { 75 | /* Double Clicked Event */ 76 | root.doubleClicked(_private.mouseEvent) 77 | } 78 | 79 | stop() 80 | _private.isCheckDoubleClickedEvent = 0 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Mycomponent/Link.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | Rectangle{ 5 | property string url: "" 6 | height:30 7 | width:10*url.length 8 | color:"gray" 9 | radius:15 10 | Text{ 11 | anchors.centerIn: parent 12 | text:url 13 | } 14 | MouseArea{ 15 | anchors.fill: parent 16 | cursorShape: Qt.PointingHandCursor 17 | onClicked: { 18 | Qt.openUrlExternally("https://www."+url) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Mycomponent/MsgTip.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | Popup { 4 | id:pop 5 | width: 200 6 | height: 50 7 | modal: true 8 | focus: true 9 | property string msg 10 | anchors.centerIn: parent 11 | Text{ 12 | anchors.centerIn: parent 13 | text: msg 14 | } 15 | onOpened: { 16 | timer.start() 17 | } 18 | Timer{ 19 | id: timer 20 | interval: 1500 21 | onTriggered: { 22 | pop.close() 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Mycomponent/MyButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | Button { 4 | width: 104 5 | height: 30 6 | flat : true 7 | property bool click: false 8 | property string nameb: "" 9 | property Component stackview: null 10 | property bool c:true 11 | property int page:0 12 | background: Rectangle { 13 | id:bg 14 | anchors.fill: parent 15 | color: "transparent" 16 | radius: 5 17 | } 18 | icon{ 19 | width: 20 20 | height: 20 21 | } 22 | spacing:5 23 | font.pointSize: 10 24 | display: AbstractButton.TextBesideIcon 25 | MouseArea{ 26 | cursorShape: Qt.PointingHandCursor 27 | anchors.fill: parent 28 | hoverEnabled: true 29 | onEntered: { 30 | if (!click){ 31 | bg.color=cfg.c("left.b_hover") 32 | } 33 | } 34 | onExited: { 35 | if(!click){ 36 | bg.color = "transparent" 37 | } 38 | } 39 | onPressed: { 40 | background.color= "#B1009B" 41 | } 42 | onClicked: { 43 | for (var i of [homebutton,collectbutton,tagsbutton,historybutton,settingbutton,downnet]){ 44 | if (i.text===nameb){ 45 | if (i===downnet){ 46 | stack.pop() 47 | }else{ 48 | if(stack.currentItem.objectName==="net"){ 49 | stack.push(stackview) 50 | }else{ 51 | stack.replace(stackview) 52 | } 53 | } 54 | bg.color= cfg.c("left.b_click") 55 | click = true 56 | }else{ 57 | i.click = false 58 | i.background.color= "transparent" 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Mycomponent/NetDown.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | import Qt5Compat.GraphicalEffects 5 | 6 | Rectangle{ 7 | id:netdown 8 | Label{ 9 | y:10 10 | anchors.horizontalCenter: parent.horizontalCenter 11 | text:"github.com/alishanjack/h-box" 12 | } 13 | TextField{ 14 | id:input 15 | y:30 16 | font.family: "Arial" 17 | font.pixelSize: 20 18 | font.weight: Font.Thin 19 | antialiasing: true 20 | placeholderText: config.netdown.urltext 21 | anchors.horizontalCenter: parent.horizontalCenter 22 | background: Rectangle { 23 | implicitWidth: root.width*2/3 24 | implicitHeight: 30 25 | radius: 15 26 | // color: input.hovered ? "orange" : "#F4F6F6" 27 | border.color: input.hovered ? "orange" : "black" 28 | border.width: 2 29 | opacity: right.enabled ? 1 : 0.7 30 | layer.enabled: input.hovered 31 | layer.effect: DropShadow { 32 | id: dropShadow 33 | transparentBorder: true 34 | color: "orange" 35 | samples: 10 36 | } 37 | } 38 | onAccepted: { 39 | showtip.visible = false 40 | taskwindow.visible=true 41 | if (col.children.length<3){ 42 | let component = Qt.createComponent("TaskView.qml") 43 | let obj = component.createObject(col) 44 | obj.url=input.text 45 | bridge.starttask(obj,input.text) 46 | }else{ 47 | msgtip.msg="最大任务数为3" 48 | msgtip.open() 49 | } 50 | 51 | 52 | // bridge.spider(input.text) 53 | // loading.visible=true 54 | // input.enabled = false 55 | // loadingimg.visible = true 56 | // showtip.visible = false 57 | } 58 | } 59 | Rectangle{ 60 | y:80 61 | id: showtip 62 | height: parent.height-80 63 | width: parent.width 64 | Flow{ 65 | id:flow 66 | anchors.fill: parent 67 | anchors.margins: 4 68 | spacing: 10 69 | } 70 | } 71 | IconButton{ 72 | id:reset 73 | anchors.horizontalCenter: input.horizontalCenter 74 | anchors.horizontalCenterOffset: input.width/2+30 75 | anchors.verticalCenter: input.verticalCenter 76 | icon{ 77 | source:"../icon/close.svg" 78 | } 79 | ToolTip{ 80 | background: Rectangle{ 81 | radius: 5 82 | color:"yellow" 83 | } 84 | text:config.netdown.clear 85 | visible: reset.hovered 86 | } 87 | onClicked: { 88 | // visible = false 89 | input.text = "" 90 | // input.enabled = true 91 | // loading.visible = false 92 | // center.source="" 93 | // ico.source="" 94 | // bridge.clear() 95 | // showtip.visible = true 96 | } 97 | } 98 | CheckBox { 99 | id:headless 100 | anchors.horizontalCenter: input.horizontalCenter 101 | anchors.horizontalCenterOffset: input.width/2+60 102 | anchors.verticalCenter: input.verticalCenter 103 | checked:true 104 | onToggled:{ 105 | cfg.headless(checkState) 106 | } 107 | ToolTip{ 108 | background: Rectangle{ 109 | radius: 5 110 | color:"yellow" 111 | } 112 | text:config.netdown.headless 113 | visible: headless.hovered 114 | } 115 | } 116 | IconButton{ 117 | id:sync 118 | icon.source: "../icon/同步.svg" 119 | anchors.horizontalCenter: input.horizontalCenter 120 | anchors.horizontalCenterOffset: input.width/2+85 121 | anchors.verticalCenter: input.verticalCenter 122 | RotationAnimator { 123 | id:syncrun 124 | target: sync 125 | from: 0 126 | to: 360 127 | duration: 1000 128 | loops: Animation.Infinite 129 | } 130 | onClicked: { 131 | bridge.syncchrome() 132 | syncrun.start() 133 | sync.enabled=false 134 | syncchrome.open() 135 | } 136 | ToolTip{ 137 | background: Rectangle{ 138 | radius: 5 139 | color:"yellow" 140 | } 141 | text:config.netdown.sync 142 | visible: sync.hovered 143 | } 144 | } 145 | 146 | Item{ 147 | id:loading 148 | visible: false 149 | anchors.centerIn: parent 150 | MsgTip{ 151 | id:syncchrome 152 | msg:"数据后台同步中" 153 | } 154 | 155 | } 156 | 157 | //多任务栏 158 | Rectangle{ 159 | id:taskwindow 160 | height: parent.height 161 | width: parent.width 162 | y:80 163 | visible:false 164 | Column{ 165 | id:col 166 | anchors.fill: parent 167 | spacing:5 168 | } 169 | } 170 | 171 | 172 | 173 | Component.onCompleted: { 174 | let c = cfg.getheadless() 175 | headless.checked = c 176 | let urls = cfg.getnet().split(",") 177 | for (var i of urls){ 178 | let component = Qt.createComponent("Link.qml") 179 | let obj = component.createObject(flow) 180 | obj.url = i 181 | } 182 | } 183 | Connections { 184 | target: bridge 185 | function onSig1(show){ 186 | tip.text = show.text 187 | input.text = show.url 188 | title.text = show.title 189 | ico.source=show.ico 190 | if(show.img.length>0){ 191 | center.source="file:///"+show.img 192 | } 193 | if(show.reset===1){ 194 | reset.visible=true 195 | } 196 | } 197 | } 198 | Connections{ 199 | target: bridge 200 | function onSig2(load){ 201 | if(load.load===1){ 202 | loadingimg.visible = false 203 | prob.visible = true 204 | }else{ 205 | loadtext.text = `${load.now}/${load.total}` 206 | prob.value = load.now*100/load.total 207 | } 208 | } 209 | } 210 | Connections{ 211 | target: bridge 212 | function onSig3(ok){ 213 | if(ok===1){ 214 | syncrun.stop() 215 | sync.enabled=true 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Mycomponent/PreView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | Window{ 4 | property string path 5 | width: 1200 6 | height: 800 7 | title:"预览" 8 | Image{ 9 | anchors.fill: parent 10 | cache:false 11 | source:"file:///"+datadir+"/"+path 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Mycomponent/RadiuImg.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import Qt5Compat.GraphicalEffects 3 | 4 | Image{ 5 | id:bgimg 6 | Image{ 7 | id:img 8 | cache:false 9 | asynchronous: true 10 | visible: false 11 | } 12 | Rectangle{ 13 | id: rmask 14 | anchors.fill: bgimg 15 | radius: 10 16 | visible: false 17 | } 18 | OpacityMask { 19 | anchors.fill: img 20 | source: img 21 | maskSource: rmask 22 | visible: true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Mycomponent/SettingView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | Rectangle { 5 | color: "white" 6 | GridLayout{ 7 | anchors.centerIn: parent 8 | columns: 3 9 | Label{text:config.setting.lang} 10 | ComboBox { 11 | Layout.columnSpan: 2 12 | id:combox 13 | model: ["English","中文"] 14 | currentIndex : 0 15 | onActivated:{ 16 | if(index===0){ 17 | config = cfg.lang(0) 18 | }else if(index===1){ 19 | config = cfg.lang(1) 20 | } 21 | else if(index===2){ 22 | config = cfg.lang(2) 23 | } 24 | else if(index===3){ 25 | config = cfg.lang(3) 26 | } 27 | cfg.updatelang(index) 28 | } 29 | } 30 | // Label{text:config.setting.privacy} 31 | // Switch { 32 | // id:sw 33 | // Layout.columnSpan: 2 34 | // onToggled:{ 35 | // if(checked){ 36 | // cfg.updateswitch(1) 37 | // }else{ 38 | // cfg.updateswitch(0) 39 | // } 40 | // } 41 | // } 42 | Label{text:config.setting.datadir} 43 | Label{id:datadirtext} 44 | TextField{ 45 | visible: false 46 | id: datadir 47 | } 48 | IconButton{ 49 | icon.source:"../icon/Edit.svg" 50 | onClicked: { 51 | if(datadir.visible === false){ 52 | datadir.visible = true 53 | datadirtext.visible = false 54 | icon.source = "../icon/sure.svg" 55 | }else{ 56 | datadir.visible = false 57 | datadirtext.visible = true 58 | icon.source = "../icon/Edit.svg" 59 | cfg.updatedatadir(datadir.text) 60 | datadirtext.text = datadir.text 61 | } 62 | } 63 | } 64 | } 65 | Component.onCompleted: { 66 | // let lock = cfg.getlock() 67 | // if(lock===1){ 68 | // sw.checked=true 69 | // }else{ 70 | // sw.checked=false 71 | // } 72 | let lang = cfg.getlang() 73 | combox.currentIndex = lang 74 | let dir = cfg.getdatadir() 75 | datadirtext.text=dir 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Mycomponent/SureButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | 4 | Button{ 5 | property int c 6 | background: Rectangle{ 7 | color:c==1?"#409EFF":"#F56C6C" 8 | radius: 5 9 | } 10 | icon{ 11 | source:c==1?"../icon/sure.svg":"../icon/unsure.svg" 12 | width: 15 13 | height: 15 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Mycomponent/TagButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | Button { 4 | 5 | background: Rectangle{ 6 | anchors.fill: parent 7 | // color: "transparent" 8 | border.width: control.pressed ? 2: 1; 9 | border.color: (color.hovered || control.pressed) ?"green" :"red"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Mycomponent/TagView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import Qt5Compat.GraphicalEffects 4 | import QtQuick.Layouts 5 | import QtQuick.Dialogs 6 | 7 | Rectangle{ 8 | id:tagmain 9 | property bool hide: false 10 | property int tid: 0 11 | property int delte: 0 12 | property string editimg 13 | StackView { 14 | clip: true 15 | id: stack 16 | initialItem: main 17 | anchors.fill: parent 18 | } 19 | 20 | Component{ 21 | id:tagdesc 22 | TagdescView{ 23 | } 24 | } 25 | 26 | Component{ 27 | id:main 28 | 29 | Rectangle{ 30 | id:tag 31 | 32 | function pop(index){ 33 | data.remove(index) 34 | } 35 | // 弹出层 36 | Popup{ 37 | id:add 38 | anchors.centerIn: parent 39 | width: tagmain.width 40 | height: tagmain.height 41 | closePolicy: Popup.NoAutoClose 42 | background:Rectangle{opacity:0.5} 43 | FileDialog { 44 | id: fileDialog 45 | nameFilters: ["Image (*.jpg *.jpeg *.png)"] 46 | onAccepted: { 47 | precon.visible = true 48 | preimage.source=selectedFile 49 | } 50 | } 51 | Rectangle{ 52 | width: 600 53 | height: 400 54 | anchors.centerIn: parent 55 | GridLayout { 56 | anchors.centerIn: parent 57 | columns: 2 58 | Label{text:config.tag.title} 59 | InputCom{ 60 | id:title 61 | } 62 | Label{text:config.tag.desc} 63 | InputCom{ 64 | id:descput 65 | } 66 | Label{text:config.tag.img} 67 | Button{ 68 | text:"select" 69 | onClicked: { 70 | fileDialog.open() 71 | } 72 | } 73 | Rectangle{ 74 | id: precon 75 | width: 200 76 | height: 100 77 | Layout.columnSpan: 2 78 | visible: false 79 | Image{ 80 | id: preimage 81 | anchors.fill: parent 82 | } 83 | } 84 | SureButton{ 85 | c:1 86 | onClicked: { 87 | if (tid===0){ 88 | bridge.addtags(title.text,descput.text,preimage.source) 89 | add.close() 90 | data.clear() 91 | data.append(bridge.getalltags()) 92 | }else{ 93 | bridge.changetags(tid,title.text,descput.text,preimage.source) 94 | add.close() 95 | data.clear() 96 | data.append(bridge.getalltags()) 97 | } 98 | 99 | } 100 | } 101 | SureButton{ 102 | c:0 103 | onClicked: { 104 | add.close() 105 | } 106 | } 107 | } 108 | } 109 | } 110 | // 111 | Column{ 112 | Rectangle{ 113 | id: action 114 | width: tag.width 115 | height: 50 116 | color:"gray" 117 | Row{ 118 | x:30 119 | spacing: 10 120 | layoutDirection: Qt.RightToLeft 121 | LayoutMirroring.enabled: true 122 | anchors.verticalCenter: action.verticalCenter 123 | IconButton{ 124 | icon{ 125 | source:"../icon/add.svg" 126 | } 127 | onClicked: { 128 | title.text="" 129 | descput.text="" 130 | preimage.source= "" 131 | precon.visible=false 132 | tid = 0 133 | add.open() 134 | } 135 | } 136 | IconButton{ 137 | icon{ 138 | source:"../icon/Edit.svg" 139 | } 140 | onClicked: { 141 | hide= !hide 142 | delte = 0 143 | editimg = "../icon/Edit.svg" 144 | } 145 | } 146 | IconButton{ 147 | icon{ 148 | source:"../icon/delete.svg" 149 | } 150 | onClicked: { 151 | hide= !hide 152 | delte = 1 153 | editimg = "../icon/delete.svg" 154 | } 155 | } 156 | } 157 | } 158 | Rectangle{ 159 | id: content 160 | width: tag.width 161 | height: tag.height-action.height 162 | ListModel{ 163 | id:data 164 | } 165 | Component.onCompleted: { 166 | data.append(bridge.getalltags()) 167 | } 168 | GridView{ 169 | id: grid 170 | model: data 171 | anchors.fill: content 172 | delegate: cards 173 | anchors.margins: 20 174 | clip: true 175 | cellWidth: 400 176 | cellHeight: 200 177 | Component{ 178 | id:cards 179 | Rectangle{ 180 | id:bgimg 181 | width: grid.cellWidth-10 182 | height: grid.cellHeight-10 183 | radius:10 184 | clip:true 185 | MouseArea{ 186 | anchors.fill: parent 187 | onClicked: { 188 | stack.push(tagdesc,{tagid:id,tagtext:name}) 189 | } 190 | } 191 | Image{ 192 | id:img 193 | anchors.fill: bgimg 194 | cache:false 195 | asynchronous: true 196 | source: "../"+bg 197 | // visible: false 198 | LinearGradient { 199 | id: mask 200 | anchors.fill: img 201 | start: Qt.point(450, 0) 202 | end: Qt.point(0, 0) 203 | gradient: Gradient { 204 | GradientStop { position: 0.1; color: "transparent"} 205 | GradientStop { position: 0.9; color: "gray" } 206 | } 207 | } 208 | layer.enabled: true 209 | layer.effect: OpacityMask { 210 | maskSource: Rectangle { 211 | width: img.width 212 | height:img.height 213 | radius: 10 214 | } 215 | } 216 | } 217 | // Rectangle{ 218 | // id: rmask 219 | // anchors.fill: bgimg 220 | // radius: 10 221 | // visible: false 222 | // } 223 | // OpacityMask { 224 | // anchors.fill: img 225 | // source: img 226 | // maskSource: rmask 227 | // visible: true 228 | // } 229 | 230 | Rectangle{ 231 | id:edit 232 | width:100 233 | height: width 234 | x:bgimg.width- width/2 235 | y:-width/2 236 | color:"gray" 237 | radius:50 238 | opacity:0.8 239 | visible: hide 240 | Popup { 241 | id:popup 242 | x:50 243 | y:60 244 | width: 100 245 | height: 30 246 | closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent 247 | Row{ 248 | anchors.centerIn: parent 249 | spacing:10 250 | SureButton{ 251 | c:1 252 | onClicked: { 253 | bridge.deltags(id) 254 | pop(index) 255 | } 256 | } 257 | SureButton{ 258 | c:0 259 | onClicked: {popup.close()} 260 | } 261 | } 262 | } 263 | 264 | onVisibleChanged: { 265 | anima.start() 266 | } 267 | PropertyAnimation{ 268 | id:anima 269 | target: edit 270 | properties: "width" 271 | from:0 272 | to:100 273 | running: true 274 | } 275 | MouseArea{ 276 | anchors.fill: parent 277 | cursorShape: Qt.PointingHandCursor 278 | onClicked: { 279 | if(delte===0){ 280 | title.text=name 281 | descput.text=desc 282 | preimage.source= "../"+bg 283 | precon.visible=true 284 | tid = id 285 | add.open() 286 | }else{ 287 | popup.open() 288 | 289 | } 290 | } 291 | } 292 | Image{ // edit image 293 | x:20 294 | y:edit.width/2+10 295 | width: 20 296 | height: 20 297 | source:editimg 298 | 299 | } 300 | } 301 | 302 | 303 | 304 | Column{ 305 | x:20 306 | y:20 307 | spacing:5 308 | Text{ 309 | id:nametext 310 | font.pointSize: 17 311 | font.family: "Arial" 312 | text:name 313 | color:"white" 314 | } 315 | Text{ 316 | width: 280 317 | font.pointSize: 12 318 | text:desc 319 | color:"#f3c669" 320 | wrapMode: Text.WrapAnywhere 321 | } 322 | } 323 | Image { 324 | id:videosvg 325 | x:20 326 | y:bgimg.height-50 327 | width: 24 328 | height:24 329 | source: "../icon/video.svg" 330 | } 331 | Text{ 332 | anchors.horizontalCenter: videosvg.horizontalCenter 333 | anchors.horizontalCenterOffset: 30 334 | anchors.verticalCenter: videosvg.verticalCenter 335 | color:"white" 336 | font.pointSize: 15 337 | text:num 338 | } 339 | } 340 | } 341 | } 342 | } 343 | } 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Mycomponent/TagdescView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | import QtQuick.Dialogs 4 | Rectangle{ 5 | id: tagdesc 6 | property int tagid: 0 7 | property string tagtext 8 | Column{ 9 | Rectangle{ 10 | id: top 11 | width: tagdesc.width 12 | height: 50 13 | color:"gray" 14 | Text{ 15 | text:tagtext 16 | font.pointSize: 16 17 | anchors.centerIn: top 18 | font.family: "Arial" 19 | } 20 | Row{ 21 | spacing:10 22 | x:30 23 | anchors.verticalCenter: top.verticalCenter 24 | IconButton{ 25 | 26 | icon{ 27 | source: "../icon/back.svg" 28 | } 29 | onClicked: { 30 | stack.pop() 31 | } 32 | } 33 | IconButton{ 34 | icon{ 35 | source:"../icon/tagadd.svg" 36 | } 37 | onClicked: { 38 | transfer.visible = true 39 | } 40 | } 41 | } 42 | } 43 | Rectangle{ 44 | id: botom 45 | width: tagdesc.width 46 | height: tagdesc.height-50 47 | ListModel { 48 | id: data 49 | } 50 | function refresh(){ 51 | data.clear() 52 | data.append(bridge.gettagdata(tagid)) 53 | } 54 | Component.onCompleted:{ 55 | data.append(bridge.gettagdata(tagid)) 56 | transfer.tagid = tagid 57 | } 58 | GridView { 59 | id: grid 60 | anchors.fill: parent 61 | anchors.centerIn: parent 62 | cellHeight: 255 63 | cellWidth: 170 64 | anchors.margins: 20 65 | model: data 66 | clip: true 67 | delegate: deleg 68 | Component { 69 | id: deleg 70 | Card{ 71 | vid: id 72 | videopath: video 73 | titletext: title 74 | icopath:company 75 | width: grid.cellWidth-10 76 | height: grid.cellHeight-10 77 | imgpath:"file:///"+datadir+"/"+feng 78 | } 79 | } 80 | } 81 | } 82 | } 83 | TransForm{ 84 | id:transfer 85 | anchors.centerIn: tagdesc 86 | visible: false 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Mycomponent/TaskView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import Qt.labs.platform 1.1 4 | import QtQuick.Layouts 5 | import Qt5Compat.GraphicalEffects 6 | Rectangle{ 7 | id:task 8 | radius: 10 9 | height: 150 10 | border.color: "#747db4" 11 | border.width: 5 12 | color:"#a59f9f" 13 | width: parent.width*4/5 14 | property string url: "" 15 | property string tiptext:"" 16 | property string titletext:"" 17 | property string icotext:"" 18 | property int nums: 1 19 | clip:true 20 | signal mysig() 21 | anchors.horizontalCenter: parent.horizontalCenter 22 | Rectangle{ 23 | x: 10 24 | y: 15 25 | width: 120 26 | height: 120 27 | radius: 10 28 | anchors.verticalCenter: parent.verticalCenter 29 | anchors.left: parent.left 30 | anchors.leftMargin: 10 31 | Image { 32 | id:center 33 | fillMode: Image.PreserveAspectFit 34 | anchors.fill: parent 35 | source: "../icon/image.svg" 36 | } 37 | } 38 | Column{ 39 | id: column 40 | height: 120 41 | anchors.verticalCenter: parent.verticalCenter 42 | anchors.left: parent.left 43 | anchors.leftMargin: 150 44 | spacing:3 45 | Row { 46 | id: row 47 | width: 200 48 | height: 25 49 | spacing: 5 50 | Image { 51 | id: ico 52 | width: 25 53 | height: 25 54 | source: "../icon/网站.svg" 55 | layer.enabled: true 56 | layer.effect: OpacityMask { 57 | maskSource: Rectangle { 58 | width: 25 59 | height: 25 60 | radius: 5 61 | } 62 | } 63 | } 64 | Label { 65 | text: url 66 | color: "#7c225f" 67 | anchors.verticalCenter: parent.verticalCenter 68 | font.pointSize: 14 69 | } 70 | } 71 | Label{ 72 | id:title 73 | text:"标题获取中。。。" 74 | anchors.verticalCenter: parent.verticalCenter 75 | anchors.verticalCenterOffset: -20 76 | font.pointSize: 12 77 | } 78 | Row { 79 | id: row2 80 | width: 200 81 | height: 20 82 | anchors.top: parent.top 83 | spacing: 5 84 | anchors.topMargin: 60 85 | Image { 86 | id: image1 87 | width: 20 88 | height: 20 89 | source: "../icon/type.svg" 90 | fillMode: Image.PreserveAspectFit 91 | } 92 | Label { 93 | id: type 94 | text: "---" 95 | anchors.verticalCenter: parent.verticalCenter 96 | } 97 | 98 | Image { 99 | id: image2 100 | width: 20 101 | height: 20 102 | source: "../icon/时间.svg" 103 | fillMode: Image.PreserveAspectFit 104 | } 105 | 106 | Label { 107 | id: time 108 | text: "---" 109 | anchors.verticalCenter: parent.verticalCenter 110 | } 111 | BusyIndicator { 112 | id: busyIndicator 113 | anchors.verticalCenter: parent.verticalCenter 114 | } 115 | } 116 | 117 | Row { 118 | id: row1 119 | width: 200 120 | height: 20 121 | anchors.bottom: parent.bottom 122 | anchors.bottomMargin: 0 123 | spacing:5 124 | ProgressBar { 125 | id:prob 126 | property color color: "#3498DB" 127 | anchors.top: parent.top 128 | anchors.topMargin: 0 129 | from: 0 130 | to: 100 131 | value:0 132 | indeterminate:true 133 | background: Rectangle { 134 | implicitWidth: 200 135 | implicitHeight: 20 136 | color: "#EBEDEF" 137 | radius: implicitHeight / 2 138 | } 139 | contentItem: Item { 140 | implicitWidth: prob.background.implicitWidth 141 | implicitHeight: prob.background.implicitHeight 142 | Rectangle { 143 | width: prob.visualPosition * parent.width 144 | height: parent.height 145 | color: prob.color 146 | radius: parent.height / 2 147 | } 148 | } 149 | Text{ 150 | id:loadtext 151 | anchors.centerIn: parent 152 | text:"---/---" 153 | z:1 154 | } 155 | } 156 | Label{ 157 | id:tip 158 | anchors.verticalCenter: parent.verticalCenter 159 | } 160 | } 161 | } 162 | IconButton { 163 | id:trash 164 | width: 30 165 | height: 30 166 | anchors.right: parent.right 167 | anchors.bottom: parent.bottom 168 | anchors.rightMargin: 10 169 | anchors.bottomMargin: 10 170 | icon.source: "../icon/delete.svg" 171 | visible: false 172 | onClicked:{ 173 | mysig() 174 | task.destroy() 175 | if (col.children.length===1){ 176 | showtip.visible = true 177 | taskwindow.visible = false 178 | } 179 | } 180 | } 181 | MessageDialog { 182 | id:error 183 | } 184 | function recvfromthread(show){ 185 | tip.text = show.text 186 | if(typeof(show.title)!="undefined"){ 187 | title.text = show.title 188 | busyIndicator.visible = false 189 | } 190 | if(typeof(show.ico)!="undefined"){ 191 | ico.source=show.ico 192 | } 193 | if(typeof(show.img)!="undefined"){ 194 | center.source="file:///"+show.img 195 | } 196 | if(typeof(show.type)!="undefined"){ 197 | type.text=show.type 198 | } 199 | if(typeof(show.time)!="undefined"){ 200 | time.text=show.time.toString().substring(0,4)+"s" 201 | } 202 | if(show.reset===1){ 203 | trash.visible=true 204 | } 205 | if(typeof(show.error)!="undefined"){ 206 | error.text = "chrome驱动与你当前chrome版本不匹配,请下载匹配版本chrome驱动\n"+show.error+ 207 | "\n下载地址 http://chromedriver.storage.googleapis.com/index.html" 208 | error.open() 209 | } 210 | } 211 | function downloaing(load){ 212 | loadtext.text = `${load.now}/${load.total}` 213 | prob.value = load.now*100/load.total 214 | nums +=1 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Mycomponent/TaskView.qml.HQWFWg: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | Rectangle{ 5 | id:task 6 | radius: 10 7 | height: 150 8 | width: parent.width*4/5 9 | color:"gray" 10 | property string url: "" 11 | property string tiptext:"" 12 | property string titletext:"" 13 | property string icotext:"" 14 | property int nums: 1 15 | clip:true 16 | signal mysig() 17 | anchors.horizontalCenter: parent.horizontalCenter 18 | 19 | Rectangle{ 20 | x: 10 21 | y: 15 22 | width: 120 23 | height: 120 24 | radius: 10 25 | anchors.verticalCenter: parent.verticalCenter 26 | anchors.left: parent.left 27 | anchors.leftMargin: 10 28 | Image { 29 | id:center 30 | fillMode: Image.PreserveAspectFit 31 | anchors.fill: parent 32 | source: "../icon/image.svg" 33 | } 34 | } 35 | Column{ 36 | id: column 37 | x: 0 38 | y: 26 39 | height: 120 40 | anchors.verticalCenter: parent.verticalCenter 41 | anchors.left: parent.left 42 | anchors.leftMargin: 150 43 | spacing:3 44 | Row { 45 | id: row 46 | width: 200 47 | height: 25 48 | spacing: 5 49 | Image { 50 | id: ico 51 | width: 25 52 | height: 25 53 | source: "../icon/网站.svg" 54 | fillMode: Image.PreserveAspectFit 55 | } 56 | Label { 57 | text: url 58 | anchors.verticalCenter: parent.verticalCenter 59 | anchors.verticalCenterOffset: -20 60 | font.pointSize: 14 61 | } 62 | } 63 | Label{ 64 | id:title 65 | text:"标题获取中。。。" 66 | font.pointSize: 12 67 | } 68 | Row { 69 | id: row2 70 | width: 200 71 | height: 20 72 | anchors.top: parent.top 73 | spacing: 5 74 | anchors.topMargin: 60 75 | Image { 76 | id: image1 77 | width: 20 78 | height: 20 79 | source: "../icon/type.svg" 80 | fillMode: Image.PreserveAspectFit 81 | } 82 | Label { 83 | id: label 84 | text: qsTr("Label") 85 | anchors.verticalCenter: parent.verticalCenter 86 | } 87 | 88 | Image { 89 | id: image2 90 | width: 20 91 | height: 20 92 | source: "../icon/时间.svg" 93 | fillMode: Image.PreserveAspectFit 94 | } 95 | 96 | Label { 97 | id: label1 98 | text: qsTr("Label") 99 | anchors.verticalCenter: parent.verticalCenter 100 | } 101 | } 102 | 103 | Row { 104 | id: row1 105 | width: 200 106 | height: 20 107 | anchors.bottom: parent.bottom 108 | anchors.bottomMargin: 0 109 | 110 | ProgressBar { 111 | id:prob 112 | property color color: "#3498DB" 113 | anchors.top: parent.top 114 | anchors.topMargin: 0 115 | from: 0 116 | to: 100 117 | value:0 118 | indeterminate:true 119 | background: Rectangle { 120 | implicitWidth: 200 121 | implicitHeight: 20 122 | color: "#EBEDEF" 123 | radius: implicitHeight / 2 124 | } 125 | contentItem: Item { 126 | implicitWidth: prob.background.implicitWidth 127 | implicitHeight: prob.background.implicitHeight 128 | Rectangle { 129 | width: prob.visualPosition * parent.width 130 | height: parent.height 131 | color: prob.color 132 | radius: parent.height / 2 133 | } 134 | } 135 | Text{ 136 | id:loadtext 137 | anchors.centerIn: parent 138 | text:"---/---" 139 | z:1 140 | } 141 | } 142 | 143 | BusyIndicator { 144 | id: busyIndicator 145 | anchors.verticalCenter: parent.verticalCenter 146 | } 147 | } 148 | function recvfromthread(show){ 149 | tip.text = show.text 150 | if(typeof(show.title)!="undefined"){ 151 | title.text = show.title 152 | } 153 | if(typeof(show.ico)!="undefined"){ 154 | ico.source=show.ico 155 | } 156 | if(typeof(show.img)!="undefined"){ 157 | center.source="file:///"+show.img 158 | } 159 | } 160 | function downloaing(load){ 161 | loadtext.text = `${load.now}/${load.total}` 162 | prob.value = load.now*100/load.total 163 | nums +=1 164 | } 165 | 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Mycomponent/TaskView.qml.autosave.OLIAbr: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | Rectangle{ 5 | id:task 6 | radius: 10 7 | height: 150 8 | width: parent.width*4/5 9 | color:"gray" 10 | property string url: "" 11 | property string tiptext:"" 12 | property string titletext:"" 13 | property string icotext:"" 14 | property int nums: 1 15 | clip:true 16 | signal mysig() 17 | anchors.horizontalCenter: parent.horizontalCenter 18 | 19 | Rectangle{ 20 | x: 10 21 | y: 15 22 | width: 120 23 | height: 120 24 | radius: 10 25 | anchors.verticalCenter: parent.verticalCenter 26 | anchors.left: parent.left 27 | anchors.leftMargin: 10 28 | Image { 29 | id:center 30 | fillMode: Image.PreserveAspectFit 31 | anchors.fill: parent 32 | source: "../icon/image.svg" 33 | } 34 | } 35 | Column{ 36 | id: column 37 | x: 0 38 | y: 26 39 | height: 120 40 | anchors.verticalCenter: parent.verticalCenter 41 | anchors.left: parent.left 42 | anchors.leftMargin: 150 43 | spacing:3 44 | Row { 45 | id: row 46 | width: 200 47 | height: 25 48 | spacing: 5 49 | Image { 50 | id: ico 51 | width: 25 52 | height: 25 53 | source: "../icon/网站.svg" 54 | fillMode: Image.PreserveAspectFit 55 | } 56 | Label { 57 | text: url 58 | anchors.verticalCenter: parent.verticalCenter 59 | anchors.verticalCenterOffset: -20 60 | font.pointSize: 14 61 | } 62 | } 63 | Label{ 64 | id:title 65 | text:"标题获取中。。。" 66 | font.pointSize: 12 67 | } 68 | Row { 69 | id: row2 70 | width: 200 71 | height: 20 72 | anchors.top: parent.top 73 | spacing: 5 74 | anchors.topMargin: 60 75 | Image { 76 | id: image1 77 | width: 20 78 | height: 20 79 | source: "../icon/type.svg" 80 | fillMode: Image.PreserveAspectFit 81 | } 82 | Label { 83 | id: label 84 | text: qsTr("Label") 85 | anchors.verticalCenter: parent.verticalCenter 86 | } 87 | 88 | Image { 89 | id: image2 90 | width: 20 91 | height: 20 92 | source: "../icon/时间.svg" 93 | fillMode: Image.PreserveAspectFit 94 | } 95 | 96 | Label { 97 | id: label1 98 | text: qsTr("Label") 99 | anchors.verticalCenter: parent.verticalCenter 100 | } 101 | } 102 | 103 | Row { 104 | id: row1 105 | width: 200 106 | height: 20 107 | anchors.bottom: parent.bottom 108 | anchors.bottomMargin: 0 109 | 110 | ProgressBar { 111 | id:prob 112 | property color color: "#3498DB" 113 | anchors.top: parent.top 114 | anchors.topMargin: 0 115 | from: 0 116 | to: 100 117 | value:0 118 | indeterminate:true 119 | background: Rectangle { 120 | implicitWidth: 200 121 | implicitHeight: 20 122 | color: "#EBEDEF" 123 | radius: implicitHeight / 2 124 | } 125 | contentItem: Item { 126 | implicitWidth: prob.background.implicitWidth 127 | implicitHeight: prob.background.implicitHeight 128 | Rectangle { 129 | width: prob.visualPosition * parent.width 130 | height: parent.height 131 | color: prob.color 132 | radius: parent.height / 2 133 | } 134 | } 135 | Text{ 136 | id:loadtext 137 | anchors.centerIn: parent 138 | text:"---/---" 139 | z:1 140 | } 141 | } 142 | 143 | BusyIndicator { 144 | id: busyIndicator 145 | anchors.verticalCenter: parent.verticalCenter 146 | } 147 | } 148 | function recvfromthread(show){ 149 | tip.text = show.text 150 | if(typeof(show.title)!="undefined"){ 151 | title.text = show.title 152 | } 153 | if(typeof(show.ico)!="undefined"){ 154 | ico.source=show.ico 155 | } 156 | if(typeof(show.img)!="undefined"){ 157 | center.source="file:///"+show.img 158 | } 159 | } 160 | function downloaing(load){ 161 | loadtext.text = `${load.now}/${load.total}` 162 | prob.value = load.now*100/load.total 163 | nums +=1 164 | } 165 | 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Mycomponent/TransForm.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | import Qt5Compat.GraphicalEffects 4 | Rectangle{ 5 | id:roots 6 | width: 700 7 | height:500 8 | radius: 10 9 | property string transtitle 10 | property int tagid 11 | property var rightcheck : [] 12 | property var leftcheck : [] 13 | property var rightindex : [] 14 | property var leftindex : [] 15 | layer.enabled: true 16 | layer.effect: DropShadow { 17 | transparentBorder: true 18 | } 19 | onTagidChanged: { 20 | var dd = bridge.gettransfer(tagid) 21 | if(dd.check.length!==0){ 22 | rightdata.append(dd.check) 23 | } 24 | if(dd.all.length!==0){ 25 | leftdata.append(dd.all) 26 | } 27 | } 28 | function changetag(){ 29 | 30 | } 31 | Rectangle{ 32 | id:rightrel 33 | anchors.centerIn: roots 34 | anchors.horizontalCenterOffset: -(width/2+50) 35 | width: roots.width/3 36 | height: roots.height*4/5 37 | border.color:"gray" 38 | border.width: 2 39 | clip: true 40 | ListModel{ 41 | id:leftdata 42 | } 43 | ListView{ 44 | y:30 45 | anchors.fill: parent 46 | model:leftdata 47 | spacing: 10 48 | delegate: tagscoml 49 | Component{ 50 | id:tagscoml 51 | Rectangle{ 52 | width: rightrel.width 53 | height: 30 54 | CheckBox{ 55 | id:checkb 56 | x:25 57 | anchors.verticalCenter: parent.verticalCenter 58 | onToggled:{ 59 | if(checkState===Qt.Checked){ 60 | leftcheck.push({"mid":mid,"title":title}) 61 | leftindex.push(index) 62 | }else{ 63 | leftcheck.pop({"mid":mid,"title":title}) 64 | leftindex.pop(index) 65 | } 66 | } 67 | } 68 | Text{ 69 | x:50 70 | anchors.verticalCenter: checkb.verticalCenter 71 | text:title 72 | } 73 | } 74 | } 75 | } 76 | } 77 | Rectangle{ 78 | id:rightrec 79 | anchors.centerIn: roots 80 | anchors.horizontalCenterOffset: width/2+50 81 | width: roots.width/3 82 | height: roots.height*4/5 83 | border.color:"gray" 84 | border.width: 3 85 | clip: true 86 | ListModel{ 87 | id:rightdata 88 | } 89 | ListView{ 90 | y:30 91 | anchors.fill: parent 92 | model:rightdata 93 | spacing: 10 94 | delegate: tagscom 95 | Component{ 96 | id:tagscom 97 | Rectangle{ 98 | width: rightrec.width 99 | height: 30 100 | CheckBox{ 101 | id:checkb 102 | x:25 103 | anchors.verticalCenter: parent.verticalCenter 104 | onToggled:{ 105 | if(checkState===Qt.Checked){ 106 | rightcheck.push({"mid":mid,"title":title}) 107 | rightindex.push(index) 108 | }else{ 109 | rightcheck.pop({"mid":mid,"title":title}) 110 | rightindex.pop(index) 111 | } 112 | } 113 | } 114 | Text{ 115 | x:50 116 | anchors.verticalCenter: checkb.verticalCenter 117 | text:title 118 | } 119 | } 120 | } 121 | } 122 | } 123 | Button{ 124 | anchors.centerIn: roots 125 | anchors.verticalCenterOffset: 20 126 | icon{ 127 | source:"../icon/rarrow.svg" 128 | } 129 | onClicked: { 130 | rightdata.append(leftcheck) 131 | for(var i of leftindex){ 132 | leftdata.remove(i) 133 | } 134 | leftindex = [] 135 | leftcheck = [] 136 | } 137 | } 138 | Button{ 139 | anchors.centerIn: roots 140 | anchors.verticalCenterOffset: -20 141 | icon{ 142 | source:"../icon/larrow.svg" 143 | } 144 | onClicked: { 145 | leftdata.append(rightcheck) 146 | for(var i of rightindex){ 147 | rightdata.remove(i) 148 | } 149 | rightindex =[] 150 | rightcheck = [] 151 | } 152 | } 153 | Button{ 154 | anchors.centerIn: roots 155 | anchors.verticalCenterOffset: 140 156 | text:"确认" 157 | onClicked: { 158 | let l = [] 159 | print(rightdata) 160 | for (let i=0;i0?nums.indexOf(target):arr.sort((a,b)=>a-b).indexOf(target) 58 | arr.splice(index,1) 59 | return index 60 | } 61 | function setHis(times = 0){ 62 | if(typeof times ==='number'){ 63 | if(times <= 0){ 64 | return '00:00:00'; 65 | }else{ 66 | let hh = parseInt(times / 3600); //小时 67 | let shh = times - hh * 3600; 68 | let ii = parseInt(shh/ 60); 69 | let ss = parseInt(shh - ii * 60); 70 | return (hh < 10 ? '0'+hh : hh) + ':' + (ii < 10 ? '0'+ii : ii) +':'+(ss < 10 ? '0'+ss : ss); 71 | } 72 | }else{ 73 | return '00:00:00'; 74 | } 75 | } 76 | Column{ 77 | anchors.fill: parent 78 | Rectangle{ 79 | id: videoshow 80 | width: root.width 81 | height: root.height-90 82 | color: "black" 83 | VideoOutput { 84 | id: videoput 85 | anchors.fill: parent 86 | } 87 | MediaPlayer{ 88 | id: player 89 | videoOutput: videoput 90 | audioOutput: voloutput 91 | source: "file:///"+datadir+"/"+videopath 92 | onMediaStatusChanged:{ 93 | if(status===MediaPlayer.EndOfMedia){ 94 | playb.icon.source = "../icon/play.svg" 95 | } 96 | } 97 | onPositionChanged:function(n){ 98 | probar.value = n 99 | protext.text = setHis(n/1000) 100 | if (d.zimul.length>0){ 101 | try{ 102 | let f = searchInsert(d.zimul,n) 103 | if (f 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 270 21 | 210 22 | 25 23 | 23 24 | 25 | 26 | 27 | ... 28 | 29 | 30 | 31 | 32 | 33 | 560 34 | 320 35 | 80 36 | 24 37 | 38 | 39 | 40 | PushButton 41 | 42 | 43 | 44 | 45 | 46 | 260 47 | 300 48 | 97 49 | 22 50 | 51 | 52 | 53 | RadioButton 54 | 55 | 56 | 57 | 58 | 59 | 120 60 | 240 61 | 80 62 | 24 63 | 64 | 65 | 66 | PushButton 67 | 68 | 69 | 70 | 71 | 72 | 310 73 | 350 74 | 81 75 | 22 76 | 77 | 78 | 79 | CheckBox 80 | 81 | 82 | 83 | 84 | 85 | 170 86 | 120 87 | 81 88 | 22 89 | 90 | 91 | 92 | CheckBox 93 | 94 | 95 | 96 | 97 | 98 | 460 99 | 170 100 | 97 101 | 22 102 | 103 | 104 | 105 | RadioButton 106 | 107 | 108 | 109 | 110 | 111 | 380 112 | 430 113 | 168 114 | 41 115 | 116 | 117 | 118 | CommandLinkButton 119 | 120 | 121 | 122 | 123 | 124 | 50 125 | 240 126 | 300 127 | 200 128 | 129 | 130 | 131 | QQuickWidget::SizeRootObjectToView 132 | 133 | 134 | 135 | 136 | 137 | 138 | 0 139 | 0 140 | 800 141 | 21 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | QQuickWidget 150 | QWidget 151 |
QtQuickWidgets/QQuickWidget
152 |
153 |
154 | 155 | 156 |
157 | -------------------------------------------------------------------------------- /Mycomponent/qmldir: -------------------------------------------------------------------------------- 1 | module Mycomponent 2 | Mybutton Mybutton.qml 3 | HomeView HomeView.qml 4 | CollectView CollectView.qml 5 | HistoryView HistoryView.qml 6 | SettingView SettingView.qml 7 | VideoView VideoView.qml 8 | Card Card.qml 9 | MsgTip MsgTip.qml 10 | TagdescView TagdescView.qml 11 | IconButton IconButton.qml 12 | TagButton TagButton.qml 13 | InputCom InputCom.qml 14 | TransForm TransForm.qml 15 | DownLoad DownLoad.qml 16 | PreView PreView.qml 17 | NetDown NetDown.qml 18 | SureButton SureButton.qml 19 | RadiuImg RadiuImg.qml 20 | TaskView TaskView.qml 21 | Link Link.qml 22 | -------------------------------------------------------------------------------- /Mycomponent/transfer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Controls 3 | 4 | Rectangle{ 5 | id:roots 6 | width: 800 7 | height:600 8 | 9 | Row{ 10 | spacing:10 11 | anchors.centerIn: roots 12 | Rectangle{ 13 | width: roots.width/3 14 | height: roots.height*4/5 15 | color:"blue" 16 | } 17 | Rectangle{ 18 | width: roots.width/3 19 | height: roots.height*4/5 20 | color:"blue" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Mycomponent/util.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | Rectangle{ 5 | function dictgetvalue(data,key){ 6 | return data.hasOwnProperty(key)?data[key]:"" 7 | } 8 | Timer{ 9 | id:timer 10 | interval: 1000 11 | repeat: true 12 | onTriggered:{ 13 | let d = bridge.getprocess() 14 | if(d.end){ 15 | timer.stop() 16 | print("end---") 17 | }else{ 18 | protext.text = `${d.now}/${d.total}MB` 19 | probar.value = (d.now/d.total)*100 20 | } 21 | } 22 | } 23 | GridLayout{ 24 | anchors.centerIn: parent 25 | columns: 3 26 | Label{text:"标题"} 27 | TextField{ 28 | Layout.columnSpan: 2 29 | id:nbt 30 | onTextChanged:{dict.title=nbt.text} 31 | } 32 | Label{text:"封面"} 33 | TextField{ 34 | id:nf 35 | onTextChanged:{dict.feng=nf.text} 36 | } 37 | Image{ 38 | sourceSize.width: 17 39 | sourceSize.height: 17 40 | source:"../icon/qes.svg" 41 | } 42 | Label{text:"视频路径"} 43 | TextField{ 44 | id:nv 45 | onTextChanged:{dict.vp=nv.text} 46 | } 47 | Image{ 48 | sourceSize.width: 17 49 | sourceSize.height: 17 50 | source:"../icon/qes.svg"} 51 | Label{text:"下载目录"} 52 | TextField{ 53 | id:mu 54 | onTextChanged:{dict.path=mu.text} 55 | } 56 | Image{ 57 | sourceSize.width: 17 58 | sourceSize.height: 17 59 | source:"../icon/qes.svg"} 60 | Label{text:"网络代理"} 61 | TextField{ 62 | id:proiexs 63 | onTextChanged:{dict.proxy=proiexs.text} 64 | } 65 | Image{ 66 | sourceSize.width: 17 67 | sourceSize.height: 17 68 | source:"../icon/qes.svg"} 69 | Button{ 70 | text:"下载" 71 | onClicked: { 72 | bridge.spider(dict) 73 | timer.start() 74 | } 75 | } 76 | Button{ 77 | text:"清除" 78 | onClicked: { 79 | nbt.text = "" 80 | nf.text = "" 81 | nv.text = "" 82 | mu.text = "" 83 | proiexs.text = "" 84 | dict = {} 85 | } 86 | } 87 | Row{ 88 | Layout.columnSpan: 3 89 | ProgressBar { 90 | id:probar 91 | from:0 92 | to:100 93 | } 94 | Label{ 95 | id:protext 96 | } 97 | } 98 | Component.onCompleted: { 99 | if (JSON.stringify(dict) != '{}') { 100 | nbt.text = dictgetvalue(dict,"title") 101 | nf.text = dictgetvalue(dict,"feng") 102 | nv.text = dictgetvalue(dict,"vp") 103 | mu.text = dictgetvalue(dict,"path") 104 | proiexs.text = dictgetvalue(dict,"proxy") 105 | timer.start() 106 | } 107 | } 108 | 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | H-BOX 6 |

7 | 8 | [DOWNLOAD from github](https://github.com/alishanjack/h-box/releases) or [DOWNLOAD from gofile.io](https://gofile.io/d/44fq12) 9 | # 中文 | [English](https://github.com/alishanjack/h-box/blob/main/user.md) 10 | # H-BOX是什么? 11 | h-box是一个将视频从porn网站下载到本地,并可以收藏播放管理的桌面应用软件,目前只有window端。 12 | ## H-BOX介绍 13 | - 首页
14 | 下载过的视频会在这里显示
15 | 翻页功能,跳转功能,搜素功能,编辑功能,不多赘述。 16 | ![](https://github.com/alishanjack/h-box/blob/main/img/home.png) 17 | - 自带播放器
18 | 点击首页的卡片弹出播放页面
19 | 点击图片按钮会在后台生成视频预览图
20 | 点击心形按钮收藏视频
21 | 快进,音量调节 22 | ![](https://github.com/alishanjack/h-box/blob/main/img/video.jpg) 23 | - 收藏
24 | 首页的收藏会在这里显示,点击同样可以播放
25 | ![](https://github.com/alishanjack/h-box/blob/main/img/collect.png) 26 | - 标签
27 | 给视频分类用
28 | 新增按钮,编辑按钮,删除按钮,标题描述,背景图。 29 | ![](https://github.com/alishanjack/h-box/blob/main/img/tag.jpg) 30 | - 浏览历史
31 | 在首页播放过的视频记录在这里,点击同样可以播放,直接 32 | 跳转到上次播放点 33 | ![](https://github.com/alishanjack/h-box/blob/main/img/history.jpg) 34 | - 网络下载
35 | **下载的前提在于你的网络能访问到这个网站,软件不需要配置代理。**
36 | 从网站下载的界面,想要下载视频只需要将播放页的链接复制到 37 | 输入框再点击回车即可。具体操作看下方gif。 38 | ![](https://github.com/alishanjack/h-box/blob/main/img/netdown.jpg) 39 | **!!!注意!!!**
40 | 记得点击最右边的同步按钮,会将你的chrome的用户数据同步到软件的目录下, 41 | 这样selenium在请求网站的时候很容易躲过一些验证码的侦察,像cloudflare验证, 42 | twitter则需要登录cooikes 验证才能下载视频,你在chrome登录后再同步到selenium 43 | 去请求twitter便是登录状态,很容易便能抓到视频链接 44 | - 设置
45 | 语言切换目前支持中英文
46 | 数据目录控制下载的视频存在哪里,必须是绝对路径。建议不要改这个,改了比较麻烦。 47 | ![](https://github.com/alishanjack/h-box/blob/main/img/setting.jpg) 48 | - 规则解析
49 | 解析规则通过域名来判断对应的规则,一个网站有多个域名则用','隔开。
50 | type 控制下载的是m3u8 链接还是mp4链接
51 | video 资源路径的正则匹配,多种资源则用',' 隔开 52 | pic.re 封面的xpath 路径 pic.value 元素的值 53 | ico 对应的图标 54 | ...
55 | 开发更多的规则类型,以及应用 请加入讨论组一起讨论学习😁 [t.me/h-box](https://t.me/hboxapp) 56 | ![](https://github.com/alishanjack/h-box/blob/main/img/rule.jpg) 57 | - 最后
58 | 志同道合欢迎一起交流。 59 | - 视频
60 | ![](https://github.com/alishanjack/h-box/blob/main/img/H-box.gif) 61 | ## H-BOX的工作方式 62 | - h-box主要从目标网站获取三种东西,视频标题,视频封面,视频的资源链接。 63 | - h-box 获取视频链接并非像普通的爬虫一样解析html来获取资源链接,而是 64 | 通过监控网页发出的所有链接并通过规则中的正则匹配来获取资源。所以你完全 65 | 可以自己写规则来下载h-box中未支持的网站。标题与封面是通过解析html获取 66 | 但也是基于编写的规则,这很简单。目前支持的视频链接格式为m3u8与mp4。 67 | - h-box 使用selenium 工作方式为无头模式与非无头模式(界面可以选择), 68 | 一些网站在无头模式下根本无法加载出资源,只能选择非无头模式,手动触发网页发出视频链接。 69 | 这就是主要原理。 70 | ## H-BOX的运行需要 71 | - chrome,chrome driver,ffmpeg。(chrome driver,ffmpeg)安装包中已经附带了这些。 72 | 如果chrome drive与你的chrome版本不符,请下载对应的驱动在安装目录替换。 73 | # V2.0.5 release 74 | - 支持多任务 75 | - 加快了解析速度 76 | - BUG fix 77 | # V2.0.4 release 78 | - 增加视频图标 79 | - BUG fix 80 | # V2.0.3 release 81 | - 优化91,pornhub,xvideos,xnxx 解析速度。 82 | - BUG fix -_- 83 | # V2.0.2 release 84 | - BUG fix 85 | # V2.0.1 release 86 | 新功能: 87 | - 支持20 站点解析 88 | - 新增同步chrome数据 89 | - BUG fix 90 | 91 | # V2.0.0 release 92 | 新功能: 93 | - 支持15+ 站点解析 94 | - 新的使用规则解析站点方式 95 | - BUG fix 96 | -------------------------------------------------------------------------------- /chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/chromedriver.exe -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [left] 2 | bg='#6272a4' 3 | b_hover='white' 4 | b_click='#717DD7' 5 | [config] 6 | version='v2.0.4' 7 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/db.sqlite3 -------------------------------------------------------------------------------- /en_US.toml: -------------------------------------------------------------------------------- 1 | [left] 2 | home="home" 3 | collect="collect" 4 | tags="tags" 5 | history="history" 6 | import="import" 7 | netdown="netdown" 8 | setting="setting" 9 | [home] 10 | tip="Go to import or download your favorite video" 11 | title="title" 12 | img="img" 13 | net="net url" 14 | edit="edit mode" 15 | [collect] 16 | tip="Go and add your favorite" 17 | title="title: " 18 | addtime="addtime: " 19 | views="views: " 20 | [tag] 21 | title="title" 22 | desc="descrption" 23 | img="img" 24 | [history] 25 | tip="Nothing" 26 | [imp] 27 | tip="Drag the video here, preferably in MP4 format" 28 | [netdown] 29 | urltext="please input download url" 30 | title="title" 31 | feng="cover page" 32 | headless="chrome headless,If some websites cannot be parsed in headless mode, you can choose to close and manually assist." 33 | sync="sync chrome data" 34 | clear="clear input" 35 | [setting] 36 | lang="language" 37 | privacy="privacy protect" 38 | datadir="datadir" -------------------------------------------------------------------------------- /ffmpeg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/ffmpeg.exe -------------------------------------------------------------------------------- /icon/Edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/addimg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/bg-backward.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/bg-forward.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/icon/bg.jpg -------------------------------------------------------------------------------- /icon/box2-heart-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /icon/card-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icon/change.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/detail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/icon/favicon.ico -------------------------------------------------------------------------------- /icon/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/heart-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/history.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/icon/img.png -------------------------------------------------------------------------------- /icon/import.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/larrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/load.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/icon/loading.gif -------------------------------------------------------------------------------- /icon/love.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/mute.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/notfound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/icon/notfound.png -------------------------------------------------------------------------------- /icon/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/previous.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/qes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/rarrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/skip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/sure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/tag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/tagadd.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/type.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/unlove.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/unsure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/video.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/vol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/同步.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/图片预览.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/播放.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/时间.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/时间1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/网站.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/H-box.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/H-box.gif -------------------------------------------------------------------------------- /img/collect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/collect.png -------------------------------------------------------------------------------- /img/hbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/hbox.png -------------------------------------------------------------------------------- /img/history.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/history.jpg -------------------------------------------------------------------------------- /img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/home.png -------------------------------------------------------------------------------- /img/img.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/local.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/local.jpg -------------------------------------------------------------------------------- /img/net.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/net.jpg -------------------------------------------------------------------------------- /img/netdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/netdown.jpg -------------------------------------------------------------------------------- /img/rule.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/rule.jpg -------------------------------------------------------------------------------- /img/set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/set.jpg -------------------------------------------------------------------------------- /img/setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/setting.jpg -------------------------------------------------------------------------------- /img/tag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/tag.jpg -------------------------------------------------------------------------------- /img/video.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/img/video.jpg -------------------------------------------------------------------------------- /logfile.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alishanjack/h-box/c2c6056b157d35c69a12ba47c9987bc8971ca7e9/logfile.log -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import pathlib 3 | from spider import * 4 | import os 5 | import subprocess 6 | from time import strftime,gmtime 7 | import sys 8 | import toml 9 | from PySide6.QtCore import QObject, Slot, Signal, QThread 10 | from PySide6.QtWidgets import QApplication 11 | from Global import Global 12 | from PySide6.QtGui import QIcon 13 | from PySide6.QtQml import QQmlApplicationEngine, QmlElement 14 | import db 15 | import time 16 | from dirsync import sync 17 | import logging 18 | import threading 19 | from PIL import Image 20 | QML_IMPORT_NAME = "io.qt.textproperties" 21 | QML_IMPORT_MAJOR_VERSION = 1 22 | 23 | Log_Format = "%(levelname)s %(asctime)s - %(message)s" 24 | 25 | logging.basicConfig(filename = "logfile.log", 26 | filemode = "a", 27 | format = Log_Format, 28 | level = logging.ERROR) 29 | 30 | logger = logging.getLogger("main.log") 31 | 32 | 33 | @QmlElement 34 | class Bridge(QObject): 35 | sig = Signal(str,arguments=['res']) 36 | sig1 = Signal("QVariant",arguments=['show']) 37 | sig2 = Signal("QVariant",arguments=['load']) 38 | sig3 = Signal(int,arguments=['ok']) 39 | num = 0 40 | @Slot(int, result=list) 41 | def getdata(self, num): 42 | Global.now_page += num 43 | return db.getdata() 44 | 45 | 46 | @Slot(result=int) 47 | def gettotal(self): 48 | return db.gettotal() 49 | 50 | @Slot(result=int) 51 | def getnowpage(self): 52 | return Global.now_page 53 | 54 | @Slot(int, result=list) 55 | def jump(self, num): 56 | Global.now_page = num 57 | return db.getdata() 58 | 59 | @Slot(str, int,result=list) 60 | def search(self, text,page): 61 | return db.getdata(text,page) 62 | 63 | @Slot(int, result=list) 64 | def love(self, num): 65 | if num == 0: 66 | Global.collect_page = 1 67 | else: 68 | Global.collect_page += 1 69 | return db.getcollectdata() 70 | 71 | @Slot(int) 72 | def history(self, id): 73 | db.recordhistory(id) 74 | 75 | @Slot(result=list) 76 | def getallhistory(self): 77 | return db.gethistorydata() 78 | 79 | @Slot(int, int) 80 | def uphistory(self, id, num): 81 | db.updatehistory(id, num) 82 | 83 | @Slot(result=list) 84 | def getalltags(self): 85 | return db.getalltags() 86 | 87 | @Slot(int, result=list) 88 | def gettagdata(self, num): 89 | return db.gettagdata(num) 90 | 91 | @Slot(str, str, str) 92 | def addtags(self, title, desc, imgpath): 93 | db.addtags(title, desc, imgpath) 94 | 95 | @Slot(int, str, str, str) 96 | def changetags(self, id, title, desc, imgpath): 97 | db.ctags(id, title, desc, imgpath) 98 | 99 | @Slot(int) 100 | def deltags(self, num): 101 | db.deltag(num) 102 | 103 | @Slot(int, int,int,str) 104 | def collect(self, id, num,ts,vpath): 105 | if num ==1: 106 | t = threading.Thread(target=gencpic,args=(id,ts,vpath,num)) 107 | t.setDaemon(True) 108 | t.start() 109 | else: 110 | db.change_collect(id, num, "") 111 | 112 | @Slot(int,result="QVariant") 113 | def getdetail(self,id): 114 | return db.detail(id) 115 | 116 | @Slot(int,result="QVariant") 117 | def gettransfer(self,id): 118 | return db.transfer(id) 119 | 120 | @Slot(list,int) 121 | def changetag(self,l,id): 122 | s = "" 123 | nums = len(l) 124 | for i in l: 125 | s += f"{int(i)}," 126 | s =s.rstrip(",") 127 | db.changetags(s,id,nums) 128 | 129 | @Slot(str,str,str) 130 | def localimport(self,title,feng,video): 131 | db.insertvideo(title,feng,video) 132 | 133 | @Slot(str,str,str,str) 134 | def netimport(self,title,feng,video,mu): 135 | t = threading.Thread(target=download,args=(title,feng,video,mu)) 136 | t.start() 137 | 138 | @Slot(str,int) 139 | def genimg(self,path,id): 140 | t = threading.Thread(target=genpreview, args=(path,id,self.sig)) 141 | t.setDaemon(True) 142 | t.start() 143 | 144 | 145 | @Slot(result="QVariant") 146 | def getprocess(self): 147 | return Global.process 148 | 149 | @Slot(int,str,str) 150 | def update(self,id,title,img): 151 | if "notfound" not in img: 152 | img = img.lstrip('file:///') 153 | newpath = db.cpimg(img,id) 154 | else: 155 | newpath = "" 156 | db.updatedata(id,title,newpath) 157 | 158 | @Slot(str,str) 159 | def stredit(self,s): 160 | l = s.split('/')[0] 161 | return l.rstrip(".mp4") 162 | 163 | @Slot(str) 164 | def spider(self,url): 165 | Global.download['url'] = url 166 | Global.download['run'] = 1 167 | Global.download['text'] = "页面解析中" 168 | self.sig1.emit(Global.download) 169 | t = threading.Thread(target=parser,args=(url,self.sig1,self.sig2)) 170 | t.setDaemon(True) 171 | t.start() 172 | 173 | 174 | @Slot(result="QVariant") 175 | def running(self): 176 | return Global.download 177 | 178 | @Slot() 179 | def clear(self): 180 | Global.download = {"url":"","text":"","img":"","run":0,"title":"","reset":0,"ico":"","load":0} 181 | 182 | @Slot(int,result=int) 183 | def getimgrun(self,id): 184 | return Global.genimg.get(id,0) 185 | 186 | @Slot(int) 187 | def delvideo(self,id): 188 | db.delvideo(id) 189 | 190 | @Slot() 191 | def syncchrome(self): 192 | if not Global.download['sync']: 193 | t = threading.Thread(target=syncdata,args=(self.sig3,)) 194 | t.setDaemon(True) 195 | t.start() 196 | 197 | @Slot(QObject,str) 198 | def starttask(self,qml,url): 199 | task = ThreadTask(url) 200 | task.message.connect(qml.recvfromthread) 201 | task.download.connect(qml.downloaing) 202 | qml.mysig.connect(self.deltask) 203 | t = threading.Thread(target=task.run) 204 | t.setDaemon(True) 205 | t.start() 206 | 207 | @Slot() 208 | def deltask(self): 209 | print("------------") 210 | 211 | 212 | class ThreadTask(QObject): 213 | message = Signal("QVariant") 214 | download = Signal("QVariant") 215 | def __init__(self,url): 216 | super(ThreadTask, self).__init__() 217 | self.url = url 218 | 219 | def destory(self): 220 | Bridge.num -= 1 221 | print(Bridge.num) 222 | 223 | def run(self): 224 | parser(self.url,self.message,self.download) 225 | 226 | 227 | 228 | 229 | 230 | 231 | def syncdata(sig): 232 | Global.download['sync'] = True 233 | home = str(pathlib.Path.home()) + r"\AppData\Local\Google\Chrome\User Data" 234 | pwd = os.getcwd() 235 | pwd = pwd.replace("\\", "/") + "/userdir" 236 | sync(home, pwd, 'sync') 237 | Global.download['sync'] = False 238 | sig.emit(1) 239 | 240 | 241 | 242 | 243 | 244 | def download(title,feng,video,mu): 245 | path = db.getdatadir()+f"/{mu}/" 246 | if not os.path.exists(path): 247 | os.mkdir(path) 248 | s = 0 249 | for i in (feng,video): 250 | rep = httpx.get(i,verify=False,proxies=gethttpxporoxy()) 251 | if s==0: 252 | with open(path+f"{title}.jpg","wb") as f: 253 | f.write(rep.content) 254 | s += 1 255 | else: 256 | with open(path+f"{title}.mp4","wb") as f: 257 | f.write(rep.content) 258 | db.insertvideo(title, f"milker/{mu}/{title}.jpg", f"milker/{mu}/{title}.mp4") 259 | 260 | def gencpic(id,ts,vpath,num): 261 | text = strftime("%H:%M:%S", gmtime(ts / 1000)) 262 | cmd = f'ffmpeg.exe -ss "{text}" -i "{vpath}" img/img{id}.jpg -y' 263 | # os.system(cmd) 264 | o = subprocess.Popen(cmd,stdout=subprocess.PIPE,shell=True, 265 | stderr=subprocess.PIPE) 266 | o.wait() 267 | db.change_collect(id, num, f"img{id}.jpg") 268 | 269 | def genpreview(path,id,sig): 270 | Global.genimg[id]=1 271 | yulan = "" 272 | try: 273 | basedirs = db.getdatadir()+"/" 274 | workpath = path.split("/") 275 | path = basedirs + path 276 | workdir = basedirs + workpath[0] 277 | out = subprocess.Popen('ffmpeg.exe -i "%s"' % path, stdout=subprocess.PIPE,shell=True, 278 | stderr=subprocess.PIPE) 279 | out.wait() 280 | strout, strerr = out.communicate() 281 | l = strerr.decode('gb18030', 'ignore').split("\n") 282 | timedur = 60 283 | for i in l: 284 | i = i.strip(" ") 285 | if i.startswith("Duration"): 286 | d = i.split(',')[0].split(": ")[1] 287 | dd = d.split(":") 288 | dur = int(dd[1]) * 60 + int(dd[2].split(".")[0]) 289 | timedur = int(dur / 25) 290 | break 291 | s = r'ffmpeg.exe -i "%s" -vf fps=1/%s "%s/%s"' % (path, timedur, workdir, "images-%d.png") 292 | out = subprocess.Popen(s, stdout=subprocess.PIPE,shell=True, 293 | stderr=subprocess.PIPE) 294 | out.communicate() 295 | newimg = Image.new('RGB', (5 * 800, 5 * 500)) 296 | for y in range(1, 6): 297 | for x in range(1, 6): 298 | im = Image.open(workdir + '/images-%s.png' % ((y - 1) * 5 + x)) 299 | im = im.resize((800, 500)) 300 | newimg.paste(im, ((x - 1) * 800, (y - 1) * 500)) 301 | name = "yulan%s.png" % id 302 | namepath = workdir + "/" + name 303 | newimg.save(namepath) 304 | db.updatepreview(workpath,name,id) 305 | for ii in range(1, 26): 306 | os.remove(workdir + "/images-%s.png" % ii) 307 | yulan = workpath[0] + '/' + name 308 | except Exception as e: 309 | logging.error(e) 310 | finally: 311 | Global.genimg.pop(id) 312 | sig.emit(yulan) 313 | 314 | 315 | 316 | 317 | 318 | @QmlElement 319 | class Config(QObject): 320 | def __init__(self): 321 | super(Config, self).__init__() 322 | self.cfg = toml.load('config.toml') 323 | 324 | @Slot(str, result=str) 325 | def c(self, key): 326 | if '.' in key: 327 | o, t = key.split('.') 328 | return self.cfg[o][t] 329 | 330 | @Slot(int,result="QVariant") 331 | def lang(self,t): 332 | if t==9: 333 | t = db.getlang() 334 | if t==0: 335 | c = toml.load('en_US.toml') 336 | c['netdown']['tiptext'] = self.getnet() 337 | return c 338 | elif t==1: 339 | c = toml.load('zh_CN.toml') 340 | c['netdown']['tiptext'] = self.getnet() 341 | return c 342 | elif t==2: 343 | pass 344 | elif t==3: 345 | pass 346 | # t, encoding = locale.getdefaultlocale() 347 | 348 | 349 | @Slot(result=int) 350 | def getlock(self): 351 | return db.getswitch() 352 | 353 | @Slot(str,result=bool) 354 | def checkpwd(self,pwd): 355 | return db.checkpwd(pwd) 356 | 357 | @Slot(int) 358 | def updateswitch(self,c): 359 | db.updateswitch(c) 360 | 361 | @Slot(result=int) 362 | def getlang(self): 363 | return db.getlang() 364 | 365 | @Slot(int) 366 | def updatelang(self,n): 367 | db.updatelang(n) 368 | 369 | @Slot(str) 370 | def updatedatadir(self,dir): 371 | db.updatedatadir(dir) 372 | 373 | @Slot(result=str) 374 | def getdatadir(self): 375 | return db.getdatadir() 376 | 377 | @Slot(int) 378 | def headless(self,state): 379 | if state == 0: 380 | Global.headless = False 381 | else: 382 | Global.headless = True 383 | db.setheadless(int(Global.headless)) 384 | 385 | @Slot(result=int) 386 | def getheadless(self): 387 | print("+++++++++++++++++++") 388 | return db.getheadless() 389 | 390 | @Slot(int) 391 | def mute(self,num): 392 | db.changemute(num) 393 | 394 | @Slot(float) 395 | def volvalue(self,num): 396 | db.changevalue(num) 397 | 398 | @Slot(result="QVariant") 399 | def loadvol(self): 400 | return db.getvol() 401 | 402 | @Slot(result=str) 403 | def getnet(self): 404 | t = toml.load("rule.toml") 405 | s = ",".join(t.keys()) 406 | return s 407 | 408 | 409 | 410 | if __name__ == "__main__": 411 | db.datadir() 412 | Global.ruleset = toml.load("rule.toml") 413 | app = QApplication(sys.argv) 414 | app.setWindowIcon(QIcon('icon/box2-heart-fill.svg')) 415 | engine = QQmlApplicationEngine() 416 | bridge = Bridge() 417 | config = Config() 418 | engine.rootContext().setContextProperty('bridge', bridge) 419 | engine.rootContext().setContextProperty('cfg', config) 420 | engine.load("main.qml") 421 | if not engine.rootObjects(): 422 | sys.exit(-1) 423 | sys.exit(app.exec()) 424 | -------------------------------------------------------------------------------- /main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Window 3 | import QtQuick.Controls 2.0 4 | import "Mycomponent" 5 | Window { 6 | id: root 7 | width: 1050 8 | height: 600 9 | visible: true 10 | title: qsTr("H-box") 11 | property var dict:({}) 12 | property string datadir:cfg.getdatadir() 13 | property var config:({}) 14 | Component.onCompleted: { 15 | // stack.push(netview) 16 | // stack.push(home) 17 | config = cfg.lang(9) 18 | let lock = cfg.getlock() 19 | if(lock===0){ 20 | main.visible = true 21 | lockview.visible = false 22 | }else{ 23 | main.visible = false 24 | lockview.visible = true 25 | } 26 | } 27 | VideoView{ 28 | id: videoviews 29 | } 30 | MsgTip{ 31 | id:msgtip 32 | } 33 | Row{ 34 | id:main 35 | visible:false 36 | Rectangle { 37 | id: left 38 | height: root.height 39 | width: 130 40 | color: cfg.c("left.bg") 41 | Rectangle{ 42 | id:version 43 | visible: false 44 | height: 30 45 | width:parent.width*2/3 46 | anchors.horizontalCenter: left.horizontalCenter 47 | y:root.height-70 48 | radius: 10 49 | color:"yellow" 50 | Text { 51 | anchors.centerIn: parent 52 | text: "new version" 53 | } 54 | MouseArea{ 55 | anchors.fill: parent 56 | cursorShape: Qt.PointingHandCursor 57 | onClicked: { 58 | Qt.openUrlExternally("https://github.com/alishanjack/h-box") 59 | } 60 | } 61 | } 62 | Label{ 63 | anchors.horizontalCenter: left.horizontalCenter 64 | y:root.height-30 65 | text:cfg.c("config.version") 66 | } 67 | Column { 68 | anchors.horizontalCenter: left.horizontalCenter 69 | spacing: 10 70 | y:10 71 | MyButton { 72 | id:homebutton 73 | nameb:config.left.home 74 | text: config.left.home 75 | stackview: home 76 | icon{ 77 | source: "./icon/home.svg" 78 | } 79 | 80 | } 81 | MyButton { 82 | id:collectbutton 83 | text: config.left.collect 84 | nameb:config.left.collect 85 | stackview: collect 86 | icon{ 87 | source: "./icon/love.svg" 88 | } 89 | } 90 | MyButton { 91 | id:tagsbutton 92 | text: config.left.tags 93 | nameb: config.left.tags 94 | stackview: tags 95 | icon{ 96 | source: "./icon/tag.svg" 97 | } 98 | } 99 | MyButton { 100 | id:historybutton 101 | text: config.left.history 102 | stackview: history 103 | nameb:config.left.history 104 | icon{ 105 | source: "./icon/history.svg" 106 | } 107 | } 108 | // MyButton{ 109 | // id:daoru 110 | // text:config.left.import 111 | // nameb:config.left.import 112 | // stackview: daoruview 113 | // icon{ 114 | // source:"./icon/import.svg" 115 | // } 116 | // } 117 | MyButton{ 118 | id:downnet 119 | text:config.left.netdown 120 | nameb:config.left.netdown 121 | stackview: netview 122 | icon{ 123 | source:"./icon/download.svg" 124 | } 125 | } 126 | MyButton { 127 | id:settingbutton 128 | text: config.left.setting 129 | nameb: config.left.setting 130 | stackview: setting 131 | icon{ 132 | source: "./icon/setting.svg" 133 | } 134 | } 135 | Button{ 136 | width: 104 137 | height: 30 138 | spacing:5 139 | font.pointSize: 10 140 | text:"Join Us" 141 | background: Rectangle { 142 | anchors.fill: parent 143 | color: "white" 144 | radius: 5 145 | } 146 | icon{ 147 | width: 20 148 | height: 20 149 | source: "./icon/telegram.svg" 150 | } 151 | onClicked: { 152 | Qt.openUrlExternally("https://t.me/hboxapp") 153 | } 154 | } 155 | Button{ 156 | width: 104 157 | height: 30 158 | spacing:5 159 | font.pointSize: 10 160 | text:"Address" 161 | background: Rectangle { 162 | anchors.fill: parent 163 | color: "white" 164 | radius: 5 165 | } 166 | icon{ 167 | width: 20 168 | height: 20 169 | source: "./icon/github.svg" 170 | } 171 | onClicked: { 172 | Qt.openUrlExternally("https://github.com/alishanjack/h-box") 173 | } 174 | } 175 | } 176 | 177 | } 178 | Rectangle { 179 | id: right 180 | height: root.height 181 | width: root.width - 130 182 | StackView { 183 | clip: true 184 | id: stack 185 | initialItem: netview 186 | anchors.fill: parent 187 | } 188 | } 189 | } 190 | Component { 191 | id: home 192 | HomeView{} 193 | } 194 | Component { 195 | id: collect 196 | CollectView{} 197 | } 198 | Component { 199 | id: history 200 | HistoryView{} 201 | } 202 | Component { 203 | id: setting 204 | SettingView{} 205 | } 206 | Component { 207 | id: tags 208 | TagView{} 209 | } 210 | // Component { 211 | // id: daoruview 212 | // DownLoad{} 213 | // } 214 | Component { 215 | id: netview 216 | NetDown{objectName: "net"} 217 | } 218 | Rectangle { 219 | id:lockview 220 | anchors.fill: parent 221 | visible:false 222 | Column{ 223 | anchors.centerIn: parent 224 | Text{ 225 | id:err 226 | text:"passwd error" 227 | visible:false 228 | } 229 | TextField{ 230 | id:pwd 231 | placeholderText: "password" 232 | 233 | onAccepted:{ 234 | if(cfg.checkpwd(pwd.text)){ 235 | main.visible = true 236 | lockview.visible = false 237 | }else{ 238 | err.visible=true 239 | } 240 | } 241 | } 242 | } 243 | } 244 | Connections{ 245 | target: bgsig 246 | function onSig(ver){ 247 | version.visible = true 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /rule.toml: -------------------------------------------------------------------------------- 1 | ['91porn.com,9p234.com,91p46.com,wonderfulday25.live,91splt.app'] 2 | type='m3u8' 3 | video=['https://.*\.m3u8'] 4 | pic.re='//video[@id="player_one_html5_api"]' 5 | pic.value='poster' 6 | ico='http://91porn.com/favicon.ico' 7 | strategy='eager' 8 | ['pornhub.com'] 9 | type='m3u8' 10 | master=true 11 | video=['.*master.m3u8'] 12 | pic.re='//link[@rel="preload"]' 13 | pic.value='href' 14 | wait=15 15 | ico='https://ei.phncdn.com/www-static/favicon.ico' 16 | ['xhamster.com,xhamster3.com'] 17 | type='m3u8' 18 | master=true 19 | video=['https://19-18.b.cdn13.com.*master.m3u8.*','https://video7.xhcdn.com/.*\.mp4.m3u8'] 20 | pic.re='//meta[@property="og:image"]' 21 | pic.value='content' 22 | ico='https://static-lvlt.xhcdn.com/xh-desktop/images/favicon/favicon.ico' 23 | b=true 24 | ['xvideos.com'] 25 | type='m3u8' 26 | master=true 27 | video=['https://.*hls.m3u8'] 28 | pic.re='//meta[@property="og:image"]' 29 | pic.value='content' 30 | ico='https://static-cdn77.xvideos-cdn.com/v3/img/skins/default/logo/xv.white.32.png' 31 | ['xnxx.com'] 32 | type='m3u8' 33 | master=true 34 | video=['https://.*hls.m3u8'] 35 | pic.re='//meta[@property="og:image"]' 36 | pic.value='content' 37 | ico='https://www.xnxx.com/favicon-32x32.png' 38 | ['eporner.com'] 39 | type='m3u8' 40 | master=true 41 | click = [{type='id',value='moviexxx'}] 42 | video=['https://.*master.m3u8'] 43 | pic.re='//video[@id="EPvideo_html5_api"]' 44 | pic.value='poster' 45 | ico='https://static-ca-cdn.eporner.com/favicon.png' 46 | wait=5 47 | ['beeg.com'] 48 | type='m3u8' 49 | video=['https://.*\.mp4.m3u8','https://.*.ahcdn.com/.*\.m3u8'] 50 | pic.re='//meta[@property="og:image"]' 51 | pic.value='content' 52 | ico='https://beeg.com/assets/favicon-192.png' 53 | ['85tube.com'] 54 | type='mp4' 55 | video=['https://.*remote_control.php.*'] 56 | pic.re='//meta[@property="og:image"]' 57 | pic.value='content' 58 | ico='https://85tube.com/favicon.ico' 59 | ['91pornplus.com'] 60 | type='m3u8' 61 | video=['https://.*index.m3u8'] 62 | pic.re='//img[@style="object-fit: cover;"]' 63 | pic.value='src' 64 | wait=10 65 | ico='https://www.91pornplus.com/assets/favicons/tvideos.ico' 66 | ['hentaimama.io'] 67 | type='mp4' 68 | video=['https://.*.javprovider.com/.*\.mp4.*'] 69 | pic.re='//img[@class=" ls-is-cached lazyloaded"]' 70 | pic.value='src' 71 | ico='https://hentaimama.io/wp-content/uploads/2022/01/favicon-2.ico' 72 | ['hentaicloud.com'] 73 | type='mp4' 74 | video=['https://www.hentaicloud.com/media/videos/.*\.mp4'] 75 | pic.re='//meta[@property="og:image"]' 76 | pic.value='content' 77 | ico='https://www.hentaicloud.com/favicon.ico' 78 | ['hentai.tv'] 79 | type='mp4' 80 | video=['https://r2.1hanime.com/.*\.mp4'] 81 | click = [{type='xpath',value='//*[@id="aawp"]/div[2]/section/p/button',ignore=true}, 82 | {type='xpath',value='// *[ @ id = "aawp"] / div[2] / div / div[1] / div[2] / figure / div / button'}, 83 | {type='xpath',value='/html/body/div/div[1]',iframe=true}] 84 | pic.re='//meta[@property="og:image"]' 85 | pic.value='content' 86 | ico='https://hentai.tv/wp-content/uploads/2022/04/cropped-cropped-favicon1-32x32.png' 87 | ['uncensoredhentai.xxx'] 88 | type='mp4' 89 | video=['https://r2.1hanime.com/.*\.mp4','https://cdn\d+.htstreaming.com/cdn/.*\.mp4.mp4?.*'] 90 | click = [{type='xpath',value='/html/body/div/div[1]',iframe=true}] 91 | pic.re='//*[@id="thumbnail-post"]/img' 92 | pic.value='src' 93 | wait=5 94 | ico='https://uncensoredhentai.xxx/wp-content/uploads/2022/08/cropped-android-chrome-512x512-1-180x180.png' 95 | ['animeidhentai.com'] 96 | type='mp4' 97 | bak={type='xpath',value='/html/body/span',iframe=true} 98 | video=['https://r2.1hanime.com/.*\.mp4'] 99 | video-bak={type='xpath',value='/html/body/span/label/ul/li/a',iframe=true} 100 | click-bak=[{type='xpath',value='//*[@id="player"]',iframe=true}] 101 | click = [{type='xpath',value='/html/body/div/div[1]',iframe=true}] 102 | pic.re='//meta[@property="og:image"]' 103 | pic.value='content' 104 | wait=10 105 | ico='https://animeidhentai.com/storage/2021/12/cropped-App1-32x32.png' 106 | ['hentaifreak.org'] 107 | type='mp4' 108 | video=['https://media.hentaifreak.org/.*\.mp4'] 109 | pic.re='/html/body/div[1]/div[1]/main/article/div/div/div[4]/div/p/img' 110 | pic.value='src' 111 | click = [{type='class',value='vjs-big-play-button'}] 112 | wait=10 113 | ico='https://media.hentaifreak.org/2016/10/cropped-favicon-1-32x32.png' 114 | ['hentaiworld.tv'] 115 | type='mp4' 116 | video=['https://www.porn-d.xyz/.*\.mp4','https://jav-trailers.com/.*\.mp4','https://hentaiworld.tv/.*\.mp4'] 117 | pic.re='//meta[@property="og:image"]' 118 | pic.value='content' 119 | ico='https://cdn.hentaiworld.tv/wp-content/uploads/2020/04/cropped-1586514450834-32x32.png' 120 | ['hanime1.me'] 121 | type='mp4' 122 | video=['https://cdn5-videos.motherlessmedia.com/videos/.*\.mp4', 123 | 'https://vdownload-47.sb-cd.com/.*.mp4?.*','https://vdownload-\d+.hembed.com/.*\.mp4.*', 124 | 'https://cdne-mobile.youjizz.com/videos/.*\.mp4.*'] 125 | pic.re='//meta[@property="og:image"]' 126 | pic.value='content' 127 | check=true 128 | ico='https://cdn.jsdelivr.net/gh/jokogebai/jokogebai@v1.0.0/tab_logo.jpg' 129 | ["hanime.tv"] 130 | type='mp4' 131 | video=['https://file\d+.gofile.io/download/.*\.mp4','https://p30.highwinds-cdn.com/.*\.mp4\?md5=.*'] 132 | pic.re='//*[@id="app"]/div[15]/main/div/div/div/div[2]/div[1]/div/div[1]/img' 133 | pic.value='src' 134 | ico='https://hanime.tv/favicon.png' 135 | ["hentaihaven.xxx"] 136 | type='mp4' 137 | video=['https://alpha.hentai.download/alpha/.*\.mp4?token=.*'] 138 | pic.re='//meta[@property="og:image"]' 139 | pic.value='content' 140 | ico='https://hentaihaven.xxx/www/2019/04/cropped-hentaihavenico-32x32.png' 141 | ["miohentai.com"] 142 | type='mp4' 143 | click=[{'value'='//*[@id="video-player"]/button','type'='xpath'}] 144 | video=['https://cdn.miohentai.com/index.php.*'] 145 | pic.re='//meta[@property="og:image"]' 146 | pic.value='content' 147 | wait=10 148 | ico='https://miohentai.com/wp-content/uploads/2016/01/favicon-1.ico' 149 | ['twitter.com'] 150 | type='m3u8' 151 | m4s=true 152 | b=true 153 | wait=10 154 | video=['https://video.twimg.com/.*\.m3u8\?container=fmp4'] 155 | pic.re='//meta[@property="og:image"]' 156 | pic.value='content' 157 | ico='https://abs.twimg.com/favicons/twitter.2.ico' 158 | ['motherless.com'] 159 | type='mp4' 160 | video=['https://cdn\d+-videos.motherlessmedia.com/videos/.*\.mp4'] 161 | pic.re='//meta[@property="og:image"]' 162 | pic.value='content' 163 | ico='https://motherless.com/favicon.ico' 164 | ['optimize'] 165 | url='xvideos.com,xnxx.com,pornhub.com,91porn.com,9p234.com,91p46.com,wonderfulday25.live,91splt.app' 166 | -------------------------------------------------------------------------------- /user.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | H-BOX 6 |

7 | 8 | [DOWNLOAD from github](https://github.com/alishanjack/h-box/releases) or [DOWNLOAD from gofile.io](https://gofile.io/d/44fq12) 9 | # [中文](https://github.com/alishanjack/h-box/blob/main/README.md) | English 10 | # H-BOX manual 11 | ## H-BOX introduce 12 | - Home
13 | Downloaded videos will be displayed here.
14 | Page turning function, jump function, search function, editing function, not to repeat. 15 | ![](https://github.com/alishanjack/h-box/blob/main/img/home.png) 16 | - Player
17 | Click the card on the home page to pop up the playback page
18 | Clicking the image button will generate a video preview image in the background
19 | Click the heart button to bookmark the video
20 | fast forward, volume adjustment 21 | ![](https://github.com/alishanjack/h-box/blob/main/img/video.jpg) 22 | - Collect
23 | The favorites on the home page will be displayed here, click to play
24 | ![](https://github.com/alishanjack/h-box/blob/main/img/collect.jpg) 25 | - TAg
26 | for video classification
27 | Add button, edit button, delete button, title description, background image. 28 | ![](https://github.com/alishanjack/h-box/blob/main/img/tag.jpg) 29 | - History
30 | The videos played on the home page are recorded here, click to play them, and jump directly to the last playback point. 31 | ![](https://github.com/alishanjack/h-box/blob/main/img/history.jpg) 32 | - Internet download
33 | **The premise of downloading is that your network can access this website, and the software does not need to configure a proxy.**
34 | From the interface downloaded from the website, if you want to download the video, you only need to copy the link of the play page to 35 | enter the box and click Enter. See below for specific operationsg video。 36 | ![](https://github.com/alishanjack/h-box/blob/main/img/netdown.jpg) 37 | **!!!Notice!!!**
38 | Remember to click the synchronization button on the far right to synchronize your chrome user data to the software directory, 39 | In this way, selenium can easily avoid the detection of some verification codes when requesting websites, such as cloudflare verification, 40 | Twitter needs to log in to cooikes for verification to download videos. You log in to chrome and then sync to selenium 41 | to request twitter is to log in, and it is easy to grab the video link 42 | - Setting
43 | Language switching currently supports Chinese and English
44 | The data directory controls where the downloaded videos are stored, and must be an absolute path. It is recommended not to change this, it is more troublesome to chang. 45 | ![](https://github.com/alishanjack/h-box/blob/main/img/setting.jpg) 46 | - Rule analysis
47 | The parsing rules judge the corresponding rules through the domain name. If a website has multiple domain names, separate them with ','.
48 | type controls whether the download is an m3u8 link or an mp4 link
49 | Regular matching of video resource paths, multiple resources are separated by ',' 50 | pic.re the xpath path of the cover pic.value the value of the element 51 | Icon corresponding to ico 52 | ...
53 | Develop more rule types and applications Please join the discussion group to discuss and learn😁 [t.me/h-box](https://t.me/hboxapp) 54 | ![](https://github.com/alishanjack/h-box/blob/main/img/rule.jpg) 55 | - Finally
56 | Like-minded welcome to exchange. 57 | - Gif
58 | ![](https://github.com/alishanjack/h-box/blob/main/img/H-box.gif) 59 | ## H-BOX way of working 60 | - h-boxMainly obtain three things from the target website, video title, video cover, video resource link. 61 | - h-box Obtaining video links is not parsing html like ordinary crawlers to obtain resource links. 62 | Instead, resources are obtained by monitoring all links sent by the web page and matching regular rules in the rules. 63 | So you can write your own rules to download unsupported websites in h-box. 64 | The title and cover are obtained by parsing html but also based on written rules, 65 | which is very simple. Currently supported video link formats are m3u8 and mp4。 66 | - h-box Use selenium to work in headless mode and non-headless mode (the interface can be selected), 67 | Some websites cannot load resources at all in headless mode, and can only choose non-headless mode to manually trigger the webpage to send a video link. 68 | This is the main principle。 69 | ## H-BOX running needs 70 | - chrome, chrome driver, ffmpeg. (chrome driver, ffmpeg) these are already included in the installation package. 71 | If the chrome drive does not match your chrome version, please download the corresponding driver and replace it in the installation directory. 72 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import re 2 | from m3u8 import M3U8 3 | from urllib.request import getproxies 4 | import httpx 5 | headers = { 6 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 7 | 'Chrome/99.0.4844.74 Safari/537.36'} 8 | def getporoxy(): 9 | proxy = getproxies() 10 | if proxy: 11 | proxy['https'] = proxy['https'].replace('https','http') 12 | del proxy['ftp'] 13 | return proxy 14 | return None 15 | def gethttpxporoxy(): 16 | proxy = getproxies() 17 | if proxy: 18 | proxy['https'] = proxy['https'].replace('https','http') 19 | del proxy['ftp'] 20 | d = {} 21 | d['http://'] = proxy['http'] 22 | d['https://'] = proxy['https'] 23 | return d 24 | return None 25 | def loadm3u8(url,b=False): 26 | r = httpx.get(url, verify=False, headers=headers,proxies=gethttpxporoxy()) 27 | if b: 28 | # baseurl =r.url 29 | l = url.split("/") 30 | baseurl = l[0] + "//" + l[2] 31 | else: 32 | baseurl = "/".join(url.split("/")[:-1])+"/" 33 | # l = r.url.split("/") 34 | # baseurl = l[0] + "//" + l[2] 35 | # baseurl = urljoin(url, '.') 36 | return M3U8(r.text, base_uri=baseurl, custom_tags_parser=None) 37 | 38 | 39 | def checkhentai(url): 40 | if "uncensoredhentai" in url: 41 | return True 42 | # elif "hentai.tv" in url: 43 | # return True 44 | elif "animeidhentai" in url: 45 | return True 46 | 47 | 48 | def checkNameValid(name=None): 49 | """ 50 | 检测Windows文件名称! 51 | """ 52 | reg = re.compile(r'[\\/:*?"<>|\r\n]+') 53 | valid_name = reg.findall(name) 54 | if valid_name: 55 | for nv in valid_name: 56 | name = name.replace(nv, "_") 57 | return name 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /zh_CN.toml: -------------------------------------------------------------------------------- 1 | [left] 2 | home="首页" 3 | collect="收藏" 4 | tags="标签" 5 | history="历史" 6 | import="导入" 7 | netdown="网络下载" 8 | setting="设置" 9 | [home] 10 | tip="快去导入或者下载你心爱的视频吧" 11 | title="标题" 12 | img="图片" 13 | net="网络路径" 14 | edit="编辑模式" 15 | [collect] 16 | tip="快去添加你的最爱" 17 | title="标题:" 18 | addtime="添加时间:" 19 | views="观看次数:" 20 | [imp] 21 | tip="将视频拖动至此,最好是MP4格式" 22 | [tag] 23 | title="标题" 24 | desc="描述" 25 | img="图片" 26 | [history] 27 | tip="还没有什么浏览记录" 28 | [netdown] 29 | urltext="输入下载链接" 30 | title="标题" 31 | feng="封面" 32 | headless="无头模式。如果一些网站在无头模式下无法解析,可选择关闭并加以手动辅助。" 33 | sync="同步chrome数据" 34 | clear="清空输入框" 35 | [setting] 36 | lang="语言" 37 | privacy="隐私保护" 38 | datadir="数据目录" --------------------------------------------------------------------------------